Kirill Zonov

Golang for Rubyists. Part 7. Ruby and Golang, methods comparison

June 29, 2018 | 13 Minute Read

Hello, my dear friends. We all love Ruby (you too, right?) for its expressiveness and a set of useful methods out of the box. It would be a pleasure if when start using a new language, you had the similar methods or at least familiar ways to achieve same goals. Let’s try to pick a few useful methods from Ruby and find out, are there any equivalents in Golang for them.

pablo-hermoso-422530-unsplash

Array

Let’s start with something simple:

2.2.1 :001 > array = [1,3,5,7]
 => [1, 3, 5, 7]
2.2.1 :002 > array.shift
 => 1
2.2.1 :003 > array
 => [3, 5, 7]
2.2.1 :004 > array.push(1)
 => [3, 5, 7, 1]
2.2.1 :005 > array.unshift(array.pop)
 => [1, 3, 5, 7]

So, we used four methods here: shift, unshift, push, pop. Now let me try to do something similar in Golang:

package main
import ("fmt")
func main() {
	// Initialize the slice
	array := []int{1, 3, 5, 7}
	fmt.Println(array)
	// Shift
	x := array[0]
	fmt.Println(x)
	array = array[1:]
	fmt.Println(array)
	// Push
	array = append(array, x)
	fmt.Println(array)
	// Pop and Unshift
	x, array = array[len(array)-1], array[:len(array)-1]
	array = append([]int{x}, array...)
	fmt.Println(array)
}

https://play.golang.org/p/wNgO9LeX514

Not so short and expressive, huh? But still pretty straightforward. The only confusion I can anticipate is the last example, with pop and unshift. Just to explain, at first we assign to x the last element of an array and we modify the array variable to be everything starting from the element at index 0 to the pre-last one. And at the next line we create a new slice with just x and append an array to it. Also, you could notice here, that usage of [] to access an array/slice element is exactly the same as in Ruby (or most of the languages). Let’s move on to the more solid examples:

2.2.1 :001 > array = [1, 3, 5, 7]
 => [1, 3, 5, 7]
2.2.1 :002 > array.map { |a| a + 1 }
 => [2, 4, 6, 8]
2.2.1 :003 > array
 => [1, 3, 5, 7]
2.2.1 :005 > array.reject { |a| a % 3 == 0}
 => [1, 5, 7]
2.2.1 :006 > array
 => [1, 3, 5, 7]
2.2.1 :007 > array.sample
 => 7
2.2.1 :008 > array
 => [1, 3, 5, 7]
2.2.1 :009 >
2.2.1 :010 > array.min
 => 1
2.2.1 :011 >
2.2.1 :012 > array.max
 => 7
2.2.1 :014 > array.shuffle
 => [5, 7, 3, 1]

Here we iterate through a collection, without original collection modification, then we reject some value also without a modification, then we get a random element from it, then we have a basic min/max methods and a shuffle method, which shuffles an array elements. Are you optimistic about Golang abilities here? Let it a try! Nope, let me just tell you, that Go is not a functional language. Not at all. Not for an iota. The only way you can achieve map/reduce/filter functionality is by writing for loops. Also, there are no built-in min/max functions, unfortunately. Surprisingly I was able to find a shuffle implementation:

package main
import (
	"fmt"
	"math/rand"
)
func main() {
	// Initialize the slice
	array := []int{1, 3, 5, 7}
	fmt.Println(array)
	rand.Shuffle(len(array), func(i, j int) {
		array[i], array[j] = array[j], array[i]
	})
	fmt.Println(array)
}

Btw it still uses two for loops under the hood: https://golang.org/src/math/rand/rand.go#L232. Interesting to notice here is that it’s possible to pass a function to a method in Golang. Just to give an impression, here is the possible implementation of our own Filter function in Golang:

package main
import ("fmt")
func main() {
	array := []int{1, 3, 5, 7}
	filteredArray := Filter(array, func(i int) bool {
		return i%3 != 0
	})
	fmt.Println(filteredArray)
}
func Filter(in []int, fn func(int) bool) []int {
	out := make([]int, 0, len(in))
	for i := 0; i < len(in); i++ {
		current := in[i]
		if fn(current) {
			out = append(out, current)
		}
	}
	return out
}

https://play.golang.org/p/LFvueXzO-BU Here is a version, using interfaces, more generic, but still far from ideal https://play.golang.org/p/N9JvUXo7ugq

As you can see, this will work only with slices of integers. In the link above you can find a more generic implementation. It uses reflection mechanism of Golang, which we didn’t explore yet. So, this function may look cumbersome, but let’s take a look onto Ruby’s map method under the hood:

static VALUE
rb_ary_collect(VALUE ary)
{
    long i;
    VALUE collect;
    RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length);
    collect = rb_ary_new2(RARRAY_LEN(ary));
    for (i = 0; i < RARRAY_LEN(ary); i++) {
        rb_ary_push(collect, rb_yield(RARRAY_AREF(ary, i)));
    }
    return collect;
}

Looks pretty similar, isn’t it? Sure, in Ruby we don’t work with types - that’s the only difference. So, long story short, use for loops, that’s the way to go. But also one thing I want to mention is that there is a more convenient way to iterate through a collection, here how it looks like (works for both maps and arrays):

package main
import ("fmt")
func main() {
	array := []int{1, 3, 5, 7}
	for i := range array {
	  fmt.Println(i * 2)
	}
}

(https://play.golang.org/p/6g28EUCvQ6f)

Hashes

One of the most heavily used data structures in Ruby is Hash. Just kiddin, I have no idea, which is the most heavily used one. Let’s take a look at these examples, where we apply a couple of useful functions to it:

2.2.1 :001 > hash = { foo: 'bar', fizz: 'buzz', berlin: 'rain', munich: 'beer' }
 => {:foo=>"bar", :fizz=>"buzz", :berlin=>"rain", :munich=>"beer"}
2.2.1 :003 >
2.2.1 :004 >   hash.values
 => ["bar", "buzz", "rain", "beer"]
2.2.1 :005 > hash.keys
 => [:foo, :fizz, :berlin, :munich]
2.2.1 :006 >
2.2.1 :008 > hash.values_at(:munich, :berlin)
 , > ["beer", "rain"]

Nothing magical, just retrieving all values, all keys, and values by multiple keys, should be easy-peasy?

package main
import ("fmt")
func main() {
	hash := map[string]string{"foo": "bar", "fizz": "buzz", "berlin": "rain", "munich": "beer"}
	fmt.Println(hash)
	values := make([]string, 0, len(hash))
	for _, value := range hash {
		values = append(values, value)
	}
	fmt.Println(values)
	keys := make([]string, 0, len(hash))
	for key := range hash {
		keys = append(keys, key)
	}
	fmt.Println(keys)
	valuesAt := []string{hash["munich"], hash["berlin"]}
	fmt.Println(valuesAt)
}

(https://play.golang.org/p/YN0xnhly-r9)

aww

I am honestly not sure about the last one, probably there is a way to make it more readable. But anyway, beauty of Go in it’s straightforwardness :)

String

In Ruby, Strings can operate as arrays in most cases, but here we will pick a few string-specific methods:

2.2.1 :001 > str = " Star wars "
 => " Star wars "
2.2.1 :002 > str.upcase
 => " STAR WARS "
2.2.1 :003 > str.downcase
 => " star wars "
2.2.1 :004 > str.strip
 => "Star wars"

Nothing wow-like for Ruby, boring stuff, but let’s check, what Golang has to offer for such basic things:

package main
import (
	"fmt"
	"strings"
)
func main() {
	str := " Star wars "
	fmt.Println(str)
	fmt.Println(strings.ToLower(str))
	fmt.Println(strings.ToUpper(str))
	fmt.Println(strings.TrimSpace(str))
}

(https://play.golang.org/p/9GvLs9yFIea) Bang-bang! Such a bliss! Actually, Golang has a pretty extensive set of methods for strings manipulation, you can find way more in their official documentation: https://golang.org/pkg/strings/

Conclusion

There are much more to discover, so start using Golang for your pet projects or for your actual projects at work, f.e. if you plan to have some simple Lambda function, it’s a good place to introduce it, check out my past blog post on how to make it painlessly. But for today that’s it, thank you for reading. If you don’t want to miss the next posts, subscribe to my Twitter, tho I chat there for random topics, also I share some interesting posts I’ve discovered, as well as my own. And psst, there is a big orange button beneath, using it, you can buy me an espresso macchiato, which I really love and will appreciate ;) P.S. Thanks Claude for coffee and pleasant feedback, it helps staying motivated!