Recently I started learning the Go language. Go is an interesting language for me because it fills the gap nicely between a language that is pleasant to program in, like Ruby, and a language that performs well, like C. When learning a new language, I like to learn how to perform common tasks and see working code before diving into the language in depth.

Today I will compare a simple program in both Ruby in Go. The Ruby program features the use of classes, arrays, hashes, and strings in a way that is idiomatic for ruby. This means we will need to find and use similar features in Go, which will be the discussion of this post.

The program simply takes an array of mammals and groups them by the kind of animal (primate, carnivore, etc.). It then prints out the mammals in each group. This gist contains the full source code in both languages.

Go does not have classes

In Ruby, I declared a Mammal class, but I can't directly port this to Go because go doesn't have classes. Instead, I created a Mammal type from a struct. You can define functions that work on particular types in go, which gives you methods and class-like behavior.

The Mammal class (ruby):

class Mammal
  attr_accessor :name, :group

  def initialize(name, group)
    self.name = name
    self.group = group
  end
end

The Mammal type (go):

type Mammal struct {
    name  string
    group string
}

Arrays and slices

Ruby and Go both have arrays, but arrays in go are fixed-size. Go adds slices to the language which are more like ruby arrays, but not entirely.

Array literals (ruby):

mammals = [
  Mammal.new('Baboon', 'Primates'),
  Mammal.new('Dingo', 'Carnivora'),
  ...
]

Slice and struct literals (go):

var mammals = []Mammal{
    Mammal{"Baboon", "Primates"},
    Mammal{"Dingo", "Carnivora"},
  ...
}

Hashes and maps

Ruby has Hashes, go has maps. The only significant difference between them (besides function names and syntax) is that go's maps are statically typed. With ruby you can mix types for hash keys and values, in Go you must declare the key type and the value type for a particular map.

Declaring a hash of arrays in ruby:

grouped_mammals = {}

Declaring a map of slices in Go:

groupedMammals := make(map[string][]string)

Looping and grouping

Now we get to the fun part of this program - looping through the array of mammals and grouping. We want to build a map (hash in ruby) where the keys are the mammal group names, and the values are slices (or arrays in ruby) containing the names of each mammal in that group.

There are several intersting parts to this code, so let's go line-by-line.

Looping (enumerating) in ruby:

mammals.each do |mammal|
  ... do stuff
end

Looping in go:

for index, mammal in range mammals {
  ... do stuff
}

Notice the need for the range keyword.

One thing that's nice about ruby is the ||= operator makes it trivial to assign a default value to a variable if one is not already present. In Go, this takes a few extra steps.

Adding an empty array to a hash if no array exists for that key (ruby):

grouped_mammals[mammal.group] ||= []

Adding an empty slice to a map in go if no slice exists for that key (go):

_, ok := groupedMammals[mammal.group]
if !ok {
  groupedMammals[mammal.group] = make([]string, 0)
}

Appending an element to an array (ruby):

grouped_mammals[mammal.group] << mammal.name

Appending an element to a slice (go):

groupedMammals[mammal.group] = append(groupedMammals[mammal.group], mammal.name)

Printing the results

Looping over a hash (ruby):

grouped_mammals.each do |group, mammal_names|
  ... do stuff
end

Looping over a map (go):

for group, animalNames := range groupedMammals {
  ...
}

Notice the use of the range keyword in the loop again.

Printing a formatted string (ruby):

puts "#{group}: #{mammal_names.join(', ')}"

Printing a formatted string (go):

fmt.Printf("%s: %s\n", group, strings.Join(animalNames, ", "))

In Go, you can't just call methods on any type, oh well.

Summary

As you can see, the ruby program was pretty easy to translate to a go program. The go program is slightly longer, but the boilerplate is minimal and mostly comes in the form of type declarations. I still prefer the compact nature of ruby, but I could get used to go.