This is the seventh entry of my weekly series Learning Go. Last week I discussed Function Declarations, Arguments, Parameters, and Anonymous Functions. This week I will be talking about Function Literals and Closure.
Function Literals (Function Expressions)
Function Literals can be assigned to a variable or called (invoked) directly. They may refer to the variables defined in a surrounding function, making them a closure (we will talk more about this later in the post)
So, what is the difference between a Function Declaration and a Function Literal?
A Function Declaration binds an identifier (the function name) to a function. You can call this function by using its identifier.
A Function Literal is a closure, meaning they can reference variables that have been defined in a surrounding function. These variables can be shared between the function literal and the surrounding function. These variables persist as long as they are accessible.
Let’s start with a basic example and work our way up in complexity.
package main
import (
"fmt"
)
func main() {
f := func() {
fmt.Println("I am a function literal!")
}
f()
// I am a function literal!
}
- inside of
funcmainwe declare the variablefand assign to an anonymous function - when this function is invoked, it uses the
fmtpackage to print thestringI am a function literal! - we invoke this function literal the same way we invoke function declarations: the identifier followed by arguments wrapped in parentheses
() - this function literal expects no parameters; therefore, we do not pass any arguments
- once
fis invoked,I am a function literalis printed and the program exits
Let’s see an example when a function literal has a parameter:
package main
import (
"fmt"
)
func main() {
f := func(x int) {
fmt.Println("my birth year is ", x)
}
f(1990)
// my birth year is 1990
}
- inside of
funcmainwe declare the variablefand assign it to an anonymous function that takes one parameter,x, of typeint - using the
fmtpackage, we print thestringmy birth year isfollowed by the value ofx - notice when
fis invoked we pass a single argument1990 fprintsmy birth year is 1990and the programs exits
Next, let’s see how we can return a function from a Function Literal:
package main
import (
"fmt"
)
func main() {
f := bar()
fmt.Println(f())
// 2020
}
func bar() func() int {
return func() int {
return 2020
}
}
bar:
- below
funcmain, using thefunckeyword, we create a function declaration with an identifier ofbarwith two return types:func()andint - these return types tell us that
baris expected to return a function and anintinside of that function - inside the function body of
barwereturnan anonymous function that has a return type ofint - inside of this anonymous function, we return the value
2020of typeint
main:
- inside of
funcmainwe declare the variablefand assign it to return value of the function declarationbar - note:
fis assigned to the return value because we are invokingbar; therefore, whatbarreturns will be the value thatfholds in memory. In this case, that return value is a function fis invoked on the next line inside of thePrintlnfunction from thefmtpackagebar’s return value is a function that returns the value2020of typeint: therefore,f()will print2020
As you can see from a few of these examples - function literals can be very powerful and can be used very dynamically in your code. Remember a few things when you are thinking of using a function literal instead of a function declaration:
- they are anonymous functions
- variables are shared between a function literal and the surrounding function (closure)
- variables “survive” as long as they are still accessible
Closure
the way that an anonymous function references variables declared outside of the anonymous function itself
A bit of a brain bender, huh?
The concept of closure can seem very abstract, which makes understanding how they work and the problems they solve difficult as well.
I am confident that seeing closure in action is the best way to learn how they work:
package main
import (
"fmt"
)
func main() {
a := incrementor()
fmt.Println(a())
// 1
fmt.Println(a())
// 2
b := incrementor()
fmt.Println(b())
// 1
}
func incrementor() func() int {
var x int
return func() int {
x++
return x
}
}
incrementor:
- first we create
incrementor, this should look familiar tobarin the last section incrementoris a function declaration that returns a function and anintinside that function- using the
varkeyword we declare the variablexof typeint xis not assigned a value; therefore, it is given azero value(0)- next, we return an anonymous function that is expected to
returna value of typeint - notice, using the
++operator, we are incrementing the value ofxby1- how is this possible? the answer is closure - after we increment
x, we returnx
main:
- inside of
funcmainwe create the variableaand assign it to the return value ofincrementor() - on the next line,
ais invoked inside of thePrintlnfunction from thefmtpackage - because the return value of
ais the anonymous function insideincrementor(), we incrementxby1and return the value1; therefore,1is printed - we repeat this process by invoking
ainside of thePrintlnfunction again - since we have already invoked
athe value ofxis1; therefore, when we incrementxthe value returned and printed will be2
Notice when we assign incrementor() to the variable b it does not return 3, why is that?
Although a and b were assigned the same return value of incrementor, b has only been invoked once; therefore, it holds it’s own unique value of 1.
This is the power of closure, data isolation. Now, you can easily use common actions across multiple variables, and those variables can have their own, unique values.
In Summary
I hope you have enjoyed learning about Function Literals and Closure. With the power of closure, you are equipped with another powerful feature of the Go programming language that can make your code more modular, readable, and scalable. Next, I will discuss Recursion and how to apply those principles to your functions. Can’t wait, see you then!