Go by Example: Closures

In Go, a closure is a function value that “closes over” its surrounding scope. This means that a closure has access to the variables in its surrounding scope, even after the surrounding function has returned.

Here’s a simple example of a closure in Go:

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

Output:

0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

In this example, the adder function returns a closure. The closure is a function that adds an int value to its surrounding sum variable. When the adder function is called twice, it returns two separate closures with their own separate sum variables. This allows the two closures to maintain their own separate running totals, even though they are defined in the same scope.

Closures can be useful in a variety of situations where you need to maintain some state or context across multiple function calls. For example, you can use a closure to implement a simple counter:

package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	counterA := counter()
	counterB := counter()
	fmt.Println(counterA(), counterA(), counterA(), counterB(), counterB(), counterA(), counterB())
}

Output:

1 2 3 1 2 3 4

In this example, the counter function returns a closure that increments and returns a count variable. When the counter function is called twice, it returns two separate closures with their own separate count variables. This allows the two closures to maintain their own separate counts, even though they are defined in the same scope.

Closures are often used in Go to implement higher-order functions, which are functions that take one or more functions as arguments or return a function as a result. For example, you can use a closure to implement a simple function that returns a function that implements a simple filter:

package main

import "fmt"

func filter(f func(int) bool) func([]int) []int {
	return func(s []int) []int {
		var res []int
		for _, v := range s {
			if f(v) {
				res = append(res, v)
			}
		}
		return res
	}
}

func main() {
	even := filter(func(n int) bool {
		return n%2 == 0
	})
	odd := filter(func(n int) bool {
		return n%2 == 1
	})
	fmt.Println("even:", even([]int{1, 2, 3, 4, 5}))
	fmt.Println("odd:", odd([]int{1, 2, 3, 4, 5}))
}

Output:

even: [2 4]
odd: [1 3 5]

In this example, the filter function returns a closure that filters a []int slice based on a provided function f. The closure takes a []int slice as an argument and returns a new []int slice that contains only the elements that satisfy the f function. When the filter function is called twice with different f functions, it returns two separate closures that implement different filters. This allows the two closures to maintain their own separate filters, even though they are defined in the same scope.

Closures can also be used to implement simple state machines, like the following example:

package main

import "fmt"

func stateMachine() func() string {
	state := 0
	return func() string {
		state++
		switch state {
		case 1:
			return "first"
		case 2:
			return "second"
		default:
			return "unknown"
		}
	}
}

func main() {
	f := stateMachine()
	fmt.Println(f(), f(), f(), f(), f())
}

Output:

first second unknown unknown unknown

In this example, the stateMachine function returns a closure that implements a simple state machine. The closure increments a state variable and returns a string value based on the value of state. When the stateMachine function is called once, it returns a closure that implements the state machine. This allows the closure to maintain its own state, even though it is defined in the same scope.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *