Let's look at a few simple code examples to understand the difference between an anonymous function and a closure.
Here's a typical named function:
func namedGreeting(name string) { fmt.Printf("Hey %s!n", name) }
The following is an example of the anonymous function:
func anonymousGreeting() func(string) { return func(name string) { fmt.Printf("Hey %s!n", name) } }
Now, let's call them both and call an anonymous inline function to say Hey
to Cindy:
func main() { namedGreeting("Alice") greet := anonymousGreeting() greet("Bob") func(name string) { fmt.Printf("Hello %s!n", name) }("Cindy") }
The output will be as follows:
Hello Alice! Hello Bob! Hello Cindy!
Now, let's look at a closure named greeting
and see the difference between it and the anonymousGreeting()
function.
Since the closure function is declared in the same scope as the msg
variable, the closure has access to it. The msg
variable is said to be in the same environment as the closure; later, we'll see that a closure's environment variables and data can be passed around and referenced at a later time during a program's execution:
func greeting(name string) { msg := name + fmt.Sprintf(" (at %v)", time.Now().String()) closure := func() { fmt.Printf("Hey %s!n", msg) } closure() } func main() { greeting("alice") }
The output will be as follows:
Hey alice (at 2017-01-29 12:29:30.164830641 -0500 EST)!
In the next example, instead of executing the closure in the greeting()
function, we will return it and assign its return value to the hey
variable in the main
function:
func greeting(name string) func() { msg := name + fmt.Sprintf(" (at %v)", time.Now().String()) closure := func() { fmt.Printf("Hey %s!n", msg) } return closure } func main() { fmt.Println(time.Now()) hey := greeting("bob") time.Sleep(time.Second * 10) hey() }
The output will be as follows:
2017-01-29 12:42:09.767187225 -0500 EST Hey bob (at 2017-01-29 12:42:09.767323847 -0500 EST)!
Note that the timestamp is calculated when the msg
variable is initialized, at the time the greeting("bob")
value is assigned to the hey
variable.
So, 10 seconds later, when greeting
is called and the closure is executed, it will reference the message that was created 10 seconds ago.
This example shows how closures preserve state. Instead of manipulating the state in the outside environment, closures allow states to be created, passed around, and subsequently referenced.
With functional programming, you still have a state, but it's just passed through each function and is accessible even when the outer scopes, from where they originated, have already exited.
Later in this book, we'll see a more realistic example of how closures can be leveraged to maintain a context of application resources required by an API.
Another way to speed up our recursive Fibonacci function is to use Go's concurrency constructs.
Given the expression result := function1() + function2()
, parallelization means that we can run each function on a different CPU core and the total time will be approximately the time it takes for the most expensive function to return its result. Consider the following explanation for parallelization and concurrency:
- Parallelization: Executing multiple functions at the same time (in different CPU cores)
- Concurrency: Breaking a program into pieces that can be executed independently
Note
I recommend that you check out the video Concurrency is Not Parallelism, by Rob Pike at https://player.vimeo.com/video/49718712. This is where he explains concurrency as a decomposition of a complex problem into smaller components, where individual components can be run simultaneously resulting in improved performance, assuming communication between them is managed.
Go enhances the concurrent execution of Goroutines with synchronization and messaging using channels and provides multiway concurrent control with the Select
statement.
The following language constructs provide a model in Go for concurrent software construction that is easy to understand, use, and reason about:
- Goroutine: A lightweight thread managed by the Go runtime.
- Go statements: The
go
instruction that starts the execution of a function call as an independent concurrent thread of control, or Goroutine, in the same address space as the calling code. - Channel: A typed conduit through which you can send and receive values with the channel operator, namely
<-
.
In the following code, data
is sent to channel
in the first line. In the second line, data
is assigned the value received from channel
:
channel <- data data := <-channel
Since Go channels behave as FIFO queues, where the first items in are the first items out, and since the calculation for the next number in a Fibonacci sequence is a small component, it seems that our Fibonacci sequence function calculation is a great candidate for a concurrency implementation.
Let's give it a go. First, let's define a Channel
function that uses a channel to perform Fibonacci calculations:
func Channel(ch chan int, counter int) { n1, n2 := 0, 1 for i := 0; i < counter; i++ { ch <- n1 n1, n2 = n2, n1 + n2 } close(ch) }
First, we declare the variables n1
and n2
to hold our initial sequence values of 0
and 1
.
Then, we create a loop for the total number of times given. In each loop, we send the next sequential number to the channel and calculate the next number in the sequence, until we reach our counter value, which is the last sequential number in our sequence.
The following FibChanneled
function creates a channel, namely ch
, using the make()
function and defines it as a channel that contains integers:
funcFibChanneled(n int) int { n += 2 ch := make(chan int) go Channel(ch, n) i := 0; var result int for num := range ch { result = num i++ } return result }
We run our Channel
(Fibonacci) function as a Goroutine and pass it the ch
channel and the 8
number, which tells Channel
to produce the first eight numbers from the Fibonacci sequence.
Next, we range over the channel and print any values that the channel produces for as long as the channel has not been closed.
Now, let's take a breather and examine what we've accomplished with our Fibonacci sequence examples.