Function Types in Go

(2746 words, 13 minute read)

Most developers that are familiar with dynamic scripting language like Ruby, JavaScript, or Python have been exposed to higher-order functions. Coming from a scripting background, it can be hard to translate that knowledge into Go, since the type system seems to get in the way. On the other hand, coming from a statically typed, primarily object-oriented language like C++, C#, or Java, the problem may be the opposite: static type systems are less of a stumbling block, but the usage of higher-order functions may be less intuitive. For programmers with functional experience, most of this knowledge may come across as very pedestrian, but hopefully this article will at least demonstrate how to use Go’s type system with respect to functions.

In this article, we’ll look at a few situations where function types in Go can be very useful. The reader is not assumed to be an experienced Go programmer, although a cursory knowledge of Go will certainly be helpful in digesting the material.

Anonymous Function Closures

Off the bat, Go supports anonymous functions and closures. To me, Go’s support for anonymous functions is most reminiscent of the anonymous function support in JavaScript; it is not the flimsy kind of anonymous function support you get with Python’s lambdas (one statement only) or Ruby’s situation where there are multiple ways to create closures (I’m not a rubyist, so the whole proc/block/lambda/Method thing seems very convoluted to me). Anyway, declaring anonymous functions in Go is very lightweight:

func() {
	fmt.Println("hello")
}

As an expression, that’s fairly useless. Generally you want to save it to a variable, stick it in a data structure, or pass it to another function. It’s fairly common to see this type of thing hanging out in Go code:

fn := func() {
	fmt.Println("hello")
}

We now have a variable fn that is a function; it is of type func(). It can be invoked like any other function by saying fn(), or reassigned to any other func() that you might be interested in. Of course, since we have support for closures, we can also reference some data that was declared in the function’s containing scope:

x := 5
fn := func() {
	fmt.Println("x is", x)
}
fn()
x++
fn()

The output of this chunk of code would be:

x is 5
x is 6

Fairly standard. You can run that example here. So far, this is mostly the same as how it looks in JavaScript, except statically typed.

Function Collections

Of course, we can use functions in all the same places that we can use regular data types. We can, for example, create a slice of functions, chose a function at random, and then execute it. We define the type binFunc, which is a binary function; it takes two integers, and returns one integer. This isn’t strictly necessary for this example, but typing binFunc over and over again is much more convenient than typing func(int, int) int everywhere you see it.

type binFunc func(int, int) int

func main() {
	// seed your random number generator.
	rand.Seed(time.Now().Unix())

	// create a slice of functions
	fns := []binFunc{
		func(x, y int) int { return x + y },
		func(x, y int) int { return x - y },
		func(x, y int) int { return x * y },
		func(x, y int) int { return x / y },
		func(x, y int) int { return x % y },
	}

	// pick one of those functions at random
	fn := fns[rand.Intn(len(fns))]

	// and execute it
	x, y := 12, 5
	fmt.Println(fn(x, y))
}

(run this example)

Of course, this specific application is obscenely contrived, but it illustrates a core concept in Go: functions are first class.

Functions as Fields

Similarly, we might be interested in using a function type for a struct field. Doing so would allow us to attach additional information to a function, such as a label that we can access at runtime:

type op struct {
	name string
	fn   func(int, int) int
}

func main() {
	// seed your random number generator
	rand.Seed(time.Now().Unix())

	// create a slice of ops
	ops := []op{
		{"add", func(x, y int) int { return x + y }},
		{"sub", func(x, y int) int { return x - y }},
		{"mul", func(x, y int) int { return x * y }},
		{"div", func(x, y int) int { return x / y }},
		{"mod", func(x, y int) int { return x % y }},
	}

	// pick one of those ops at random
	o := ops[rand.Intn(len(ops))]

	x, y := 12, 5
	fmt.Println(o.name, x, y)
	fmt.Println(o.fn(x, y))
}

Functions can also be stored in maps, so you could do something similar with map[string]binFunc.

Recursive Function Types

Another interesting aspect of function types is that they allow us to define recursive function types, which are function types that operate on themselves. That is, a function type that either takes it’s own type as a parameter, or uses its own type as a return value. You can use this technique to do lots of things if you contrive hard enough, so I’ve contrived a way of using recursive functions to perform a random walk, just for the sake of illustration. Yes, this is an utterly contrived example. To start, we might say that a walk function is a function that takes a pointer to an integer, and returns a walk function:

type walkFn func(*int) walkFn

It is expected to increment the integer that is being pointed to, and then return a function describing the next step that should be taken. An example of a valid walkFn would be the following walkEqual function:

func walkEqual(i *int) walkFn {
	*i += rand.Intn(7) - 3
	return walkEqual
}

The effect of running this function would be that the integer that i points to would be randomly modified by +-3, and then the function would return itself. We could then perform a random walk in the following fashion:

fn, progress := walkEqual, 0
for i := 0; i < 20; i++ {
	fn = fn(&progress)
	fmt.Println(progress)
}

Of course, this isn’t very fun. Let’s involve two more functions, walkForward and walkBackward, which will guarantee that we walk in a “forward” (i.e., positive) direction, and “backward” (i.e., negative) direction. Each function will execute itself, and then return one of the other two functions, randomly. We’ll start with a little utility function named pickRandom that will randomly choose between its input functions, and return that function:

func pickRandom(fns ...walkFn) walkFn {
	return fns[rand.Intn(len(fns))]
}

Then we define our three “walk” functions as follows:

func walkEqual(i *int) walkFn {
	*i += rand.Intn(7) - 3
	return pickRandom(walkForward, walkBackward)
}

func walkForward(i *int) walkFn {
	*i += rand.Intn(6)
	return pickRandom(walkEqual, walkBackward)
}

func walkBackward(i *int) walkFn {
	*i += -rand.Intn(6)
	return pickRandom(walkEqual, walkForward)
}

Executing the walk is exactly as it would be before, but now notice that walkEqual can return either walkForward or walkBackward. You can run this example on the Go Playground to see how it works as a full program.

This particular example is deliberately contrived, so as to keep the focus on the mechanics of recursive functions. In the standard library, this technique is used to make a lexer in the text/template package (view in stdlib source here). A full run-down on how that works is covered in Rob Pike’s talk Lexical Scanning in Go.

Function Types as Interface Values

Function types in Go can also have methods. It’s hard to see, at first, why this would be useful. There are two side effects to the fact that function types can have methods in Go: firstly, since any type that can have methods can satisfy an interface, function types in Go can also be boxed into valid interface types. Secondly, since methods in Go can have either pointer or value receivers, we can use methods on function pointers to switch the function being pointed to in the method’s calling context. That’s a bit heady at first, so let’s go through those two things in order.

First, the most obvious way to make a function type also satisfy an interface is to pick an interface with only one method. Keeping it simple, let’s define an add function like we did above, but we’ll give it an Error() string method. In Go, any type that has an Error() string method is a valid error type, so our function can serve as both a function and an error.

type binFunc func(int, int) int

func add(x, y int) int {
	return x + y
}

func (f binFunc) Error() string {
	return "binFunc error"
}

func main() {
	var err error
	err = binFunc(add)
	fmt.Println(err)
}

You can run this totally useless example on the Go Playground.

It’s worth noting that in this case, we had to perform a type conversion to convert the function add, which is of type func(int, int) int to type binFunc. This may seem like an onerous misfeature of the Go runtime, but what if there was another valid conversion for func(int, int) int that would implement error:

type loudBinFunc func(int, int) int

func (f loudBinFunc) Error() string {
	return "THIS ERROR IS A LOT LOUDER"
}

If both binFunc and loudBinFunc are defined, the runtime wouldn’t know how to convert our add function. Even if there is only one valid implementation of an interface for a given type, the runtime will not perform the conversion for us automatically. It seems very annoying at first, but it makes it easier to write bug-free code.

In the standard library, we can see an example of using a function type to satisfy an interface in the net/http package. The http.Handler type is the interface used by the standard library to register http handlers:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

There’s also an http.HandlerFunc type, which is just a wrapper around func(http.ResponseWriter, *http.Request) that satisfies http.Handler. This allows us to use either a function or a struct as a web handler. The end result is that we get both the http.Handle and http.HandleFunc functions, which makes it a bit more convenient to write web handlers.

Now that we’ve established how methods on functions allow us to satisfy an interface, I’ll show a slightly more esoteric usage that turns out to be remarkably useful: let’s stick some functions in a json document.

The encoding/json package in the standard library is the normal way to encode and decode json documents. This package is interface-based; it defines an interface, json.Unmarshaler, that is to be used in the unmarshaling of json documents:

type Unmarshaler interface {
	UnmarshalJSON([]byte) error
}

That is, if you define this method, the encoding/json package will use this method when unpacking json data; otherwise, it attempts to figure out what to do on its own (and generally gets it correct). Let’s stick with the binFunc example, but this time, we’ll stick some binary functions in a json document. We’ll start with the tired old binFunc definition:

type binFunc func(int, int) int

Since functions themselves do not have any persistent data, we need somewhere to register names. We can use a map[string]binFunc to store our functions with their associated names:

var fnRegistry = map[string]binFunc{
	"add": func(x, y int) int { return x + y },
	"sub": func(x, y int) int { return x - y },
	"mul": func(x, y int) int { return x * y },
	"div": func(x, y int) int { return x / y },
	"mod": func(x, y int) int { return x % y },
}

In Go, if we attempt to retrieve a value from a map with a nonexistent key, the retrieval operation will return the zero value for the given type (if you’re asking “but what if we legitimately put nil into that map?” then see here). For function types, the zero value is always nil, so we can just test against nil to see if we’ve retrieved a value. Now we need to actually implement the UnmarshalJSON method on our binFunc type. Since we want to make changes that are seen by the caller of the function, we want to implement UnmarshalJSON with a pointer receiver, and since we know we want a string, we just piggy-back of the existing string unmarshaling capabilities of the standard json package.

func (fn *binFunc) UnmarshalJSON(b []byte) error {
	// start by unmarshaling the name of the function that we want
	var name string
	if err := json.Unmarshal(b, &name); err != nil {
		return err
	}

	// get the function out of our function registry
	found := getBinFunc(name)
	if found == nil {
		// return a descriptive error if we can't find the function
		return fmt.Errorf("unknown function in (*binFunc)UnmarshalJSON: %s", name)
	}

	// dereference the pointer receiver, so that the changes are visible to the caller
	*fn = found
	return nil
}

With that defined, we can now unmarshal a binFunc from a json document. See here for the full, runnable example that unpacks just one function, and here’s an example of using this to unpack a list of functions. Of course, this is just the tip of the iceberg, but it shows what types of things can be done with this technique.

Channels of Functions

There’s one last pattern that is very unique to Go, and that’s the result of what happens when you combine function types with channels. A full description of Go’s concurrency model is out of scope for this post, but for the unfamiliar looking to get their toes wet with Go’s concurrency model, I recommend reading the concurrency section in Effective Go, or if you have the time for it, watch the very excellent Google I/O talk Go Concurrency Patterns. From this point forward, some knowledge of Go is assumed.

Since channels are primitives in Go, and we can make channels of any other type, we can even make channels of functions. Since functions can be anonymous, and functions are closures, we can combine these three properties to make channels of anonymous function closures. The definition for this channel type is obvious once you realize that it is possible: chan func(). Keeping in the theme of this post, let’s start by making a slice of functions, but we’ll make each one a closure:

x := 10
fns := []func(){
	func() { x += 1 },
	func() { x -= 1 },
	func() { x *= 2 },
	func() { x /= 2 },
	func() { x *= x },
}

We’ll also nab another piece of code we looked at earlier, and device a mechanism for picking one of those functions at random:

func pickFunc(fns ...func()) func() {
	return fns[rand.Intn(len(fns))]
}

Next, we make a channel of func() values:

c := make(chan func())

And we define a function that takes as its parameters a function channel, a number describing how many functions it should write into this channel, and then a set of candidate functions:

func produce(c chan func(), n int, fns ...func()) {
	defer close(c)
	for i := 0; i < n; i++ {
		c <- pickFunc(fns...)
	}
}

At the start of this function, we use defer close(c) to ensure that our channel gets closed when we’re done producing values. From there, we just use a regular for loop, pick a function at random using pickFunc, and then write it into the channel that we’ve been given. On the receiving end, all we need to do is read values from the channel using a range clause, then immediately execute these functions upon being read. By adding a sleep after each read operation, we’re able to implement a primitive rate-limiting device for arbitrary closures.

for fn := range c {
	fn()
	fmt.Println(x)
	time.Sleep(delay)
}

Putting this all together, we arrive at the following program:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

var delay = 200 * time.Millisecond

func pickFunc(fns ...func()) func() {
	return fns[rand.Intn(len(fns))]
}

func produce(c chan func(), n int, fns ...func()) {
	defer close(c)
	for i := 0; i < n; i++ {
		c <- pickFunc(fns...)
	}
}

func main() {
	// time is frozen on Playground, so this is always the same.
	rand.Seed(time.Now().Unix())

	x := 10
	fns := []func(){
		func() { x += 1 },
		func() { x -= 1 },
		func() { x *= 2 },
		func() { x /= 2 },
		func() { x *= x },
	}

	c := make(chan func())
	go produce(c, 10, fns...)

	for fn := range c {
		fn()
		fmt.Println(x)
		time.Sleep(delay)
	}
}

Run this example on the Go playground.

Conclusion

Functions are great!