Golang Code Examples

Functional Programming

14 Aug 2014

Description

This example demonstrates the use of a few standard functional idioms, map and filter.

It demonstrates how to chain/compose them in a syntactically pleasing manner and provides some discussion on Function Programming (FP) and how FP can be accomplished using Go.

Enums are also covered, as well as the use of the Join function from the go-goodies/go_utils package.


Golang Features

This golang code sample demonstrates the following go language features:

  • enum with iota
  • heredoc with interpolation
  • methods
  • range iteration
  • string slice literal initialization
  • first-class function


3rd Party Libraries

This golang code sample uses the following third party libraries:


Code Example

package main

import (
    "fmt"
    "log"
    "strings"
    "errors"
    u "github.com/go-goodies/go_utils"
)
// enums indicating number of characters for that type of word
// ex: a TINY word has 4 or fewer characters
const (
    TEENINY WordSize = 1
    SMALL   WordSize = 4 << iota
    MEDIUM                      // assigned 8 from iota
    LARGE                       // assigned 16 from iota
    XLARGE  WordSize = 32000
)

type WordSize int

func (ws WordSize) String() string {
    var s string
    if ws&TEENINY == TEENINY {
        s = "TEENINY"
    }
    return s
}

// ChainLink allows us to chain function/method calls.  It also keeps  
// data internal to ChainLink, avoiding the side effect of mutated data.
type ChainLink struct {
    Data []string
}

func (v *ChainLink)Value() []string {
    return v.Data
}

// stringFunc is a first-class method, used as a parameter to _map
type stringFunc func(s string) (result string)

// _map uses stringFunc to modify (up-case) each string in the slice
func (v *ChainLink)_map(fn stringFunc) *ChainLink {
    var mapped []string
    orig := *v
    for _, s := range orig.Data {
        mapped = append(mapped, fn(s))  // first-class function
    }
    v.Data = mapped
    return v
}

// _filter uses embedded logic to filter the slice of strings
// Note: We could have chosen to use a first-class function
func (v *ChainLink)_filter(max WordSize) *ChainLink {
    filtered := []string{}
    orig := *v
    for _, s := range orig.Data {
        if len(s) <= int(max) {             // embedded logic
            filtered = append(filtered, s)
        }
    }
    v.Data = filtered
    return v
}


func main() {
    nums := []string{
        "tiny",
        "marathon",
        "philanthropinist",
        "supercalifragilisticexpialidocious"}

    data := ChainLink{nums};
    orig_data := data.Value()
    fmt.Printf("unfiltered: %#v\n", data.Value())

    filtered := data._filter(MEDIUM)
    fmt.Printf("filtered: %#v\n", filtered)

    fmt.Printf("filtered and mapped (MEDIUM sized words): %#v\n",
        filtered._map(strings.ToUpper).Value())

    data = ChainLink{nums}
    fmt.Printf("filtered and mapped (MEDIUM sized words): %#v\n",
        data._filter(MEDIUM)._map(strings.ToUpper).Value())

    data = ChainLink{nums}
    fmt.Printf("filtered twice and mapped (SMALL sized words): %#v\n",
        data._filter(MEDIUM)._map(strings.ToUpper)._filter(SMALL).Value())

    data = ChainLink{nums}
    val := data._map(strings.ToUpper)._filter(XLARGE).Value()
    fmt.Printf("mapped and filtered (XLARGE sized words): %#v\n", val)

    // heredoc with interpoloation
    constants := `
** Constants ***
SMALL: %d
MEDIUM: %d
LARGE: %d
XLARGE: %d
`
    fmt.Printf(constants, SMALL, MEDIUM, LARGE, XLARGE)
    fmt.Printf("TEENINY: %s\n\n", TEENINY)

    fmt.Printf("Join(nums, \"|\")     : %v\n", u.Join(nums, "|"))
    fmt.Printf("Join(orig_data, \"|\"): %v\n", u.Join(orig_data, "|"))
    fmt.Printf("Join(data, \"|\")     : %v\n\n", u.Join(data.Value(), "|"))

    if u.Join(nums, "|") == u.Join(orig_data, "|") {
        fmt.Println("No Side Effects!")
    } else {
        log.Print(errors.New("WARNING - Side Effects!"))

    }
}

Notes - FP

Functional Programming Defined

Functional programming (FP) is a programming paradigm, a style of building the structure and elements of computer programs, that treats computation as the evaluation of mathematical functions and avoids state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions. In functional code, the output value of a function depends only on the arguments that are input to the function, so calling a function f twice with the same value for an argument x will produce the same result f(x) both times. Eliminating side effects, i.e. changes in state that do not depend on the function inputs, can make it much easier to understand and predict the behavior of a program, which is one of the key motivations for the development of functional programming.

In contrast, imperative programming changes state with commands in the source language, the most simple example is the assignment. Functions do exist, not in the mathematical sense, but in the sense of subroutine. They can have side effects that may change the value of program state. Functions without return value therefore make sense. Because of this, they lack referential transparency, i.e. the same language expression can result in different values at different times depending on the state of the executing program.


Functional Programming Features

Separation of Concerns

FP provides for separation-of-concerns by emphasizing function composition, where smaller, simpler functions are combined to build more complicated ones.

In the code snippet below from this example, we combine the _filter and _map functions to perform both actions on the data, that is kept safe in the running function call stack.

When we want the result of all the actions performed by all the chained functions we call the Value method.

data._filter(MEDIUM)._map(strings.ToUpper).Value()

The ability to easily compose functions encourages factoring (breaking apart) functions for maintainability and code reuse.

No State

FP brings you to the holy grail of modularity: no side effects at all. You just have your f(x) = y.

Put x in, get y out. No changes to x or anything else.

FP makes you stop thinking about state, and start thinking in terms of values.

All of your functions simply receive values and produce new values.

FP does not need to protect the shared state of a variable, because the value is fixed.

This in turn avoids the majority of the hoop jumping that imperative languages go through to implement an algorithm across processors or machines to protect state.

Having no state means that your application is modular.

Modularity

There are no dependencies where one part/object of your app mutates a piece of data that is used by another part/object of your app, which then creates an inconsistent state (and bugs).

In FP, rather than mutating the state of objects, we simply return a new object with the changes we want.

FP makes heavy use of stacks and recursion.

Lazy Evaluation

Lazy evaluation improves application performance.

It allows you to define a variable and only load it in memory and only use it when needed.

Golang provides closures when and be leveraged to perform lazy evaluation, but we don't cover that in this example.

Sync's can be included to avoid re-evalution.

Futures can be used to compute a value before you need to actually use the value.

Lazy evaluation of function parameters can be emulated using Go channels and goroutines to implement high-level thunks, but again we don't cover that in this example.

Closures

Go supports closures via anonymous functions.

Anonymous functions are useful when you want to define a function inline without having to name it.

A closure is associated with the environment when they are defined.

Full closures can ensure referential transparency.

The following example comes from Go by Example: Closures

package main
import "fmt"

// intSeq returns another function, which we define anonymously in the body of intSeq. 
// The returned function closes over the variable i to form a closure.
func intSeq() func() int {
    i := 0
    return func() int {  // anonymous function
        i += 1
        return i
    }
}
func main() {
    // We call intSeq, assigning the result (a function) to nextInt. 
    // This function value captures its own i value, 
    /// which will be updated each time we call nextInt.
    nextInt := intSeq()
    fmt.Println(nextInt())  // 1
    fmt.Println(nextInt())  // 2
    fmt.Println(nextInt())  // 3

    // Create a new closure to prove that state is unique to that particular function.
    newInts := intSeq()
    fmt.Println(newInts())  // 1
}
$ go run closures.go
1
2
3
1

Recursion

Go supports recursive functions. Here’s a classic factorial example.

The following example comes from Go by Example: Recursion

package main
import "fmt"

//This fact function calls itself until it reaches the base case of fact(0).
func fact(n int) int {
    if n == 0 {
        return 1
    }
    return n * fact(n-1)
}
func main() {
    fmt.Println(fact(7))
}
$ go run recursion.go 
5040

Execution Order

Implicit programming is procedural and uses state for synchronization.

_filter(MEDIUM); _map(strings.ToUpper)

In this example, _filter runs before _map; Possibly because _filter sets up some data that _map needs.

FP uses chaining of function calls:

data._filter(MEDIUM)._map(strings.ToUpper)

In stateless programming, the order of execution matters only if the output of the first function called is needed as an input for the subsequent function.

In our example, the order does not matter, other than in CPU cycles required.

    data = ChainLink{nums}
    fmt.Printf("filtered and mapped (MEDIUM sized words): %#v\n",
        data._filter(MEDIUM)._map(strings.ToUpper).Value())

    data = ChainLink{nums}
    fmt.Printf("mapped and filtered (MEDIUM sized words): %#v\n",
        data._map(strings.ToUpper)._filter(MEDIUM).Value())

  // OUTPUT (same for both)
  // filtered and mapped (MEDIUM sized words): []string{"TINY", "MARATHON", "PHILANTHROPINIST"}     

Referential Transparency

In FP, referential transparency means that given a function and an input value, you will always receive the same output.

There is no external state used in the function.

_filter is an example of a referentially transparent function:

    filtered := data._filter(MEDIUM)
    fmt.Printf("filtered (MEDIUM sized words): %#v\n", filtered)

    fmt.Printf("filtered and mapped (MEDIUM sized words): %#v\n",
        filtered._map(strings.ToUpper).Value())

    data = ChainLink{nums}
    fmt.Printf("filtered and mapped (MEDIUM sized words): %#v\n",
        data._filter(MEDIUM)._map(strings.ToUpper).Value())

  // OUTPUT (same for both)
  // filtered and mapped (MEDIUM sized words): []string{"TINY", "MARATHON", "PHILANTHROPINIST"}

Given referential transparency, we can manipulate FP function calls like components in an algebraic expression.

So, we can assign data._filter(MEDIUM) to the variable filtered and either use that variable or explicitly execute data._filter(MEDIUM) and get the same results each time.

No ReAssignment Statements

When a variable X is created, it is given an initial value Y; Variable X is never subsequently modified.

So, in FP, we can be sure that each occurrence of X denotes the same value of Y.

Higher Order Functions

First-class functions are first-class citizens that can be:

  • named by a variable
  • used as parameter
  • returned as a result
  • stored in any kind of data structure

Currying allows functions to yield new functions as their return value.

Functional Programming in Go

Go supports first class functions, higher-order functions, user-defined function types, function literals, closures, and multiple return values.

Go allows you to choose to implement your solutions using functional programming techniques.

Go does not go out if its way to encourage you to do so, but as you can see in this example, it's not difficult.

If you prefer the usual functional ways to work with sequences of data in Go, there are 3rd party libraries for that like:

High Order Functions and Chaining in this Example

A Golang first-class function, stringFunc, is used as input to the _filter method, which uses that function's logic to perform it's filtering of data.

Each method (_map, _filter) is linked to a ChainLink struct.

The ChainLink struct has a single field, Data, whose value can be modified as the chain of calls progress.

The ChainLink struct has one method, Value, that is used to extract the current value of it's Data field.


Notes - Enum

This example also demonstrates the following usage of Go enums:

const (
    TEENINY WordSize = 1
    SMALL   WordSize = 4 << iota
    MEDIUM                      // assigned 8 from iota
    LARGE                       // assigned 16 from iota
    XLARGE  WordSize = 32000
)

An enum consisting of constants (TEENINY, SMALL, MEDIUM, LARGE, XLARGE) is created to indicate the number of chararacters for that type of word, e.g., a SMALL word has 4 or fewer characters.

The WordSize type is used in the definition of the constants.

Go's iota identifier is used in const declarations to very concisely define the related WordSize constants, simplifying the definitions of those incrementing numbers; It is given an initial value of 4. So, each subsequent constant in the enum is incremented by 4.

The ability to attach a method such as String to any user-defined type makes it possible for arbitrary values to format themselves automatically for printing. Although you'll see it most often applied to structs, this technique is also useful for scalar types such as floating-point types like ByteSize.

The String method linked to the WordSize struct causes the text "TEENINY" to be printed instead of the integer 0.


Notes - go-goodies

We use the Join function from go-goodies/goutils to verify that there are no side effects from calling *filter* and _map:

    if u.Join(nums, "|") == u.Join(nums_orig_state, "|") {
        fmt.Println("No Side Effects!")
    . . .

Note: The use of u.Join is not necessarily the most efficient option, but helps to visually illustrate the point.


Output


unfiltered: []string{"tiny", "marathon", "philanthropinist", "supercalifragilisticexpialidocious"}
filtered: &main.ChainLink{Data:[]string{"tiny", "marathon", "philanthropinist"}}
filtered and mapped (MEDIUM sized words): []string{"TINY", "MARATHON", "PHILANTHROPINIST"}
filtered and mapped (MEDIUM sized words): []string{"TINY", "MARATHON", "PHILANTHROPINIST"}
filtered twice and mapped (SMALL sized words): []string{"TINY", "MARATHON"}
mapped and filtered (XLARGE sized words): []string{"TINY", "MARATHON", "PHILANTHROPINIST", "SUPERCALIFRAGILISTICEXPIALIDOCIOUS"}

** Constants ***
SMALL: 8
MEDIUM: 16
LARGE: 32
XLARGE: 32000
TEENINY: TEENINY

Join(nums, "|")     : tiny|marathon|philanthropinist|supercalifragilisticexpialidocious
Join(orig_data, "|"): tiny|marathon|philanthropinist|supercalifragilisticexpialidocious
Join(data, "|")     : TINY|MARATHON|PHILANTHROPINIST|SUPERCALIFRAGILISTICEXPIALIDOCIOUS

No Side Effects!

Process finished with exit code 0


Conclusion

Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.

So, every time that I think, "There are no 'objects' in Golang. Where are the objects?" ... or "Where are the 'generics'?" ... or "Where are the built-in functional programming idioms?", Go provides a better way of doing what I want to do.


References

comments powered by Disqus
The content of this site is licensed under the Creative Commons 3.0License and code is licensed under a BSD license