After reading lesson 14, you’ll be able to
In Go you can assign functions to variables, pass functions to functions, and even write functions that return functions. Functions are first-class—they work in all the places that integers, strings, and other types work.
This lesson explores some potential uses of first-class functions as part of a theoretical Rover Environmental Monitoring Station (REMS) program that reads from (fake) temperature sensors.
A recipe for tacos calls for salsa. You can either turn to page 93 of the cookbook to make homemade salsa or open a jar of salsa from the store.
First-class functions are like tacos that call for salsa. As code, the makeTacos function needs to call a function for the salsa, whether that be makeSalsa or openSalsa. The salsa functions could be used independently as well, but the tacos won’t be complete without salsa.
Other than recipes and temperature sensors, what’s another example of a function that can be customized with a function?
The weather station sensors provide an air temperature reading from 150–300° K. You have functions to convert Kelvin to other temperature units once you have the data, but unless you have a sensor attached to your computer (or Raspberry Pi), retrieving the data is a bit problematic.
For now you can use a fake sensor that returns a pseudorandom number, but then you need a way to use realSensor or fakeSensor interchangeably. The following listing does just that. By designing the program this way, different real sensors could also be plugged in, for example, to monitor both ground and air temperature.
package main import ( "fmt" "math/rand" ) type kelvin float64 func fakeSensor() kelvin { return kelvin(rand.Intn(151) + 150) } func realSensor() kelvin { return 0 1 } func main() { sensor := fakeSensor 2 fmt.Println(sensor()) sensor = realSensor fmt.Println(sensor()) }
In the previous listing, the sensor variable is assigned to the fakeSensor function itself, not the result of calling the function. Function and method calls always have parentheses, such as fakeSensor(), which isn’t the case here.
Now calling sensor() will effectively call either realSensor or fakeSensor, depending on which function sensor is assigned to.
The sensor variable is of type function, where the function accepts no parameters and returns a kelvin result. When not relying on type inference, the sensor variable would be declared like this:
var sensor func() kelvin
You can reassign sensor to realSensor in listing 14.1 because it matches the function signature of fakeSensor. Both functions have the same number and type of parameters and return values.
How can you distinguish between assigning a function to a variable versus assigning the result of calling the function?
If there existed a groundSensor function that returned a celsius temperature, could it be assigned to the sensor in listing 14.1?
Function and method calls always have parentheses (for example, fn()) whereas the function itself can be assigned by specifying a function name without parentheses.
No. The parameters and return values must be of the same type to reassign the sensor variable. The Go compiler will report an error: cannot use groundSensor in assignment.
Variables can refer to functions, and variables can be passed to functions, which means Go allows you to pass functions to other functions.
To log temperature data every second, listing 14.2 declares a new measureTemperature function that accepts a sensor function as a parameter. It calls the sensor function periodically, whether it’s a fakeSensor or a realSensor.
The ability to pass functions around gives you a powerful way to split up your code. If not for first-class functions, you would likely end up with measureRealTemperature and measureFakeTemperature functions containing nearly identical code.
package main import ( "fmt" "math/rand" "time" ) type kelvin float64 func measureTemperature(samples int, sensor func() kelvin) { 1 for i := 0; i < samples; i++ { k := sensor() fmt.Printf("%v° K ", k) time.Sleep(time.Second) } } func fakeSensor() kelvin { return kelvin(rand.Intn(151) + 150) } func main() { measureTemperature(3, fakeSensor) 2 }
The measureTemperature function accepts two parameters, with the second parameter being of type func() kelvin. This declaration looks like a variable declaration of the same type:
var sensor func() kelvin
The main function is able to pass the name of a function to measureTemperature.
How is the ability to pass functions to other functions beneficial?
It’s possible to declare a new type for a function to condense and clarify the code that refers to it. You used the kelvin type to convey a unit of temperature rather than the underlying representation. The same can be done for functions that are being passed around:
type sensor func() kelvin
Rather than a function that accepts no parameters and returns a kelvin value, the code is about sensor functions. This type can be used to condense other code, so the declaration
func measureTemperature(samples int, s func() kelvin)
can now be written like this:
func measureTemperature(samples int, s sensor)
In this example, it may not seem like an improvement, as you now need to know what sensor is when looking at this line of code. But if sensor were used in several places, or if the function type had multiple parameters, using a type would significantly reduce the clutter.
Rewrite the following function signature to use a function type:
func drawTable(rows int, getRow func(row int) (string, string))
type getRowFn func(row int) (string, string) func drawTable(rows int, getRow getRowFn)
An anonymous function, also called a function literal in Go, is a function without a name. Unlike regular functions, function literals are closures because they keep references to variables in the surrounding scope.
You can assign an anonymous function to a variable and then use that variable like any other function, as shown in the following listing.
package main import "fmt" var f = func() { 1 fmt.Println("Dress up for the masquerade.") } func main() { f() 2 }
The variable you declare can be in the scope of the package or within a function, as shown in the next listing.
package main import "fmt" func main() { f := func(message string) { 1 fmt.Println(message) } f("Go to the party.") 2 }
You can even declare and invoke an anonymous function in one step, as shown in the following listing.
package main import "fmt" func main() { func() { 1 fmt.Println("Functions anonymous") }() 2 }
Anonymous functions can come in handy whenever you need to create a function on the fly. One such circumstance is when returning a function from another function. Although it’s possible for a function to return an existing named function, declaring and returning a new anonymous function is much more useful.
In listing 14.6 the calibrate function adjusts for errors in air temperature readings. Using first-class functions, calibrate accepts a fake or real sensor as a parameter and returns a replacement function. Whenever the new sensor function is called, the original function is invoked, and the reading is adjusted by an offset.
package main import "fmt" type kelvin float64 // sensor function type type sensor func() kelvin func realSensor() kelvin { return 0 1 } func calibrate(s sensor, offset kelvin) sensor { return func() kelvin { 2 return s() + offset } } func main() { sensor := calibrate(realSensor, 5) fmt.Println(sensor()) 3 }
The anonymous function in the preceding listing makes use of closures. It references the s and offset variables that the calibrate function accepts as parameters. Even after the calibrate function returns, the variables captured by the closure survive, so calls to sensor still have access to those variables. The anonymous function encloses the variables in scope, which explains the term closure.
Because a closure keeps a reference to surrounding variables rather than a copy of their values, changes to those variables are reflected in calls to the anonymous function:
var k kelvin = 294.0 sensor := func() kelvin { return k } fmt.Println(sensor()) 1 k++ fmt.Println(sensor()) 2
Keep this in mind, particularly when using closures inside for loops.
What’s another name for an anonymous function in Go?
What do closures provide that regular functions don’t?
An anonymous function is also called a function literal in Go.
Closures keep references to variables in the surrounding scope.
Let’s see if you got this...
Type listing 14.6 into the Go Playground to see it in action: