DEV Community

Cover image for Go Functional
Code Monk
Code Monk

Posted on

Go Functional

Go Functional is a Go package providing Functional Programming capabilities to Go.

What is Functional Programming?

Functional Programming is a style, most notably employed by LISP, Haskell, F#, Clojure, and Erlang, which (to various degrees) offer the following characteristics:

  1. Functions handle most of the logic
  2. Functions are pure. The only thing a function knows is what's passed into it. It cannot mutate it's inputs. It can only produce new outputs.
  3. Functions are first-class citizens. They can be passed as parameters to other functions.
  4. Functions can be chained. The output of one function can be the input of another. At the end of the chain, you get the data you want.

This is an extremely expressive style that makes reading and writing code a delight.

What does it look like?

There are many examples on the official docs, but in a nutshell, Go Functional works like this:

import (
    "github.com/sean9999/GoFunctional/fslice"
)

//  apply a filter function and then a map function to get squares of primes
inputNums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}

outputNums := fslice.From(inputNums).Filter(func(v int, _ int, _ []int) bool {
    return primes.IsPrime(v)
}).Map(func(v int, _ int, _ []int) int {
    return v * v
})

fmt.Println(outputNums)
// Output: [4 9 25 49 121]
Enter fullscreen mode Exit fullscreen mode

In the above, we take in a slice of integers, filter out all those that are not prime, square the remaining, and return that. We pass our user-defined functions into fslice.Map and fslice.Filter directly as anonymous functions, but this could just as easily be done differently.

onlyPrimes := func(val int, _ int, _ []int) {
    return primes.IsPrime(val)
}

square := func() {
    return val * val
}

outputNums := fslice.From(inputNums).Filter(onlyPrimes).Map(square)

fmt.Println(outputNums)
// Output: [4 9 25 49 121]
Enter fullscreen mode Exit fullscreen mode

The above might be considered more readable than the previous. They are functionally equivilant.

The general pattern that Fslice employs is this:

  • Every Fslice method takes a user-defined function
  • That user-defined function usually takes three parameters: value, index, and the underlying slice in it's entirety.
  • The user-defined function is called for every element in the slice, or as many times as necessary (if it can quit early, it will).

So, for example Fslice.Map() takes a user-defined function matching the signature MapFunction. The user-defined function takes one value and produces another value of the same type. Since Fslice.Map() calls that function for every element of the slice, the return value is another slice.

Often you'll only need the value in your user-defined function, but sometimes you need the full set of parameters. Here is an example of an fslice method returning early because it has all the information it needs, and makes use of all three parameters:

import (
    "github.com/fxtlabs/primes"
    "github.com/sean9999/GoFunctional/fslice"
)

// are any two sequential integers co-prime?
inputSlice := []int{1,2,3,4,5,6,7,8,9,20,24,665,771}

isCoPrimeWithPrevious := func(n int, i int, arr []int) bool {
    if n > 0 {
        m := arr[i-1]
        return primes.Coprime(m, n)
    }
    return false
}

answer := fslice.From(inputSlice).Some(isCoPrimeWithPrevious)

fmt.Println(answer)
// Output: true
Enter fullscreen mode Exit fullscreen mode

The set of methods and their signatures was inspired by the Javascript spec for iterative methods on arrays. Some array methods in Javascript mutate the underlying array. Go Functional avoids these. It aims to be more respectful of Functional Programing principles.

What's Next?

I would like to find a semi-authoritative set of methods, aside from Javascript, to base the work on.

I would like to develop Fmap, in the same manner as Fslice.

I would like to find a way to provide greater polymorphism by introducting the concept of a Functor. This could free the developer from having to restrict themselves to just one type (int, float64, string, etc) at a time.

Pull requests enthusiastically welcomed.

Read the blog post
Discuss on Hacker News

Top comments (0)