What about Go? Can it Functional?
Go isn’t a functional language like Scala, but it has functions that are first-class types. Let’s see what we can do with it. You can still enjoy this post with exactly no Go experience. We’ll walk you through the key concepts and any unusual syntax.
Previous posts examined Java 8’s functional additions and compared a short example written in Java 8 with the same one written in idiomatic Scala, a functional language designed from the ground up.
We concluded that while you can create functional code in Java 8, it’s not nearly as succinct as its Scala equivalent. But it turned out the reason has almost nothing to do with Java 8’s functional features: It’s because it doesn’t have type inference, meaning you have to declare types for the local variables. That can get really verbose, and the resulting code is often ungainly.
Significant in all of this is that this author found Java 8 functional code harder to write than the equivalent in Scala. The lack of type inference means that your code must compile to get the right feedback on the types in the sequence of assignments in your functional code. Ironically, it’s often that flow of types you’re trying to get help with. The Scala IDE (eclipse with specialized plug-ins) has some nice features that include showing the types of assignments it has worked out for us as tooltips. This allows a more iterative approach to finding the right sequence of operations, a natural way to program many people like. Specifically, when you can’t have that it gets in the way.
So much for Scala. What about Go? It’s got the advantage of type inference but is not truly functional. How does that look? Don’t worry if you’re not a Go expert yet. We’ll help you out with any unfamiliar syntax so you can follow along just fine.
We’re going to use our running example, the Sudoku-board validator. Thanks to Misha Korablin for the suggestion.
First, let’s work out some types we’re going to use. We need the row and column coordinates into the board, Index
. Then we need nine of those to describe a complete row, column, or block, Group
. Here are example Group
s for a row, column, and block.
In Go, the array index goes before the type. That’s not the only thing in Go that’s a bit odd on first look. Last, we need a two-dimensional array for the board values. Go has unsigned numbers, like uint
, so we use those. In Go, you can create named types as a shorthand. You can say type Index struct{ r, c int }
for each declaration in turn, or you can group them as follows.
We need some functions to create groups of Index values for each row, column, and block. Let’s start with a row.
Note how Go has nice for-loop semantics with range
, and that constructors are compact Index{r, c}
and provided. Also, we can see that types are written consistently after variables, parameters, and functions. Now, we could replicate this function for the equivalent columnOf
and blockOf
. If we’re honest, that would be lame in any case, but especially for a story about functional programming.
As stated earlier, Go is not a functional language, but we can use its functions to encapsulate the loops that create groups. Each Group
has a major and minor input—named a
and i
, respectively—which map to the corresponding row and column. For rowOf
it’s easy: Index{a, i}
. For columns we simply switch the major and minor indexes for Index{i, a}
. The complicated one, the blocks, are Index{a/3*3 + i/3, a%3*3 + i%3}
.
Wrapping this up in a function looks like
We can create a named type shorthand for this function, Indexer
, as
type Indexer func(int, int) Index
and now write the general-purpose function to generate groups that uses it as follows
Finally we can collect all the groups for each row, column, and block with the function
We call this by passing in the generator functions, which in turn get passed to the previous function. We can pass in the rowIndexer
function like this
var rows = allOf(rowIndexer)
or we can pass the function in-line like this for more idiomatic Go.
var rows = allOf(func(a, i int) Index { return Index{a, i} })
Speaking of idiomatic, Go usually deals with slices of arrays, not the arrays themselves. A slice in Go is a view on an array. A majority of library code deals with slices, so we should too. The allOf
function does this as follows
make
is a built-in function for creating slices and :=
is a convenient shorthand for declaration and assignment. It’s starting to come together now. We need a function to check that the board values in any group are valid. Go doesn’t really have sets (you usually end up using a map and ignore the values). So we define the output of the function that gets the values in a group as [9]bool
: If the cells in a group contain exactly one of each of the numbers 1 through 9, that slice will be 9 true
values. All other combinations let us know the group is invalid. We don’t have to count the occurrences of each number because any duplicate value means that another element must be missing and therefore false
. Here’s that function
A couple of new things to look out for here. First, we declare that the function valuesFrom
belongs to Board
. You can use any name you like for the type the function belongs to, including this
, self
, me
and so on. But Go’s tools will complain you’re not being very helpful using these generic names. Also, for...range
loops can have two control variables: the index over the range and the element at that index, respectively. If you don’t need the element, you can omit that variable. If you don’t need the index, you can use a _
.
This all culminates in a isValid
function that makes sure all the row, column, and block groups have exactly the numbers 1 to 9.
Here’s the final version of the Sudoku-board validator in Go, with a few lose ends tidied up.
So what did we learn?
We saw that because Scala is functional and has type inference, you can write terse code that preserves legibility. Java 8’s functional features can be handy and expressive, but we saw how Java 8’s lack of type inference made the code harder to read and anecdotally harder to write.
What Go lacks in functional features it makes up for in type inference. In addition, Go has some really nice programming features like expressive for loops and compact object creation.
Your verdict? Comment below and let us know. Did we skew the results or miss something? Let us know that too.
Postscript
Here are a couple more things we like about Go: small and fast.
Originally published at ruetech.io on June 2, 2016.