How do you learn "Go" when you are intermediate in Scala ๐Ÿš€ - Part I

Photo by Azamat E on Unsplash

How do you learn "Go" when you are intermediate in Scala ๐Ÿš€ - Part I

ยท

6 min read

So, you're a Scala enthusiast eyeing the mysterious realms of Go? Join me on this rollercoaster journey from Scala to Go, and let's dive into the captivating world of a new language! ๐ŸŒโœจ

The One2N Serendipity ๐ŸŒˆ

It all started with a serendipitous job opening at One2N, a magical land where they dance to the tunes of Go. The remote culture and no micromanaging caught my eye, and I knew I had to be a part of it. Despite knowing zilch about Go, I applied, and in the process, discovered ๐Ÿ•ต๏ธโ€โ™‚๏ธ the One2N Go Bootcamp. It is handcrafted by their CEO and it is open source. FREE.

Go Bootcamp Initiation ๐Ÿ’ป

Armed with determination and this open-source Go Bootcamp, I dived into the official Go Tour, just skimming through the syntax. Pointers and concurrency sent shivers down my spine, reminiscent of college days. Fast forward, and I found myself immersed in the first project โ€“ filtering numbers. Easy peasy, right? ๐Ÿค”

There are 8 user stories given in this bootcamp project. Starting with very basic and then to adding complex but real life requirements.

The First User Story

Lets start with the first one. Given a list of integers, write a function to return only the even numbers from this list.

Scala Way

I thought - โ€œCould it be any simpler?!!โ€. I think in Scala and here is the thought process

  • I first write isEven on integer type using implicit class

  • I use the filter method and call this as an anonymous function and voila you are done.

Ok let's write the test case first because we love TDD.

import org.scalatest.funsuite.AnyFunSuite

class NumberFilterTest extends AnyFunSuite {
  // Test case for filterEven method
  test("filterEven should return only even numbers") {
    val inputNumbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    assert(inputNumbers.evens == List(2, 4, 6, 8, 10)) 
  }

}

Now all we have to do is to create the `evens` logic. Lets get into it -

object NumberFiltering {

  implicit class IntOps(val n: Int) {
    lazy val isEven: Boolean = n % 2 == 0
  }

  implicit class ListIntOps(val numbers: List[Int]) {
    lazy val evens: List[Int] = numbers.filter(_.isEven)
  }

}

That's it. This is what you write in Scala given this problem.

Go: No Built-in Map, Filter, or Flatmap ๐Ÿ˜ฑ?

So now if you have to write in go, you just need to do the similar thing right? Let's find the way how do you add methods or fields to an existing class/type. When I went through the go tour I was keenly looking for this and came to know that there is something called "receiver method". Ok, so I have to write a receiver method on int class thats it. So I wrote

func (num int) isEven() bool {
    return num%2 == 0
}

but the compiler was angry.

In Go, you can't directly add methods to built-in types like int. However, you can achieve a similar effect by creating a new type based on int and then adding methods to that new type. So I went ahead and write below -

package main

import "fmt"

type MyInt int

func (num MyInt) isEven() bool {
    return num%2 == 0
}

Now you are at the same point. All i need to do is to add the filter method to the slice of int( appx slice in go, read more about it though). But wait Go does not have map, filter, flatmap inbuilt.

ok so what? Lets write it. So lets add this filter method for our case to the slice of int first. Again compiler complained that I can not create receiver method on slice of primitive inbuilt tyoes as well. Ok i just learnt i can create custom type. Lets add that -

type MyIntSlice []MyInt

func (nums MyIntSlice) filter(fn func(MyInt) bool) MyIntSlice {
  var result MyIntSlice
  for _, num := range nums {
    if fn(num) {
      result = append(result, num)
    }
  }
  return result
}

We have our filter method ready, lets use it to filter even numbers -

func evenNumbers(numbers MyIntSlice) MyIntSlice {
  return numbers.filter(func(num MyInt) bool {
      return num.isEven()
    })
}

// or in another approach,
// you can create a function instead of a receiver method for Int
// and use that in the MyIntSlice
func isEven(num MyInt) bool {
  return num.isEven()
}

func evenNumbers(numbers MyIntSlice) MyIntSlice {
  return numbers.filter(isEven)
}

Here is the full code -

package main

import "fmt"

type MyInt int
type MyIntSlice []MyInt

func (nums MyIntSlice) filter(fn func(MyInt) bool) MyIntSlice {
  var result MyIntSlice
  for _, num := range nums {
    if fn(num) {
      result = append(result, num)
    }
  }
  return result
}

func isEven(num MyInt) bool {
  return num.isEven()
}

func evenNumbers(numbers MyIntSlice) MyIntSlice {
  return numbers.filter(isEven)
}

Ok lets try to find out how to write test case. Although this is what I should have first done in TDD. Just found the template in github and also in the One2N bootcamp solution. So lets just copy paste -

import ( "reflect" "testing" )

func TestEvenNumbers(t *testing.T) { 
  // given
  numRange := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  want := []int{2, 4, 6, 8, 10}

  // when
  output := evenNumbers(numRange)

  // then
  if !reflect.DeepEqual(output, want) {
    t.Errorf("wanted: %v but got: %v", want, output)
  }

}

Arrggggg! Compile issue again. Its not recognising integer slice as MyIntSlice so it is not able to call our filter method. As []int and MyIntSlice is different to Go compiler. What to do?

I thought ok let's convert regular int slice to MyIntSlice. And I did that. Although, it had humongous consequences as the requirement grew but lets come to that in some time.

First update the custom type

type MyIntSlice struct {
  data []int
}

Next update the filter method to use this data part

func (nums MyIntSlice) filter(fn func(MyInt) bool) []int {
  var result []int
  for _, num := range nums.data {
    if fn(num) {
      result = append(result, num)
    }
  }
  return result
}

Finally add a new method to convert int slice to MyIntSlice

func GetIntSlice(slice [lint) IntSlice {
 return IntSlice {data: slice}
๏ฝ

Final code after making this changes:-

package main

import "fmt"

type MyInt int
type MyIntSlice struct { // change it to struct
  data []int
}

func (nums MyIntSlice) filter(fn func(MyInt) bool) []int {
  var result []int
  for _, num := range nums.data { // use nums.data
    if fn(num) {
      result = append(result, num)
    }
  }
  return result
}

func GetIntSlice(slice [lint) IntSlice { // add conversion method
 return IntSlice {data: slice}
๏ฝ

func isEven(num MyInt) bool {
  return num.isEven()
}

func evenNumbers(numbers MyIntSlice) MyIntSlice {
  return numbers.filter(isEven)
}

Now update the test case -

import ( "reflect" "testing" )

func TestEvenNumbers(t *testing.T) {
  // given
  numRange := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  want := []int{2, 4, 6, 8, 10}

  // when
  output := evenNumbers(getIntSlice(numRange)))

  // then
  if !reflect.DeepEqual(output, want) {
    t.Errorf("wanted: %v but got: %v", want, output)
  }
}

Hurray!!!! No compile issue. Test case also passed. BUT....

Did you find this article valuable?

Support Abhijit Dutta by becoming a sponsor. Any amount is appreciated!

ย