Functional Go

Geison
6 min readSep 28, 2015

--

Functional Programming by Wikipedia:

“Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data”. In other words, functional programming promotes code with no side effects, no change of value in variables. It opposes imperative programming, which empathizes change of state”.

What does this mean?

  • No mutable data (no side effect).
  • No state (no implicit, hidden state).

Once assigned (value binding), a variable (a symbol) does not change its value.

All state is bad? No, the hidden, implicit state is bad.

Functional programming does not eliminate state, it just makes it visible and explicit (at least when programmers want it to be).

  • Functions are pure functions in the mathematical sense: their output depend only on their inputs, there is no “environment”.
  • The same result returned by functions called with the same inputs.

What are the advantages?

  • Cleaner code: “variables” are not modified once defined, so we don’t have to follow the change of state to comprehend what a function, a, method, a class, a whole project works.
  • Referential transparency: Expressions can be replaced by their values. If we call a function with the same parameters, we know for sure the output will be the same (there is no state anywhere that would change it).

There is a reason for which Einstein defined insanity as “doing the same thing over and over again and expecting different results”.

Advantages enabled by referential transparency

  • Memoization
  • Cache results for previous function calls.
  • Idempotence
  • Same results regardless of how many times you call a function.
  • Modularization
  • We have no state that pervades the whole code, so we build our project with small, black boxes that we tie together, so it promotes bottom-up programming.
  • Ease of debugging
  • Functions are isolated, they only depend on their input and their output, so they are very easy to debug.
  • Parallelization
  • Functions' calls are independent.
  • We can parallelize in different process/CPUs/computers/…
result = func1(a, b) + func2(a, c)

We can execute func1 and func2 in parallel because a won’t be modified.

  • Concurrence
  1. With no shared data, concurrence gets a lot simpler:
  2. No semaphores.
  3. No monitors.
  4. No locks.
  5. No race-conditions.
  6. No dead-locks.

Golang is a multi-paradigm programming language. As a Golang programmer why uses functional programming?

Golang is not a functional language but has a lot of features that enable us to applies functional principles in the development, turning our code more elegant, concise, maintainable, easier to understand and test.

Don’t Update, Create — String

Wrong

name := “Geison”name := name + “ Flores”

Right

const firstname = “Geison”const lasname = “Flores”const name = firstname + “ “ + lastname

Don’t Update, Create — Arrays

Wrong

years := [4]int{2001, 2002}years[2] = 2003years[3] = 2004years // [2001, 2002, 2003, 2004]

Right

years := [2]int{2001, 2002}allYears := append(years, 2003, [2]int{2004, 2005})

Don’t Update, Create — Maps

Wrong

ages := map[string]int{“John”: 30}ages[“Mary”] = 28ages // {‘John’: 30, ‘Mary’: 28}

Right

ages1 := map[string]int{“John”: 30}ages2 := map[string]int{“Mary”: 28}func mergeMaps(mapA, mapB map[string]int) map[string]int {allAges := make(map[string]int, len(mapA) + len(mapB))    for k, v := range mapA {        allAges[k] = v    }    for k, v := range mapB {        allAges[k] = v    }    return allAges}allAges := mergeMaps(ages1, ages2)

Higher-Order Functions

Functions and methods are first-class objects in Golang, so if you want to pass a function to another function, you can just treat it like any other object.

func caller(f func(string) string) {    result := f(“David”)    fmt.Println(result)}f := func(s name) string {    return “Hello “ + name}caller(f)

As Golang doesn`t have a builtin Map implementation, it is possible to use this one below:

Map

mapper := func (i interface{}) interface{} {    return strings.ToUpper(i.(string))}Map(mapper, New(“milu”, “rantanplan”))//[“MILU”, “RANTANPLAN”]

Filter

pred := func (i interface{}) bool {    return i.(uint64) > 5}Filter(pred, Uint64(1,2,3,4,5,6,7,8,9,10))//[6, 7, 8, 9, 10]

Reduce

acumullator := func (memo interface{}, el interface{}) interface{} {    return len(memo.(string)) + len(el.(string))}Reduce(New(“milu”, “rantanplan”), acumullator, string).(uint64)// result 14

Closure

func add_x(x int) func(z int) int {    return func(y int) int { // anonymous function        return x + y    }}add_5 := add_x(5)add_7 := add_x(7)add_5(10) // result 15add_7(10) // result 17

Currying and Partial Functions

Higher-order functions enable Currying, which the ability to take a function that accepts n parameters and turns it into a composition of n functions each of them takes 1 parameter. Direct use of currying is the Partial Functions where if you have a function that accepts n parameters then you can generate from it one or more functions with some parameter values already filled in.

func plus(x, y int) int {    return x + y}func partialPlus(x int) func(int) int {    return func(y int) int {        return plus(x, y)    }}func main() {    plus_one := partialPlus(1)    fmt.Println(plus_one(5)) //prints 6}

Eager vs Lazy Evaluation

  • Eager evaluation: expressions are calculated at the moment that variables are assigned to the function called…
  • Lazy evaluation: delays the evaluation of the expression until it is needed.
  • Memory efficient: no memory used to store complete structures.
  • CPU efficient: no need to calculate the complete result before returning.
  • Laziness is not a requisite for FP, but it is a strategy that fits nicely on the paradigm(Haskell).

Golang uses eager evaluation (but short-circuits && or ||).

Golang channels and goroutines enable the creation of generators that could be a way to have lazy evaluation.

Golang arrays are not lazy, use channels and goroutines to emulate a generator when necessary.

Recursion

Looping by calling a function from within itself. When you don’t have access to mutable data, recursion is used to build up and chain data construction. This is because looping is not a functional concept, as it requires variables to be passed around to store the state of the loop at a given time.

  • Purely functional languages have no imperative for-loops, so they use recursion a lot.
  • If every recursion created a stack, it would blow up very soon.
  • Tail-call optimization (TCO) avoids creating a new stack when the last call in recursion is the function itself.
  • TCO is not implemented in Golang.
  • Unfortunately, the following recursion style in Golang has its own tax: Performance.

Solving Golang Lack of TCO(Tail Call Optimization)

The functional solution has problems with big values

func fibonacciRecursive(n int) int {    if n <= 1 {        return n    }    return fibonacciRecursive(n — 1) + fibonacciRecursive(n — 2)}

The iterative solution works perfectly with large values

func fibonacci(n int) int {    current, prev := 0, 1    for i := 0; i < n; i++ {        current, prev = current + prev, current    }    return current}

FP in OOP?

It is possible to do FP in OOP? Yes, it is!

  • OOP is orthogonal to FP.
  • Well, at least in theory, because:
  • Typical OOP tends to emphasize the change of state in objects.
  • Typical OOP mixes the concepts of identity and state.
  • The mixture of data and code raises both conceptual and practical problems.
  • OOP functional languages: Scala, F#, …

A Practical Example

Exercise: “What’s the sum of the first 10 natural number whose square value is divisible by 5?”

Imperative

func main() {    n, numElements, s := 1, 0, 0    for numElements < 10 {        if n * n % 5 == 0 {            s += n            numElements++        }        n++    }    fmt.Println(s) //275}

Functional

sum := func (memo interface{}, el interface{}) interface{} {    return memo.(float64) + el.(float64)}pred := func (i interface{}) bool {    return (i.(uint64) * i.(uint64)) % 5 == 0}createValues := func () []int {
values := make([]int, 100)
for num := 1; num <= 100; num++ { values = append(values, num) }
return values
}Reduce(Filter(pred, createValues), sum, uint64).(uint64)

The last advice

Learn at least one functional language, it will open your mind to a new paradigm making you a better programmer.

Some Functional Languages:

  • Haskell
  • ML (Standard ML, Objective Caml, …)
  • Scheme
  • Erlang
  • Scala
  • Clojure
  • F#

Conclusion

  • As you can tell, Golang helps you write in functional style but it doesn’t force you to it.
  • Writing in a functional style enhances your code and makes it more self-documented. Actually it will make it more thread-safe also.
  • The main support for FP in Golang comes from the use of closures, iterators, and generators.
  • Golang still lacks an important aspect of FP: Map, Filter, Reduce, Immutable and Generic types, Pattern Matching and Tails Recursion.
  • There should be more work on tail recursion optimization, to encourage developers to use recursion.
  • Any other thoughts?

--

--

Geison

Software Craftsman, traveller, dog trainer and Husband.