I love a lot of the thinking in functional programming (FP):
- Side-effect free functions - That they do not do hidden things beside doing work on the input and returning output
- Referential transparency of functions - A bit of the same as above, that what you put in to a function plus the function itself, completely defines its output
An area where I'm not so happy with some things I've seen in FP, is composability.
In my view, a well designed system or langauge should make functions (or other smallest unit of computation) more easily composable, not less.
What strikes me as one of the biggest elephants in the room regarding FP, is that typical functions compose fantastically as long as you are working with a single input argument, and a single output for each function application, but as soon as you start taking multiple input arguments and returned outputs though, you tend to end up with very messy trees of function application. Even handy techniques such as currying tend to get overly complex if you want to handle all the possible downstream dataflow paths in a structured way.
I (think I) know that monads are supposed to be a highly general way of addressing this problem, but it seems 99% of programmers (including me) have a really really hard time properly understanding the concept well enough that it would help make their code clearer, to them and others.
This is where I find the principles around network composability of Flow-based programming (FBP) shines so brightly.
It solves the composability problem in three basic ways:
- It gives each input and output its own identity, in the form of ports.
- It allows to define the data dependencies between these ports, instead of between functions.
- It allows the setup of these dependencies to happen at any place in the program, which makes it easy to e.g. produce a list of all the connections, as e.g. a simple list of (outport, inport) tuples.
The second point above is significant, as it means data dependencies are defined at the level of data, not functions.
Trying to define data dependencies by defining dependencies between functions, is a major impedance mismatch in my books, and what is causing the need for such complicated syntax in a lot of functional programming.
Flow-based programs on the other hand, are so easy that they can be simplified to two main parts:
- A list of processes (Somewhat the counterpart to functions in FP)
- A list of connections between input- and output ports.
These lists can either be code in a programming language embedded framework or serialized text formats like JSON or XML. The simplicity allows enormous flexibility.
With this background, I find that flow-based programming provides a level of composability that in practice helps makes programs clear to a much greater extent than functional programming.