The ONE fp Design Pattern

Rodolfo Hansen
5 min readSep 6, 2024

--

meme comparison pick

I’ve often heard it is hard to contemplate how functional programers actually do software architecture when they stand behind memes like the above! It would seem that function composition (`f · g`) can’t get you very far, can it?!

The Singular? Pattern in FP

Well, what is this function as a lego brick idea anyway, and is it “actually” the only pattern we use?

how functional programmers think of functions

In the picture above, this value f which happens to be a function seems to have lots of degrees of freedom when you are specifying its input and return types. On the fp side of software development we have been busy making functions as if they were swiss utility knives…

Well, not exactly. That would be a bad analogy, a better analogy would be that functional programmers have a decent bag of reusable functions in their toolkit. Functions that take in specific shapes of other functions, and data types so that you can actually recombine and re-adjust pipelines and being able to change the shape of different values.

f: List[Future[Int]] => Future[List[Int]]

g: Either[List[Error], String] => Validation[String]

h: Future[Either[Error, String]] => Future[String]
common shapes for functions that change the shape of values
common shapes of functions that change the shape of values

Here you see functions manipulating the stack of containers around the key values you are interested in. These stacks often represent side effects around what you are interested in: things like disk/network IO or exceptions. Still, unsurprisingly these stacks can still be modeled as functions as you’ll see bellow…

Koans

So it seems although, we “only” have functions in our toolbelt, the meme is a bit cheeky because here is one of these functions in our toolbelt:

type State[S, A] = S => (S, A)

State is a function. It has one parameter s and returns a tuple with its state ( s) parameter updated and a new value a.

It comes with other functions that help you work with it:

public State[S, A] create(A a){
return (S s) => (s, a);
}

public State[S, S] get[S](){
return (S s) => (s, s);
}

public State[S, ()] put[S](S s) {
return (S ignored) => (s, ());
}

public (S, A) run[S, A](S s, State[S, A] st) {
return st(s);
}

public State[S, B] map[S, A, B](State[S, A] sa, (A => B) f) {
return (S s) => {
(is, a) = run(s, sa); //get the next state and value
return (is, f(a)); //keep the state and replace the value with f(a)
}
}

public State[S, B] combine[S, A, B](State[S, A] sa, (A => State[S, B]) f) {
return (S s) => {
(is, a) = run(s, sa); //get intermediate state and value from sa
return run(is, f(a)); //run the second state with intermediate state
}
}

You could think of this as an FP Koan, as a functional programmer you learn how to use ONLY FUNCTIONS to, for example, track the evolution of the state of your favorite card game. An OOP expert might see instead a State class with a set of operations you can run on it, but if you remember how we defined type State[S, A] = S => (S, A) this is an alias for a function. There is no class.

So FP architecture is just combining functions with other functions that take their own different set of parameters to get your job done.

Lego blocks

In FP we discover these functions. Functions like

public A identity[A](A a) { return a; }

or

public C fold[A, B, C](Either[A, B] e, (A => C) onLeft, (B => C) onRight) = {
e match {
case Left(a) => return onLeft(a);
case Right(b) => return onRight(b);
}
}

which are unique in the sense they have a single implementation that matches that particular return type and parameter definition.

As an OOP expert you will think these functions are pointless! You would never call them in practice, since you would just use the value! or write the if statement yourself! Now is a great time to revisit the original Lego brick analogy; when you are an FP programmer you often find yourself in situations similar to this:

public Either[Int, Int] oddOrEven(Int i) {
if (i % 2 == 0) {
return Left(i);
}
return Right(i);
}

public Int nextOdd(Int i) {
return fold(oddOrEven(i), (i) => i+1, identity);
}

A simplified example, which I hope clarifies the practice. In FP we build functions like nextOdd by reusing other functions, both domain specific oddOrEven and from our toolbox fold , identity. This paradigm leads to an accordion of foreground values (the values in our domain / business logic we are actually interested in) going through a background context (supporting “containers” like State above) which is in your toolbelt along with some “shape shifting” functions that help you manipulate both the foreground values and background context like these concrete usages I drew bellow:

shapeshifting combinators like sequence, liftM, raiseError and attempt

The Singular? Pattern in FP

No, there isn’t a singular pattern in FP, it is just that they are built around function composition, instead of message passing.

In FP, the values you are interested in get wrapped, like in a burrito, into some background context. This serves a dual purpose of highlighting what’s truly important: your foreground values, through language features like do notation or for comprehensions while keeping explicit and local access to the entirety of the relevant background of the computation you are modeling at any given point.

Do you believe these Koans will melt away once you’ve mastered them and look back? Are you willing to dive in and max out on reusability?

--

--

Rodolfo Hansen
Rodolfo Hansen

Written by Rodolfo Hansen

Constructive Programming Advocate. Looking for new ways to leverage the connection between categories, logic, language, and lambdas…

No responses yet