Book Image

Learning Functional Programming in Go

By : Lex Sheehan
Book Image

Learning Functional Programming in Go

By: Lex Sheehan

Overview of this book

Lex Sheehan begins slowly, using easy-to-understand illustrations and working Go code to teach core functional programming (FP) principles such as referential transparency, laziness, recursion, currying, and chaining continuations. This book is a tutorial for programmers looking to learn FP and apply it to write better code. Lex guides readers from basic techniques to advanced topics in a logical, concise, and clear progression. The book is divided into four modules. The first module explains the functional style of programming: pure functional programming, manipulating collections, and using higher-order functions. In the second module, you will learn design patterns that you can use to build FP-style applications. In the next module, you will learn FP techniques that you can use to improve your API signatures, increase performance, and build better cloud-native applications. The last module covers Category Theory, Functors, Monoids, Monads, Type classes and Generics. By the end of the book, you will be adept at building applications the FP way.
Table of Contents (21 chapters)
Title Page
Credits
About the Author
Acknowledgments
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface
Index

The difference between an anonymous function and a closure


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.

FP using 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 8number, 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.