Prefer Continuations to API Classes in F#
F# comes with a substantial object-oriented feature set, which makes it a very challenging language for making good decisions about the abstractions to employ to solve particular problems.
This article is a reaction to this very good article on why OO matters in F#. The central thesis of that article is the following.
The thesis of this article is that strategically admitting elements of OO in an F# codebase significantly improves quality and maintainability.
I’m going to argue the opposite, here is the thesis of this article and a few more to come.
The thesis of this article is that admitting elements of OO in an F# codebase may be a sign something is significantly wrong with your design and decreases quality and maintainability.
You’ll notice the use of the word may in the thesis above, as I am not trying to be dogmatic and suggest OO is always the wrong abstraction for a functional first code base. But my guess is most people attracted to F# are coming from a C# background and will have tendancy to use the abstractions they are readily familiar with to solve problems, OO abstractions.
Easy functional languages, of which I will include Elm and Purescript and yes even Haskell, are easy first and foremost easy because they have no mechanism for traditional OO programming. In fact, Purescript and Haskell are so different, that as a traditional OO programmer, you may struggle to figure out how you write anything at all in those languages. I know i have! And trust me, there is a way :)
Tangible Suggestions vs Vague Platitudes
The OO matters article references another very good article of using SOLID principles for writing solid and functional code. And for every point there is a counterpoint, here is a recent talk declaring every element of SOLID is wrong.
I referenced this talk in the comments section of the OO matters article and the author responded that the talk was offering nothing in response while tearing apart SOLID.
“Write simple code!” is a vaugue platitude just as meaningless as “modules should have a single responsibility”.
And I agree with this criticism, so I hope to provide tangible suggestions for restructuring your desgin to avoid OO abstractions, and a memorable acronym as a plugin replacement for each of the SOLID principles, of which I tend to agree are of little use to authors out in field writing functional API’s.
Avoid Classes as Value Parametric Modules
It is true that an API exposed through a module must often be context aware, however using classes as value parametric modules sets off alarm bells ringing in the back of brain.
Before explaining why those sirens are wailing, let’s have a look at a class used as a value parametric module.
The basic idea is that the class above is not any different from a regular ol’ module, except for a few dependencies injected as value parameters. This saves us from having to pass the dependencies into each function. I do this every day in my dependency injection driven C# codebase, so what’s the big deal?
The following chart is cribbed from the best source of functional design on the web, and it visualises why the alarm bells are ringing.
Using the classes as value parametric modules will most likely lead to a request/response api where a service is instantiated with some dependencies, and all further interaction with this service will be in a a request/response manner, with each function called independently of every other function in the api.
This is traditional dependency injection driven OO service design and can be replaced with a more flexible continuation passing style functional design. If you haven’t heard of CPS, then again I refer you to the best source of functional design on the web.
Continuation Passing Style
Continuations passing style is so fundamental to functional API design that I’m going to use it as the first acronym in my replacement for SOLID. If you’re struggling on how to write a functional API then remember users of your functional API will be using it in a CPS manner as opposed to an OO request/response manner.
And as this concept is so fundamental, we will replace the Single Responsiblity Principle with the Continuation Passing Style Principle (S -> C). Alluding back to my point at the start of the article about writing code in Purescript and Haskell, this is the secret, and and this is the reason why writing API’s in those languages is such a struggle for progammers weaned on the request/response mantra of mainstream languages.
So now that we’ve introduced the principle, let’s see how we can apply it to a class as value parametric module.
The Reader Pattern
The first thing that jumps out in the MyParametricClass code above is the reuse of dependencies in multiple functions, the general pattern emerging in the class is the following function signature.
'arg -> 'dep -> 'return
We take an argument, some dependencies, and we return something. And looking though our vast array of CPS style patterns, one in particular stands out as a way to abstract away the above signature, Reader.
A quick search of the web and the following turns up as a reasonably simple implementation of the Reader pattern in F#.
And let’s get a quick sense of how this pattern works with a couple of test functions.
I hope you see straight away at how even this trivial test code shows the power of the CPS style of API design, by choosing a common abstraction for our functions (Reader), we have handed control back over to the user of our API, and allowed them to do something unimaginable in our previous design.
(greeter >>= calc)
We’ve allowed them to sequence two of our functions together for use in a manner appropriate to solve a problem in their particular domain.
The user sequences a solution, and then runs the solution, along with the configuration dependency to get a result. No matter how complicated and intricate their sequencing gets it will always combine to simple Reader.
The Reader Pattern Applied to My API
With an initial success out of the way, let’s hop back to our API design and apply the Reader Pattern to it.
The function themselves do nothing of any use, what’s more imporatant are the function signatures and the MyApiPart type. This is the abstraction we’ll be using to tie our whole api together and make it easy for a user to chain together functions and combine them in ways we can’t possibly imagine.
So let’s add a few more combinators to our Reader module in order to facilitate this sequencing of our newly functional API.
Hopefully the friendly function names, that accompany the infix operators, make it obvious how they allow the functions in our API to be combined.
Because now the fruits of our labor paid off and our API has new and exciting ways for a user to combine our functions.
Why to Avoid Classes as Parametric Modules
As opposed to giving you vague platitudes of “write simple code” or “modules should do one thing”, I hope this article has provided you something meaty to chew on when implementing a functional API.
The crux of my reasoning for avoiding classes as parametric modules, is employing them will make it very difficult to provide a composable, continuation passing style functional api, that inverts control to the user and allows them to solve problems in manners the author of the api never could have imaginged.
The sequencing of functional programming is fundamentally different from OO and an API should enable the user of the code to consume it in a functional manner, continuation passing style.
Oh and remember to always “Write Simple Code!” :)
As I’ve gotten a few comments on this article, which is altogether unusual occurrence, but an altogether welcome one, I felt it would better to address them here as an addendum to the original article.
Maybe this better spells out the crux of my misunderstanding.
I don’t find the original example a particularily compelling example of why using classes as value parametric modules is good design. And yes, when I translate the example to C#, it does indeed look a whole lot like injecting dependecies into the constructor of a class, call me bonkers.
And my whole misunderstanding could probably be clarified with a more in depth explanation of this technique, of which I would be most welcoming, but was not provided in the original article.
As far as my non rigourous usage of the term continuation, instead of some term that from a branch of abstract algebra I have no knowledge of, and imparts zero intuition to programmers who rely upon intuition for their livelihood, I’ll leave it to the Haskellers to teach their followers programming in terms of category theory and see how far they get with that, while I’ll keep to learning it in terms of continuations.
f : ('a -> 'b)
print : ('b -> unit)
let a = 1
let b = f a
f : 'a -> ('b -> unit) -> unit
let a = 1
f a print
bind : Reader<'a> -> (a -> Reader<'b>) -> Reader<'b>
The type signature of bind sure looks a whole lot like a continuation to me, and as such imparts a sense of intuition that terms from category theory never will. In fact this explanation of bind in terms of a pipeInto function is when I first started to grok what bind was even doing.
And it’s precisely this intuition as to why i’ve not used builder syntax, and instead used the Hopac style of “skeleton bones”, and a corresponding friendly function name for usage with the pipe forward operator.
The idea is to reinforce that F# supports a third option for structuring an API, unfamilar to OO programmers, and that’s in terms of continuations.
And what I find simpler about continuations, compared to passing in depedencies to each method, or injecting them into a constructor of some service, and then repeatedly making requests to this service, is they allow us to compose our progam as a function, and then run that program with a supplied context that gets passed along on down the chain.