Functions

Higher-order functions

= functions that take one or more functions as arguments or return a function as a result.

package main

import (
   "fmt"
)

func power(fn func(int) int) func(int) int { // <--- high-order function
   return func(x int) int {
      return fn(x)
   }
}

func square(x int) int {
   return x * x
}

func cube(x int) int {
   return x * x * x
}

func main() {
   squareFunc := power(square)
   cubeFunc := power(cube)

   fmt.Println(squareFunc(2)) // Output: 4
   fmt.Println(cubeFunc(2))   // Output: 8
}

Anonymous functions/Closures/Lambda Functions

= functions that are defined and called in the same place, without a name. They retain bindings to variables defined outside the body of the closure.

package main

import "fmt"

func main() {

     sum := func(a, b, c int) int {
          return a + b + c
     }(3, 5, 7)

     fmt.Println("5+3+7 =", sum)
}

Monads

= design pattern that allows us to chain operations together in a pipeline, while abstracting away the details of how the operations are performed. Has 3 components:

  • Type Constructor (T): A type that represents a computational context. It wraps a value and provides a way to apply functions to that value while preserving the context.

  • Unit Function (unit or return): A function that takes a value and wraps it in the monadic context, creating a new instance of the monad.

  • Bind Function (flatMap, chain, >>=): A function that takes a monad and a function that maps a value to a new monad. It applies the function to the value inside the monad and returns a new monad.

package main

import (
	"errors"
	"fmt"
)

// Result represents a monad-like type that can either hold a value or an error.
type Result struct {
	value int
	err   error
}

// Unit function wraps a value in the Result monad.
func Unit(val int) Result {
	return Result{value: val}
}

// Bind function applies a function to the value inside the Result monad.
func (r Result) Bind(f func(int) Result) Result {
	if r.err != nil {
		return r
	}
	return f(r.value)
}

// Example functions that work with Result monads.
func addOne(x int) Result {
	return Unit(x + 1)
}

func divideBy(x int, y int) Result {
	if y == 0 {
		return Result{err: errors.New("division by zero")}
	}
	return Unit(x / y)
}

func main() {
	// Using Result monad to sequence computations.
	result := Unit(10).Bind(addOne).Bind(func(x int) Result {
		return divideBy(x, 5)
	})

	if result.err != nil {
		fmt.Println("Error:", result.err)
	} else {
		fmt.Println("Result:", result.value)
	}
}

Functors

= objects or data structures that can be mapped over, meaning you can apply a function to each element within the functor.

= superclass of a monad, which means that all monads are functors as well.

= design pattern that represents a container or structure that can be mapped over. In functional programming, a functor is often used to apply a function to each element of a collection and return a new collection with the transformed elements.

package main

import "fmt"

// IntList is a custom functor-like type representing a list of integers.
type IntList []int

// Map applies a given function to each element in the IntList and returns a new IntList.
func (il IntList) Map(fn func(int) int) IntList {
	result := make(IntList, len(il))
	for i, v := range il {
		result[i] = fn(v)
	}
	return result
}

func main() {
	// Creating an IntList.
	list := IntList{1, 2, 3, 4, 5}

	// Define a function to square each element.
	square := func(x int) int {
		return x * x
	}

	// Using the Map method to apply the square function to each element.
	squaredList := list.Map(square)

	// Printing the squared list.
	fmt.Println(squaredList) // Output: [1 4 9 16 25]
}

Monoids

= data types that have two key properties: associativity and an identity element.

  1. Associativity: For all values a, b, and c of the same type, (a mappend b) mappend c should be equal to a mappend (b mappend c), where mappend represents the operation.

  2. Identity Element: There exists an element (usually denoted as mempty) such that for any value a of the same type, mempty mappend a is equal to a and a mappend mempty is equal to a.

package main

import "fmt"

// IntMonoid is a custom monoid-like type representing integers with addition as the operation.
type IntMonoid int

// Mappend defines the addition operation for IntMonoid.
func (a IntMonoid) Mappend(b IntMonoid) IntMonoid {
	return a + b
}

// Mempty represents the identity element for IntMonoid, which is 0 for addition.
func (IntMonoid) Mempty() IntMonoid {
	return 0
}

func main() {
	// Create instances of IntMonoid.
	x := IntMonoid(5)
	y := IntMonoid(10)

	// Use the Mappend method to combine values.
	result := x.Mappend(y)

	// Print the result.
	fmt.Println(result) // Output: 15
}

Last updated