/ Go

Getting Going with Go

Yesterday I wrote about what sparked me to embark on a bit of a journey exploring concurrency in other languages. What I have decided to do is learn just enough to write and test a concurrent solution to N-Queens in each language, just as I did with web workers. I initially wanted to write a small web server in each as well but I think that this should suffice to give me a taste and suggest which languages I might want to spend more time in.

As I mentioned in that post my first stop was the Go language from Google. At this point I have actually completed this task in Go and moved on to learning Clojure, as it turns out the basics of Go are remarkably simple. But before I write up my solution I thought it would be good to give a bit of a primer on Go for anyone who might be unfamiliar and struggle with the new syntax. If you would like to run any of these little examples you can copy and paste them into the playground

Types

Yup, Go is a strongly typed language meaning that all variables and constants(yes there are constants!) must have a strict type. Check it out

// This is a comment
var myString string = "This is a string"
const planets int = 8
var myFloat float64 = 8.0
var mySmallFloat float32 = 8.0

If you have ever worked in Java or one of the C's this might look familiar but simply rearranged. Notice also that there are no semicolons. Turns out Go actually does require the semicolons but the compiler places them into the code at compile time so you don't have to.

Beyond the basic primitive types seen above Go also has arrays, slices(segments of arrays), structs and maps. Today I am only going to go into what is necessary to read the N-Queens solution which is arrays and slices.

array := [5]string{"some", "strings"}
slice := []string{"other", "strings"}
slice2 := []int

Ok wat? The syntax is identical right? Pretty much. The difference is that with the array I declared it's size in the []. An array is a fixed contiguous block in memory and thus it must be declared up front. A slice is a resizable representation of an array and it's what you will usually use.

With the array above I could not add a 6th value. I would have to create a new array of length 6 or more, move all the old values into it and then add the 6th value. Slices can be added to and subtracted from at will. Yes there are performance and behavior differences but they are beyond this scope.(One will come up in the N-Queens solution)

Functions

With typing out of the way functions are very familiar, just add types. Let me use three examples to explain some nuances.

func printString(myString string) {
  println(myString)
}

Simple right? We just place a type declaration after the function parameter so the function knows what to expect.

Now in this next example there is actually one more bit about types. The := tells the Go compiler to infer the type from the value on the right. You also get to drop the var but this only works in a function. Notice also we are now returning a value.

func addNums(a, b int) int {
  answer := a + b
  return answer
}

Just place a type declaration after the parameters and the function knows what type it is expected to return. Once again Go is strictly typed so if you try and return a value without a type declaration it won't compile.

Now let's get fancy

func doBoth(a, b int, myString string) (answer int) {
  println(myString)
  answer = a + b
  return
}

Notice how I can group the parameters of like type to save some key strokes. Also notice this time I named my return variable with it's type declaration. This function will now only return that specific variable and so I don't have to place it after the return.

Ok one more thing on basic functions, and this is really cool! You have the ability to return multiple values from a function!

func randomMulti() (a, b, c int) {
  a, b, c = 5, 6, 7
  return
} 
func main() {
  e, f, g := randomMulti()
  println(e, f, g) // 5, 6, 7
}

Alright, that was two things. Every Go package must have a main function, just as you might expect from Java or the C's. This is the entry point for the program and everything is called from there.

Ok that's functions in Go! Yes there are a few things I'm leaving out, like variadic functions, but this is just a primer. I'll link some great resources down below if you want to learn more.

Control Flow

Coming from Javascript, and again Java and the C's, the basics of control flow in Go look very familiar. There is one immediately noticeable difference, though. Go has no while loop! Everything is done with varying syntax in a for loop.

I probably should have mentioned this earlier but the goal of Rob Pike and the team making Go is to make a very powerful but simple language that is small and easy to learn. One of the ways in which they pursue this goal is to remove clutter in syntax and establish strong conventions in form and style. More on this later.

Lets look at the for loop.

for var i := 0; i < 10; i++ {
  println(i);
}

Looks familiar but no parens. Now lets take a look at looping over an array.

arr := []string{"some", "strings"}
for i, v := range arr {
  print(i, v)
  // 0, "some"  | 1, "strings"
}

This one will actually look somewhat familiar to anyone who has done any work in Python and I rather like it. But we still haven't seen how this replaces a while loop. Check it out.

i := 10
for i > 0 {
  println(i)
  i--
}

Boom! Just like that, the for loop in Go is like the Leatherman of control flow. I like it.

There is just one more thing for control flow and we will need it. That's the select statement. Just think switch.

i := 10
for i > 0 {
  select {
    case i%2 == 0:
      println("even")
    default:
      println("odd")
  }
}

Nothing unusual there.

Ok that's all the basics, just the big one left: go routines.

Concurrency

Go's method for achieving concurrency are the Go routines. (If you aren't sure the difference between concurrency and parallelism go watch this or read this) Go routines are just a play on words and the same thing as coroutines in other languages. I won't go into details here but just know they are a very lightweight process, not a thread. (See here for more on this difference)

How do they work? Beautifully simply. Remember the web workers and the message passing? Go routines also communicate via message passing but here you create a channel for messages to be passed on and the channels can be passed like a variable. To make a function run in a new go routine you simple put go before you call it.

// Here the submit param is a channel
// which sends int's
func addNums(c, d int, submit chan int) {
  answer := c + d
  submit <- answer
  close(submit)
}
func main() {
  a, b := 5, 6
  // Make a channel which handles int values
  sub := make(chan, int)
  // invoke addNums in a go routine
  go addNums(a, b, sub)
  // wait for the channel to send a response
  e := <- sub
}

And that's all there is do creating a channel and using a go routine to handle a function invocation. Obviously this is a trivial example but I think the N-Queens solution will illustrate a less trivial and effective use. Heck I even found the point at which their use became excessive and slowed the result. But that's for another day.

Hopefully you now feel informed about the basics of the Go syntax. I didn't get to go into any of the auxiliary things like the tooling and general experience (which is mostly a joy) but it's just too much for this post.

Should you want to dive further into the language I will put some resources here in the order you should approach them.