Functional Design Patterns

Partial application

Given a function that takes multiple arguments, we can partially apply it by fixing one or more of the arguments. This creates a new function that takes fewer arguments than the original function.

def add(x, y):
    return x + y

add(1, 2)  # 3

add_1 = partial(add, 1)
add_1(2)  # 3

Currying

Currying is the process of transforming a function that takes multiple arguments into a sequence of functions that each take a single argument.

def add(x, y):
    return x + y

add(1, 2)  # 3

add_curried = lambda x: lambda y: add(x, y)
add_curried(1)(2)  # 3

Composition

Composition is the process of combining two or more functions to produce a new function.

def add(x, y):
    return x + y

def square(x):
    return x * x

add_square = lambda x, y: square(add(x, y))
add_square(1, 2)  # 9

Continuation Passing Style aka The Hollywood Principle

Continuation-passing style (CPS) is a programming style used to simplify the control flow of a program. In CPS, functions take an additional argument called a continuation, which is a function that represents the rest of the computation.

def add(x, y, k):
    return k(x + y)

def square(x, k):
    return k(x * x)

def add_square(x, y, k):
    return add(x, y, lambda z: square(z, k))

add_square(1, 2, print)  # 9

Monads

A monad is a design pattern that provides a way to chain operations together in a functional programming language. Monads are used to encapsulate side effects and provide a way to compose functions that return monadic values.

Monads must satisfy the following laws:

  1. Left identity: return a »= f = f a
  2. Right identity: m »= return = m
  3. Associativity: (m »= f) »= g = m »= (\x -> f x »= g)

A monad is just a monoid in the category of endofunctors

class Monad:
    def __init__(self, value):
        self.value = value

    def bind(self, func):
        return func(self.value)

def add(x):
    return Monad(x + 1)

def square(x):
    return Monad(x * x)

result = Monad(1).bind(add).bind(square)
print(result.value)  # 4

Functors

A functor is a design pattern that provides a way to apply a function to the value inside a container. Functors are used to encapsulate side effects and provide a way to compose functions that return functor values.

An endofunctor is a functor that maps a category to itself.

class Functor:
    def __init__(self, value):
        self.value = value

    def map(self, func):
        return Functor(func(self.value))

def add(x):
    return x + 1

def square(x):
    return x * x

result = Functor(1).map(add).map(square)
print(result.value)  # 4

We can use monads to chain operations together in a functional programming with bind method. Functors are used to apply a function to the value inside a container.

Map

Map is a design pattern that provides a way to apply a function to each element of a collection. Map is used to transform a collection of values into a new collection of values by applying a function to each element.

def add(x):
    return x + 1

def square(x):
    return x * x

result = list(map(add, [1, 2, 3]))
print(result)  # [2, 3, 4]

result = list(map(square, [1, 2, 3]))
print(result)  # [1, 4, 9]

Monoids

A monoid is a design pattern that provides a way to combine two values of the same type into a single value. Monoids are used to encapsulate the concept of combining values and provide a way to compose functions that return monoidal values.

Monoids must satisfy the following laws:

  1. Associativity: (a + b) + c = a + (b + c)
  2. Identity: a + 0 = a
  3. Closure: a + b is of the same type as a and b
class Monoid:
    def __init__(self, value):
        self.value = value

    def combine(self, other):
        return Monoid(self.value + other.value)

    @staticmethod
    def empty():
        return Monoid(0)

result = Monoid(1).combine(Monoid(2)).combine(Monoid(3))
print(result.value)  # 6

A modoid homomorphism is a function that maps a complex structure to a simpler one while preserving the monoid structure.

Endomorphism (endofunctor) is a monoid homomorphism that maps a type to itself. All endomorphisms are monoids.

An example with the Event Sourcing pattern

Event sourcing is a design pattern that takes a state and applies an event to it to produce a new state. The state can be represented as a monoid, and the events are represented as a list of monoidal values.

Event->State->State

class Event:
    def __init__(self, value):
        self.value = value

    def apply(self, state):
        return state.combine(self)

class State(Monoid):
    def __init__(self, value):
        self.value = value

    def combine(self, other):
        return State(self.value + other.value)

    @staticmethod
    def empty():
        return State(0)

state = State.empty()
events = [Event(1), Event(2), Event(3)]

for event in events:
    state = event.apply(state)

print(state.value)  # 6