DMarlon Studio

Lesson 7 · 7 of 20

Closures and function values · Go

Use functions as values in Go: function types, functions that return closures, and closures with their own state. Price rules and invoices.

Go · Closures and function values in DMarlon Studio: the example's code and its output in the terminal

In Go, functions are values: you can store them in variables, pass them as arguments and return them. PriceRule is a function type: any func(float64) float64 fits, which lets you keep a list of rules, rules, and apply them one after another.

percentOff(10) returns an anonymous function that uses pct, a parameter of percentOff. That function is a closure: it remembers the variables from where it was created, even after percentOff has returned. percentOff(10) and percentOff(5) are two different rules, each with its own pct.

addVAT is a function literal stored in a variable. It does not need to be declared as a PriceRule: its type is func(float64) float64, so it goes straight into the list. The inner loop applies the three rules in order: 100 minus 10%, minus 5% and plus 21% VAT gives 103.45.

invoiceNumbers returns a closure that changes n. The variable is not lost when the function returns: it lives as long as the closure does, and every call to next() increments it (INV-001, INV-002…). It is a simple way to keep state without a struct.

Each call to invoiceNumbers creates a fresh n: other starts at REF-001 while next carries on with INV-004. The arguments to fmt.Println are evaluated left to right, which is why REF-001 and REF-002 come out in that order.

Code · main.go

package mainimport "fmt"// PriceRule is a function type: any func(float64) float64 fits.type PriceRule func(float64) float64func percentOff(pct float64) PriceRule {	return func(price float64) float64 { return price * (1 - pct/100) } // remembers pct}// invoiceNumbers returns a closure with its own counter, kept between calls.func invoiceNumbers(prefix string) func() string {	n := 0	return func() string {		n++		return fmt.Sprintf("%s-%03d", prefix, n)	}}func main() {	addVAT := func(price float64) float64 { return price * 1.21 } // a function value	rules := []PriceRule{percentOff(10), percentOff(5), addVAT}	next := invoiceNumbers("INV")	for _, price := range []float64{100, 40, 25} {		final := price		for _, apply := range rules {			final = apply(final)		}		fmt.Printf("%s  %6.2f -> %6.2f\n", next(), price, final)	}	other := invoiceNumbers("REF") // a new closure starts its own count	fmt.Println(other(), other(), "| main series goes on:", next())}

Output · go run main.go

INV-001  100.00 -> 103.45
INV-002   40.00 ->  41.38
INV-003   25.00 ->  25.86
REF-001 REF-002 | main series goes on: INV-004

Key points

  • Functions are values: store them, pass them, return them.
  • A function type, such as PriceRule, names a signature.
  • A closure remembers the variables from where it was created.
  • Captured variables survive as long as the closure does.
  • Each call to the outer function creates new state.

Exercise

Write minus(amount float64) PriceRule, which subtracts a fixed amount without going below zero, and add minus(5) to rules before the VAT. Check how each invoice total changes.