You Can’t Spell Function Without ‘Fun’
A Function⌗
A function is a machine that takes inputs and generates outputs or effects. An example would be a function to return the circumference of a circle, given a radius. Or maybe encapsulating saving information to a file. The former is always testable and is “always” true in some sense. The latter is sometimes true and is testable, given a specific world state. I’m not going to discuss the merits of functional programming. This is just an overview of Go functions.
The Good Old Function⌗
The basic definition of a function is… well… basic.
It has a name, maybe some parameters, and a return type.
Methods are functions with a receiver.
The common name in other languages like C++ is a “member function”.
In the code below FmtHelloWorld
is a function of type func(string) string
.
The Pet
type has a method attached to it.
package main
import "fmt"
func FmtHelloWorld(name string) string {
return fmt.Sprintf("Hello %s", name)
}
type Pet struct {
Name, Species string
}
func (p Pet) String() string {
return fmt.Sprintf("A %s named %d", p.Species, p.Name)
}
func main() {
fmt.Println(FmtHelloWorld("Jane User"))
p := Pet{Name: "Spot", Species: "Dog"}
fmt.Println(p.String())
}
A function can have named returned values.
Named return values are variables to which we can assign what will be returned by the function.
We don’t have to provide the values to the return statement (e.g. return min, max
).
For example, I could have a function that orders two numbers.
The benefit is that I can cue the user of the function to what I intended.
Rather than just returning two integers, in the example below, I clearly indicate which is which.
func swap(x, y int) (min, max int) {
if x < y {
min, max = x, y
return
}
min, max = y, x
return
}
Variadic Functions⌗
Functions can take an arbitrary number of parameters.
For example, say I wanted to pass a list of numbers, but I don’t want to create a slice.
In the example below, the values
parameter is an arbitrarily long list of numbers.
In the example, I also show how a slice of numbers can be ‘applied’ to the function.
The ...
operator takes each member of the slice values
and passes it as a function argument.
The variadic parameter must be the last parameter for the function.
package main
import "fmt"
func IsMoreThan(amount int, values ...int) bool {
sum := 0
for _, v := range values {
sum += v
}
return amount > sum
}
func main() {
if !IsMoreThan(7, 1, 2, 3, 4) {
fmt.Println("7 is not more than 1+2+3+4")
}
values := []int{1, 2, 3}
if IsMoreThan(7, values...) {
fmt.Printf("7 is more than %v\n", values)
}
}
Pre-Generics⌗
Until generics become part of the language we have two choices for passing unknown types to the same function.
(I’m writing this when 17.2 is the ‘current’ version).
The first option is to implement a implement a type and an interface.
For example, the built-in sort package requires you to implement sort.Interface
.
The example below makes both integers and strings acceptable to the PrintHelloWorld
function.
The PrintHelloWorld function doesn’t care about the underlying type as long as it implements the Helloer
interface.
package main
import "fmt"
type Helloer interface {
SayHello() string
}
type myInt int
type myString string
func (m myInt) SayHello() string {
return fmt.Sprintf("Hello %d", m)
}
func (m myString) SayHello() string {
return fmt.Sprintf("Hello %s", m)
}
func PrintHelloWorld(h Helloer) {
fmt.Printf("%s world\n", h.SayHello())
}
func main() {
i := myInt(1)
s := myString("Friendly")
PrintHelloWorld(i)
PrintHelloWorld(s)
}
The other option is to pass values as the interface{}
type.
This is like the void*
in C.
It makes no guarantees as to the underlying type.
To make it useful, you need to cast the interface{}
value to the correct type.
Go has the ability to cast safely when you use the two value form of casting.
You can also use a switch statement on the type to which to cast (a type-switch).
package main
import "fmt"
func PrintHelloWorld(v interface{}) {
switch v.(type) {
case int:
fmt.Printf("Hello %d world\n", v.(int))
case string:
fmt.Printf("Hello %s world\n", v.(string))
default:
fmt.Printf("Some other type\n")
}
if i, ok := v.(int); ok {
fmt.Printf("Integer: %d\n", i)
}
if f, ok := v.(float64); ok {
fmt.Printf("Float: %f\n", f)
}
}
func main() {
PrintHelloWorld(1)
PrintHelloWorld(3.4)
PrintHelloWorld("Big")
}
There may be good reasons to use casting even after generics are introduced. That said, my expectation is generics will casting or type-switch statements less necessary. There will also be instances where confirming to an interface is more appropriate than a generic function. Rob Pike has a good reason not to change all the APIs in the first iteration of generics. For those of us who have seen the introduction of generics in other languages (cough, cough Java), some caution is a Good Thing(tm). For the first few months everyone will want to use the shiny new feature for everything, even if it doesn’t really make sense.
Anonymous Functions⌗
Anonymous functions are very useful in Go.
Sometimes they are single-use, “throw away” functions..
For example, let’s say we have a function Filter
that filters a slice.
It takes a predicate function as a parameter and returns the slice members that satisfy that predicate.
package main
import "fmt"
func FilterNumbers(numbers []int, predicate func(int) bool) []int {
result := make([]int, 0, len(numbers))
for _, val := range numbers {
if predicate(val) {
result = append(result, val)
}
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 6, 7, 8, 9, 0}
filtered := FilterNumbers(numbers, func(x int) bool { return x%2 == 0 })
fmt.Printf("%v\n", filtered)
}
The predicate may only be used in the call to FilterNumbers
.
It won’t be used outside that code.
Rather than create a standalone function, the anonymous function is used.
However, only do this for short, obviously correct functions.
Anything longer than a simple one-liner should be tested and therefore should be defined normally.
The second common use is to clean up resources. For example, the code below prints reads and prints out 12 bytes from a file. The resource allocation occurs in the anonymous function. To ensure the file is closed, we immediately defer a call to close the file. The anonymous function is called immediately. After the anonymous function completes, we no longer have to worry about the open file.
package main
import (
"fmt"
"os"
)
func main() {
data := make([]byte, 12)
func () {
f, _ := os.Open("examples.go")
defer f.Close()
f.Read(data)
}()
fmt.Println(string(data))
}
Functions Defining Functions and Currying⌗
One use for a function is to generate another function. I’ll start with the most common use case (IMHO) for this feature. Let’s say we want each request to log the request start and end time. We could make that part of each request but we might forget to add it to some handler defined in some other file. Instead, we create a function that wraps the handler and performs the logging. It returns a function that we register with the http handler.
package main
import (
"log"
"net/http"
"time"
)
func IndexHandler(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("hello world"))
}
func AboutHandler(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("about"))
}
func MakeLoggingHandler(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Starting handler: %v", time.Now())
handler(w, r)
log.Printf("Done handler: %v", time.Now())
}
}
func main() {
http.HandleFunc("/", MakeLoggingHandler(IndexHandler))
http.HandleFunc("/about", MakeLoggingHandler(AboutHandler))
http.ListenAndServe(":9999", nil)
}
The next most likely use is to take a function and curry it.
Let’s go back to our filter example.
There might be a convenience function IsGreaterThan
that takes a value and a test number.
It returns true if the value is greater than the test number.
We can’t directly use this function with the FilterNumbers
function.
Instead, we generate a new function with the test value fixed to the number we need.
The resulting function can be used with the FilterNumbers
function.
package main
import "fmt"
func FilterNumbers(numbers []int, predicate func(int) bool) []int {
result := make([]int, 0, len(numbers))
for _, val := range numbers {
if predicate(val) {
result = append(result, val)
}
}
return result
}
func IsGreaterThan(val, test int) bool {
return val > test
}
func main() {
numbers := []int{1, 2, 3, 4, 6, 7, 8, 9, 0}
curried := func(x int) bool { return IsGreaterThan(x, 4) }
filtered := FilterNumbers(numbers, curried)
fmt.Printf("%v\n", filtered)
}
Closures⌗
Generating a function is useful. When that function is able to refer back to variables defined in the scope it was created, it is a closure. Garbage collection makes closures elegant and possible. The generated function can refer to that variable for the life of the generated function. If you’re familiar with languages like JavaScript and Lisp, closures are nothing new.
package main
import (
"fmt"
"strings"
)
func HelloWorld(name string) func() string {
upcase := strings.ToUpper(name)
return func() string {
return fmt.Sprintf("Hello %s! Why are you shouting?\n", upcase)
}
}
func main() {
hw := HelloWorld("bob")
fmt.Println(hw())
}
In this case the variable upcase
is still available to the returned function.
The returned function is executed outside the scope of HelloWorld
.
The garbage collector knows not to de-allocate the memory for upcase
.
That memory is still referenced by the function returned from HelloWorld
.
Deferrable and Go-able⌗
Functions can be scheduled to run outside the normal flow of control.
The most convenient form is to defer the execution of a function after a given function has completed.
The most common reason to do this is to clean up resources.
For example, calling defer someFile.Close()
to close a file after the file is opened.
It is also used to recover from panics, which is also relatively common.
package main
import (
"fmt"
)
func SaySomething(s string) {
fmt.Printf("%s\n", s)
}
func HelloWorld() {
defer SaySomething("one")
defer SaySomething("two")
fmt.Println("three")
}
func main() {
HelloWorld()
}
In the code above, the first line to print is three
.
The function finishes execution and the deferred functions run.
Then the defer statements are executed in the reverse order they are defined.
The final output is “three”, then “two”, and then “one”.
The second way to schedule function execution is through a goroutine.
Using the keyword go
, the function execution is no longer determinant.
It could be executed concurrently, but maybe not.
package main
import (
"fmt"
"sync"
)
func HelloWorld(wg *sync.WaitGroup) {
fmt.Println("I don't know when this will run")
wg.Done()
}
func main() {
wg := &sync.WaitGroup{}
wg.Add(1)
go HelloWorld(wg)
fmt.Println("Maybe this runs first, maybe not")
wg.Wait()
}
Some mechanism, like a Wait Group, prevents the main function from exiting before the go routine runs.
Another option is to use channels and wait for a message from the executing go routine.
In either case, control could pass immediately from the main function or during the wg.Wait()
.
The Go runtime makes those decisions.
Functions are Types⌗
I promise this is the last section.
We can declare a type using a function signature.
For example, the http
package defines the HandlerFunc
type as a method that takes a ResponseWriter
and a Request
.
Any type that takes those two parameters can be cast to a HandlerFunc
.
What’s more interesting, is that we can define methods on a function type.
package main
import (
"fmt"
"strings"
)
type UpCaser interface {
UpCase() string
}
type Generator func () string
func ScaryGenerator() string {
return "boo"
}
func MeanGenerator() string {
return "hiss"
}
func (g Generator) UpCase() string {
return strings.ToUpper(g())
}
func main() {
t := Generator(ScaryGenerator).UpCase()
fmt.Println(t)
t = Generator(MeanGenerator).UpCase()
fmt.Println(t)
}
In the example above we define an interface called UpCaser
that requires a function called UpCase
.
We also define a type called Generator
for zero argument functions that return a string.
Both the defined functions ScaryGenerator
and MeanGenerator
are zero argument functions returning a string.
That means they can both be cast to the Generator
type.
Because the Generator
type implements UpCase
, we can call UpCase
on the methods.
Note that when we do it, we still call the underlying method.
When I first saw this, it was a little mind bending. You might be able to do something similar in JavaScript but not exactly this. Not that I have infinite experience, but I don’t recall seeing this in other languages I’ve used. The question becomes, when would I use this? The answer is any time you pass a function has an HTTP handler function.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
I tried to come up with a great example of how this might come in handily, but it was a little contrived.
The example above is from the Go net/http
package.
It is incredibly elegant.
Either you can implement a struct with a ServeHTTP
method or you can write a handler function.
Because the handler function type also implements the ServeHTTP
call, they are interchangeable.
This allows us to easily wrap handler functions in other functions.
This is what enabled us to write the handler example under Functions Defining Functions.
Conclusion⌗
Functions are cool. Use them.