Ascending Functional Reactive Programming 4/7

Nicolas Roumiantzeff
Esker-Labs
Published in
10 min readOct 26, 2020

Functional Reactive Programming: Simple and Unanimous?

Photo by Simon

My faith in functional reactive programming relies on the hypothesis that something nice might come up from mixing two hazardous components. Functional reactive programming as I see it, is about elegantly solving paradoxes and putting out fire with gasoline!

Part 1/7 Why is Reactive Programming so Complicated?
Part 2/7 Functional Programming to the Rescue?
Part 3/7 Why is Functional Programming so Controversial?
Part 4/7 Functional Reactive Programming: Simple and Unanimous?
Part 5/7 How did Functional Reactive Programming Originate?
Part 6/7 Where is Functional Reactive Programming Now?
Part 7/7 Which Functional Reactive Programming is the Grail?

Disclaimer: So here I am in the middle of my journey, after an exhausting approach march. Before the final assault on the summit, I need to cross a perilous monkey bridge and check my climbing equipment. This is not to be taken lightly.

1. Major criteria

In the next posts we will be exploring and comparing functional reactive tools. I have selected two major criteria in order to establish a rating: simplicity and unanimity.

1.1. Simplicity

We have seen in the first post of this series that reactive programming can be complicated. Let us see how functional reactive programming might solve this concern.

Making reactive programming simple:

Reactive programming deals with events occurring in the outside world (we are talking about the user clicking the mouse for instance).

We previously explained that this generates complexity.

Functional programming, on the other hand, is isolated from the outside world.

Functional reactive programming would make reactive programming simple, finally!

1.2. Unanimity

We have seen in the third post in this series that functional programming can be controversial. Let us see how functional reactive programming might solve this concern.

Making functional programming unanimous:

Functional programming has no direct impact on the outside world.

We saw previously that this explains, at least partly, the low popularity of functional programming among real world application developers.

Reactive programming, on the other hand, is good at interacting with the outside world.

Functional reactive programming would make adoption of functional programming unanimous, finally!

2. Minor criteria

In addition to simplicity and unanimity criteria, let us introduce two others, unidirectional and fractal, taken from an enlightening article by André Saltz about user-interface architectures.

Let me show how unidirectional and fractal architectures relate to functional reactive programming.

2.1. Unidirectional architecture

Recent years of Web application development have seen unidirectional data-flow taking over two-way binding. Functional reactive programming is the prime suspect in this matter.

To illustrate the two-way binding architecture, I chose the famous Model-View-Presenter pattern. When the view changes (because of a user input), the presenter updates the model accordingly. Conversely, when the model changes (because of an update performed by the presenter), the presenter updates the view accordingly. The model is synchronized with the view and the view is synchronized with the model.

To illustrate unidirectional data-flow, I chose the Model-View-Intent pattern. In this model, a new HTML page is generated after each user input. Since this might severely impact performance, the view component often implements a Virtual-DOM, an Incremental-DOM or the like.

Making reactive architecture unidirectional:

Allow me to draw a parallel between a Web application and a list structure. A unidirectional architecture would correspond to a singly-linked list and a two-way binding architecture would correspond to a doubly-linked list.

At first sight, a doubly-linked list seems superior as it is more powerful. On the other hand, there are situations where a singly-linked list is a better choice because it is simple and light-weight.

There is no pure-functional implementation of a doubly-linked list. Adding a node to a doubly-linked list requires a mutation of the existing nodes.

Luckily, when you need to go back and forth in a list, functional programming offers several clever alternatives to the doubly-linked list, one called a zipper for example.

One area where the functional approach really shines is concurrency. Without proper protections, you get into trouble when one thread updates a doubly-linked list while another one updates or even accesses the same list.

Functional programming naturally leads to a unidirectional architecture!

2.1. Fractal architecture

A fractal architecture is one that may repeat itself, indefinitely. Taking the example of a tree structure, one may add nodes beneath the root and above the leaves. In a non-fractal tree structure, the nature of the root or leave nodes differs from the nature of the regular nodes, preventing extensions of the tree from the root or leaves.

Making functional architecture fractal:

Functional programming implies immutability.

In classical functional programming, you pass a huge global immutable data structure (representing the whole outside world) to the main function which returns a modified copy (representing the new version of the outside world).

In reactive programming, as we saw earlier, the framework calls the program (instead of the program calling the framework).

In functional reactive programming, the framework would repeatedly call small functions of the program, passing as argument a small immutable data structure (representing a small part of the outside world).

Internally, the reactive framework would use the returned small modified copy to mutate the huge global data structure, hiding the dirty work from the pure functional program.

This functional reactive architecture would solve potential performance issues in a simple and unanimous way.

Reactive programming naturally leads to a fractal architecture!

Should I care or should I not?

Well, until we identify concrete implementations, you should not, as these fine words are only hypotheses and wishful thinking.

What we need: simple unanimous functional reactive programming!

What we desire: unidirectional fractal architecture!

3. Case study: IO Monads

Following the theoretical explanations above I wanted to elaborate on a more concrete example to show that the reactive programming paradigm is a necessary addition to the functional programming paradigm.

In the previous article, I described Monads as means to execute side-effects on the “real world” without giving up on the functional principles. I presented to you a code snippet to illustrate how inputs and outputs side-effects can be expressed as pure functions.

I will now point some short-comings of this minimalist version and demonstrate ways to circumvent them, without any object-oriented artifact, I promise.

Version 1, introducing the case study

This is a classical code with side-effects which reads an input value from an HTML element, multiplies it by 2 and outputs the result to an other HTML element.

var input = document.getElementById("input").value;
var times2 = 2 * input;
document.getElementById("output").value = times2;

Wait a minute, this is not even functional! Let us try and remove the side-effects, shall we?

Version 2, using an IO Monad to isolate the pure functions from the dirty work

Here, inputs and outputs are expressed as functions.

The unit function generates a monad instance which is, in the case of our IO monad, a function with no arguments, returning a value. It represents an effect (a function which produces a side-effect).

The bind function, applied on a monad instance, produces another monad instance, given a monadic function as second argument. A monadic function takes a value and returns a monad instance. The bind function is the means to compose monad instances.

The unit and bind functions abide by the monad laws, or “axioms” as named in Douglas Crockford’s talk: left identity, right identity and associativity. The laws are important to ensure that Monads may be composed to produce the expected results.

The input and output functions are examples of monadic functions. The input function returns an effect. This effect is a function returning the value contained in a given HTML element. The output function returns an effect which sets the value of a given HTML element.

The dirtyWork function “executes” the effect returned by the pureDescription function. In this implementation, “executing” an effect (which is a function), is simply calling the function (without argument).

dirtyWork();function dirtyWork(){
var monad = pureDescription();
monad();
}
///////////
// IO Monad
// unit: (() -> number) -> Monad (() -> number)
function unit(effect){
return effect;
}
// bind: Monad (() -> number) ->
// ((() -> number) -> Monad (() -> number)) -> Monad (() -> number)
function bind(monad, monadicFunction){
return unit(function(){
return monadicFunction(monad)();
});
}
// input: (() -> number) -> Monad (() -> number)
function input(effect){
return unit(function(){
return document.getElementById("input").value;
});
}
// output: (() -> number) -> Monad (() -> ())
function output(effect){
return unit(function(){
document.getElementById("output").value = effect();
});
}
/////////////////
// pure functions
// pureDescription: () -> Monad (() -> ())
function pureDescription(){
var monad0 = unit(function(){});
var monad1 = bind(monad0, input);
var monad2 = bind(monad1, times2);
var monad3 = bind(monad2, output);
return monad3;
}
// times2: (number -> ()) -> Monad (() -> number)
function times2(effect){
return unit(function(){
return 2 * effect();
});
}

There still is a caveat though. The “pure functions” part contains code that is actually pure. Nevertheless, it uses some functions as arguments which are not pure since they produce side effects when called. Nothing prevents the programmer to inadvertently trigger a side-effect by simply calling a function.

Let us remedy this loophole, shall we?

Version 3, protecting programmers from themselves

This code encapsulates the effect functions in JavaScript closures.

The implementation of the bind function has been moved inside the effect functions generated by calling the unit function. The effects themselves are not available except through the bind function. Now, binding is performed by calling an effect function with a monadic function as argument.

Additionally, the bind function memoizes the value returned by the effect function. This mechanism prevents a monadic function to inappropriately trigger a side-effect twice.

To actually “execute“ an effect, the function must be called with the special run function as argument. This mechanism is used directly by the dirtyWork function and indirectly inside functions generated by the bind function.

function run(){
}
dirtyWork();function dirtyWork(){
var monad = pureDescription();
monad(run);
}
///////////
// IO Monad
// unit: (() -> number) -> Monad (() -> number)
function unit(effect){
return function(monadicFunction){
if (monadicFunction === run){
return effect();
}
// bind: ((() -> number) -> Monad (() -> number)) ->
// Monad (() -> number)
return unit(function(){
var value = effect();
return monadicFunction(function(){
return value;
})(run);
});
};
}
// bind: Monad (() -> number) ->
// ((() -> number) -> Monad (() -> number)) -> Monad (() -> number)
function bind(monad, monadicFunction){
return monad(monadicFunction);
}
// input: (() -> number) -> Monad (() -> number)
function input(effect){
return unit(function(){
return document.getElementById("input").value;
});
}
// output: (() -> number) -> Monad (() -> ())
function output(effect){
return unit(function(){
document.getElementById("output").value = effect();
});
}
/////////////////
// pure functions
// pureDescription: () -> Monad (() -> ())
function pureDescription(){
var monad0 = unit(function(){});
var monad1 = bind(monad0, input);
var monad2 = bind(monad1, times2);
var monad3 = bind(monad2, output);
return monad3;
}
// times2: (number -> ()) -> Monad (() -> number)
function times2(effect){
return unit(function(){
return 2 * effect();
});
}

To properly finish the job, we need to hide, typically inside the closure of a framework of some sort, the special run function. This would prevent the “pure functions“ part of the code from prematurely ”executing“ an effect by calling a monad instance with the run function as argument.

The input and output functions implementations which are not functionally pure should also be moved to the framework implementation.

Let us properly finish the job, shall we?

Version 4, exasperating nasty programmers

In this final version (left as an exercise for the industrious reader), potentially dangerous functions, that is non-pure functions with side-effects, may be safely used through the IO monad interface. The side-effects are only triggered, in a controlled manner, by the framework.

In this sophisticated, yet beautiful architecture, the programmer provides the implementation of the pureDescription function and the framework calls this function. Technically, this is reactive programming.

This closes my (quite informal) demonstration that the reactive programming paradigm is a necessary addition to the functional programming paradigm.

Should I care or should I not?

First point: be aware that monads exist as myriads and that not all of them deal with side-effects. The famous Maybe monad is a nice counter-example (please avoid understanding the “Maybe monad” as something that is maybe a monad).

Second point: be aware that monads are not magical. All that can be done with monads can be done with plain functions in a pure functional style. Monads are like design-patterns in object-oriented programming, they are just ways to produce more readable code, at least for the lucky one who happens to master those.

Third and main point: whether you are a procedural or declarative programmer, functional or object oriented, using C++ or Haskell language, you may believe you are in control but in the end, it is the framework, the runtime, the environment, the operating system, no matter how you call it, that launches your nice little main function. Reactive! Like it or not!

int main(int argc, char *argv[]);

In the following posts, we will review the most significant functional reactive programming formulations and see how they match our simplicity and unanimity criteria: How did Functional Reactive Programming Originated?

Thanks
I would like to thank Agility without which I would have had to pursue my career as a project manager and stop programming.

About the author
Nicolas Roumiantzeff is a developer team member in one of 10 R&D scrum teams at Esker, a French SaaS company, where he designed and implemented several client-side JavaScript frameworks for highly customizable Web applications.
He likes music, JavaScript (he might already have told you that) and planet Earth.
His tech superheroes are:
Albert Einstein who showed that you could achieve astonishing findings with an extremely cheap experiment, long before the browser console is at your (F12) fingertips,
Andrew Wiles who showed that you could reach your most inconceivable dream even if your first attempt fails,
Alan Turing who showed that you could prove the unprovable,
Grigori Perelman who showed that being skilled in rocket science does not prevent you from being skilled in rocket lifestyle,
Brendan Eich who showed that such a big huge impact could come out of such a tiny little thing.

--

--

Nicolas Roumiantzeff
Esker-Labs

Nicolas Roumiantzeff is a developer team member in one of 10 R&D scrum teams at Esker a French SaaS company. He likes music, JavaScript and planet Earth.