15. Functions as values
In Go, a function is a value like any other. You can put one in a variable, pass it as an argument, return it from another function, and store it in a slice or a map. This is the foundation of a lot of idiomatic Go — sorting, HTTP handlers, middleware, callbacks.
A function in a variable
package main
import "fmt"
func double(n int) int {
return n * 2
}
func main() {
f := double
fmt.Println(f(21))
var g func(int) int = double
fmt.Println(g(5))
}
Note double without parentheses — that's the function value itself.
With parentheses you'd be calling it.
The type of that value is func(int) int: the word func, the parameter
types, the result types. No names, just the shape. Any function with the same
shape can be assigned to a variable of that type.
Anonymous functions
You can write a function without naming it, right where you need it:
package main
import "fmt"
func main() {
square := func(n int) int {
return n * n
}
fmt.Println(square(7))
func(msg string) {
fmt.Println("ran immediately:", msg)
}("hello")
}
The second one is declared and called on the spot — note the ("hello")
hanging off the closing brace. You'll see that shape most often with
goroutines (go func() { ... }()) in the Concurrency module.
Passing a function as an argument
This is where it gets useful. A function that takes a function can leave a decision up to its caller:
package main
import "fmt"
func applyAll(nums []int, f func(int) int) []int {
out := make([]int, 0, len(nums))
for _, n := range nums {
out = append(out, f(n))
}
return out
}
func main() {
nums := []int{1, 2, 3, 4}
fmt.Println(applyAll(nums, func(n int) int { return n * n }))
fmt.Println(applyAll(nums, func(n int) int { return -n }))
}
applyAll knows how to walk a slice and collect results. It knows nothing
about squaring or negating — that's the caller's business. One function, any
transformation.
The same idea with a filter, where the passed function returns a bool
(commonly called a predicate):
package main
import "fmt"
func filter(nums []int, keep func(int) bool) []int {
var out []int
for _, n := range nums {
if keep(n) {
out = append(out, n)
}
}
return out
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
evens := filter(nums, func(n int) bool { return n%2 == 0 })
big := filter(nums, func(n int) bool { return n > 5 })
fmt.Println(evens)
fmt.Println(big)
}
Naming a function type
When a signature shows up repeatedly, give it a name with type:
package main
import "fmt"
type Validator func(string) error
func check(name string, v Validator) {
if err := v(name); err != nil {
fmt.Println(name, "->", err)
return
}
fmt.Println(name, "-> ok")
}
func main() {
notEmpty := func(s string) error {
if s == "" {
return fmt.Errorf("must not be empty")
}
return nil
}
check("brevfeed", notEmpty)
check("", notEmpty)
}
Validator is now a real type. It reads better in signatures, and it gives
you a place to hang documentation. The standard library does exactly this —
http.HandlerFunc is a named function type.
A map of functions
Because functions are values, a map can hold them — a compact dispatch table:
package main
import "fmt"
func main() {
ops := map[string]func(int, int) int{
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"mul": func(a, b int) int { return a * b },
}
for _, name := range []string{"add", "sub", "mul"} {
fmt.Printf("%s(6, 3) = %d\n", name, ops[name](6, 3))
}
}
We loop over an explicit list of names rather than ranging the map directly, because map iteration order in Go is deliberately random. More on that in the Collections module.
Sorting with a function
The everyday payoff. sort.Slice takes your slice and a function that says
whether element i should sort before element j:
package main
import (
"fmt"
"sort"
)
func main() {
words := []string{"banana", "fig", "apple", "cherry"}
sort.Slice(words, func(i, j int) bool { return words[i] < words[j] })
fmt.Println("alphabetical:", words)
sort.Slice(words, func(i, j int) bool { return len(words[i]) < len(words[j]) })
fmt.Println("by length: ", words)
}
One sort implementation, any ordering you can express — because the ordering is a value you hand in.
Your turn
Complete countIf, which takes a slice of ints and a predicate, and returns
how many elements satisfy it. Call it with a predicate matching numbers
greater than 10 so the program prints exactly:
3
package main
import "fmt"
func countIf(nums []int, match func(int) bool) int {
// count the elements where match(n) is true
}
func main() {
nums := []int{4, 12, 9, 30, 11, 2}
fmt.Println(countIf(nums, func(n int) bool { return n > 10 }))
}
package main
import "fmt"
func countIf(nums []int, match func(int) bool) int {
count := 0
for _, n := range nums {
if match(n) {
count++
}
}
return count
}
func main() {
nums := []int{4, 12, 9, 30, 11, 2}
fmt.Println(countIf(nums, func(n int) bool { return n > 10 }))
}
Next: what happens when one of these anonymous functions reaches out and grabs a variable from around it — closures.