title: Functional Programming: Anonymous Functions and Closures
date: 2022-07-03 21:38:00
toc: false
index_img: http://api.btstu.cn/sjbz/?lx=m_dongman&cid=8
category:
- Go
tags: - Java
- Creation
- Support
- Environment
- Request
- Strings
- Different
- Occurrence
- add
- Leave
- JavaScript
- Method
- Function
Definition and Usage of Anonymous Functions#
An anonymous function is a function declaration without specifying a function name (in contrast, a named function is called a named function). Many programming languages, such as PHP and JavaScript (think about the implementation of Ajax requests), support and implement anonymous functions. Go language also provides support for anonymous functions, and the syntax is similar to other languages:
func(a, b int) int {
return a * b
}
Like other languages, anonymous functions in Go can be assigned to a variable or executed directly:
// 1. Assigning the anonymous function to a variable
mul := func(a, b int) int {
return a * b
}
// Calling the anonymous function mul
fmt.Println(mul(1, 2))
// 2. Calling the anonymous function directly during definition
func(a, b int) {
fmt.Println(a * b)
} (1, 2)
Anonymous Functions and Closures#
To answer this question, we need to understand the concept of closures.
A closure refers to a function that references free variables (variables not bound to a specific object, usually defined outside the function). The referenced free variables will exist together with this function, even if they have left the context in which they were created (such as being passed to other functions or objects). In simple terms, "closure" means "enclosing external state", which means that even if the external state becomes invalid, the closure still retains a copy of the variable referenced from the outside.
Obviously, closures can only be implemented through anonymous functions. We can consider closures as stateful anonymous functions. Conversely, if an anonymous function references an external variable, it forms a closure.
The value of closures lies in their ability to act as function objects or anonymous functions that hold external variables. For the type system, this means representing both data and code. Languages that support closures treat functions as first-class objects (also known as first-class citizens, first-class entities, etc.), and Go language is no exception. This means that Go functions and ordinary Go data types (integers, strings, arrays, slices, maps, structures, etc.) have equal status. They can be assigned to variables, passed as arguments to other functions, and can also be dynamically created and returned by functions.
Note: The so-called first-class objects refer to entities that can be created at runtime and passed as arguments to other functions or assigned to variables. In most languages, numbers and basic types are first-class objects. In programming languages that support closures (such as Go, PHP, JavaScript, Python, etc.), functions are also first-class objects. In languages like C and C++ that do not support anonymous functions, functions cannot be created at runtime, so functions are not first-class objects.
In simple terms: closures and anonymous functions appear together, and if they reference external variables, they are closures! The following code can help deepen the understanding!
package main
import "fmt"
var iotaVal int
func iotaFunc() int {
val := 1 << (10 * iotaVal)
iotaVal++
return val
}
func main() {
fmt.Printf("%d: KB -> %-14d byte \n", iotaVal, iotaFunc())
fmt.Printf("%d: MB -> %-14d byte \n", iotaVal, iotaFunc())
fmt.Printf("%d: GB -> %-14d byte \n", iotaVal, iotaFunc())
fmt.Printf("%d: TB -> %-14d byte \n", iotaVal, iotaFunc())
fmt.Printf("%d: PB -> %-14d byte \n", iotaVal, iotaFunc())
}
Output:
1: KB -> 1 byte
2: MB -> 1024 byte
3: GB -> 1048576 byte
4: TB -> 1073741824 byte
5: PB -> 1099511627776 byte
Common Use Cases of Anonymous Functions#
Now let's look at some typical use cases of anonymous functions in Go.
Ensuring the Safety of Local Variables#
Local variables declared within an anonymous function cannot be modified from the outside, ensuring safety (similar to private attributes of a class):
var j int = 1
f := func() {
var i int = 1
fmt.Printf("i, j: %d, %d\n", i, j)
}
f()
j += 2
f()
The above code will output:
i, j: 1, 1
i, j: 1, 3
In the above example, the anonymous function references an external variable, so it is also a closure. The variable f
points to the closure that references local variables i
and j
. i
is defined within the closure and its value is isolated and cannot be modified from the outside. On the other hand, j
is defined outside the closure, so it can be modified from the outside. The closure only holds a reference to it.
Using Anonymous Functions as Function Parameters#
In addition to assigning anonymous functions to regular variables, they can also be passed as parameters to functions and called, just like ordinary data types:
add := func(a, b int) int {
return a + b
}
// Passing a function type as a parameter
func(call func(int, int) int) {
fmt.Println(call(1, 2))
}(add)
When declaring a function type, we need to strictly specify the type of each parameter and the return value. This is a complete function type. Therefore, the function add
corresponds to the function type func(int, int) int
.
We can also extract the second anonymous function outside the main
function and make it a named function handleAdd
. Then, we can define different addition algorithm implementation functions and pass them as parameters to handleAdd
:
func main() {
// Regular addition operation
add1 := func(a, b int) int {
return a + b
}
// Define multiple addition algorithm implementations
base := 10
add2 := func(a, b int) int {
return a*base + b
}
handleAdd(1, 2, add1) // 3
handleAdd(1, 2, add2) // 1*10 + 2 = 12
}
// Passing an anonymous function as a parameter
func handleAdd(a, b int, call func(int, int) int) {
fmt.Println(call(a, b))
}
The output of the above code is:
3
12
In this example, the second anonymous function add2
references an external variable base
, forming a closure. When calling the outer function handleAdd
, the closure add2
is passed as a parameter. When the closure add2
is executed in the outer function, although the scope has left the main
function, it can still access the variable base
.
In this way, we can implement multiple different addition algorithm implementations through one function, improving code reusability. Based on this feature, we can implement more complex business logic. For example, the underlying route handler in Go's official net/http
package is implemented in this way:
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
Returning Anonymous Functions from Functions#
Finally, anonymous functions can also be returned from functions:
// Returning a function as the return value type
func deferAdd(a, b int) func() int {
return func() int {
return a + b
}
}
func main() {
// The return value is an anonymous function
addFunc := deferAdd(1, 2)
// The addition operation is performed here
fmt.Println(addFunc())
}
The output of the above code is:
3
In the above example, calling the deferAdd
function returns an anonymous function. However, this anonymous function references the parameters passed into the outer function, forming a closure. As long as this closure exists, the variables it holds will also exist, even if they are outside the scope of the deferAdd
function, they can still be accessed.
In addition, the closure is not executed when calling the deferAdd
function. The closure is only executed when addFunc()
is called, and the business logic inside the closure (in this case, addition operation) is executed. Therefore, by declaring the return value as a function type, we can achieve delayed execution of business logic, allowing developers to have complete control over the execution timing.
Try reading the code and obtaining the program's execution result:
// Returning a function as the return value type
func deferAdd(a, b int) func() int {
return func() int {
a++
return a + b
}
}
func main() {
// The return value is an anonymous function
addFunc := deferAdd(1, 2)
// The addition operation is performed here
fmt.Println(addFunc())
fmt.Println(addFunc())
}