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:
- Left identity: return a »= f = f a
- Right identity: m »= return = m
- 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:
- Associativity: (a + b) + c = a + (b + c)
- Identity: a + 0 = a
- 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