A myth about F# that I hear repeatedly is that imperative code (e.g. loops) is simpler than functional code (e.g. folds). On one hand, simplicity is very subjective. On the other hand, simplicity is mostly determined by familiarity.
A typically used example is something like the following:
Let’s see what’s wrong with this myth.
What is simple anyway?
We find code simple when it is easy to grasp (and change). So the question is, what makes code easy to grasp for us?
A quick, coarse and not fully correct summary: our brain reads code in chunks. A chunk is a part of the code that we can understand quickly all at once. Examples are
foreach in the above code (assuming you are used to C#). The same holds for
List.fold when you are used to F#. Our working memory is strongly limited in the number of chunks we can track at once (around 5 to 7). So if we can read a method or function that is made up of at most seven chunks, it’s probably simple to us. If there are more chunks, the cognitive load grows, and the code is subjectively not simple anymore.
When I started programming in F#,
List.fold was new to me, and when I stumbled upon one – or wanted to write one – I had to break it down into several smaller parts:
fold: ah, this is a fold where we walk through all the values and aggregate some value
- the aggregation function (the
List.fold f initialState): the function we use for the aggregation that takes two arguments, the state so far and the current element (
f state current)
initialStateto start folding over the values.
Even worse was writing a fold; I always had to check the documentation to put these parts together correctly.
After reading and writing a couple of folds, my brain started to recognise a “fold pattern” as a whole, not as three individual parts. And all of a sudden, folds were simple to me.
Please, don’t use point-free style to scare people away.
What makes me sad about the example used for comparison is that it uses a point-free style. Point-free style is very condensed and hard to grasp when not used to. And it is not a good representation for functional programming, especially not for writing code in F#.
The comparison would be fairer when for example, this code is used:
Of course, if you want to sum the elements in a list in your real code, then simply use:
Which one is now simpler?
Assuming you are familiar with loops and with folds, then for me, there is a very clear winner: folds.
The reason is not that I can read one of them faster. They are both easy to read. The big difference is: how many things can possibly go wrong. A fold is a pure function that executes the passed function on every element and takes care of the plumbing. Not much can go wrong as long as I don’t do stupid things in the passed function.
By using a loop with mutable data, I have to do the plumbing myself. And therefore, more things can go wrong. Some may say that means less “magic”, but then assembler is way less magic than a loop in C# 😉
The above example is so small that the loop is easy to grasp. But when the algorithm grows bigger, things turn in favour of the fold, where plumbing is separated from the aggregation function.
In the end, my manual plumbing adds to the cognitive load.
Functional programming is more than folds!
I chose the above example because I have seen it being used together with the argument that imperative code is simpler than functional code more than once. And I think it serves as a good example for comparing statement- and loop-heavy code to a more declarative expression-based style using higher-ordered functions.
When starting with F#, you have to get used to
|>, function currying, partial application, computation expressions, and discriminated unions. At first, they look unfamiliar and take more time to understand than what you are probably used to (assignments, loops, if statements*) when coming from C# or similar language. But with time, they lose their spookiness, and you start using these additional tools alongside loops and mutable data in F#. And that’s the thing I like very much about F#; I can use functional, object-oriented and imperative tools in the language** to solve our business problems. Whatever is simplest.
* in F#,
if is an expression, not a statement
** I’m not a purist. As a purist, I probably would say that there are too many ways to accomplish a single thing in F#. I learned a very purist language at university (Oberon), where one design goal was that a thing could only be solved in a single way. This has its clear benefits, but overall, I disliked the way the code turned out because of it.