Ascending Functional Reactive Programming 7/7

Nicolas Roumiantzeff
Esker-Labs
Published in
11 min readMar 15, 2021
Original photo by Simon

Which Functional Reactive Programming is the Grail?

What a journey! It has been more than a year of exhausting yet fascinating work. We started by thoroughly exploring the pros and cons of reactive programming and functional programming. This led us to the strong conviction that the combination of the two, functional reactive programming, could be the Grail all Web developers are desperately looking for. After reviewing functional reactive programming frameworks, both academic and professional, from simplicity and unanimity perspectives, we may finally answer the crucial question: which functional reactive programming is the Grail?

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?

To conclude this series on functional reactive programming, the first section will comment on the results of the survey from the two previous articles. The second section will list the lessons learned during the whole series. The third and final section will go beyond known frontiers and explore strange new worlds… where no man has gone before.

1. Who’s the SURFest of Them All?

Let us comment on the results of the surveys (academic works and professional frameworks) for each of our four criteria: Simple, Unanimous, Reactive, Functional (SURF).

1.1. Simple

As shown in the above graph, Svelte and RxJS/Cycle have the highest score for the Simple criteria: 8/10.

Reminder: RxJS is a JavaScript implementation of a Stream library and Cycle is a JavaScript framework based on Streams.

Note that none gets the maximum. This corroborates the fact that Web development is definitely not simple.

Personally, I like the simplicity of Stream marble diagrams and Cycle Model-View-Intent.

1.2. Unanimous

Again RxJS/Cycle wins, this time for the Unanimous criteria: 8/10.

Again no one gets the maximum, reflecting the numerous controversies already mentioned in this series.

The main takeaway for me is that unlike Promises, Streams are not prone to much criticism.

1.3. Reactive

For the Reactive criteria, RxJS/Cycle shares the throne with ELM: 10/10. Bravo!

ELM is a Web oriented pure functional language that compiles to JavaScript with no runtime exceptions.

I would like to stress out that Streams follow the observer pattern: the source of an event is not directly responsible for updating the observer of the event.

What I like best about ELM is that it pioneered the unidirectional data-flow architecture.

1.4. Functional

Fran and AFRP both get the highest mark for the Functional criteria: 10/10.

Reminder: Fran initiated functional reactive programming with functions of time (Signals) and AFRP enhanced this by using functions from Signal to Signal (Signal functions).

It is not surprising that the winners are both academic works, implemented in the pure functional Haskell language. While professionals need to get things done in a pragmatic way, academic works require formal proofs and functional programming is perfect to reason about.

It is worth mentioning that both winners of the Functional criteria have mediocre scores for all other criteria. Consequently, they score poorly on the SURF total: 25/40 and 24/40 respectively. Fran and AFPR, both focused on Signals more than Events, are not mainstream for Web application development which focuses on Events almost exclusively.

Nevertheless, it is important to me that the frameworks I use at work have a strong theoretical background.

1.5. SURF (Simple, Unanimous, Reactive, Functional)

When it comes to the SURF total, the sum of the four individual criteria, the champion is RxJS/Cycle: 34/40.

Logically, RxJS/Cycle gets rewarded thanks to its leadership for three out of four criteria: Simple 8/10, Unanimous 8/10 and Reactive 10/10. RxJS/Cycle also gets a good score on the fourth criteria even though 8/10 is not high enough to win the Functional criteria.

I must say that streams provided by libraries such as RxJS are quite impressive. Still, I perceive their relative weakness on the Functional criteria as an Achilles heel. Moreover, we must be aware that streams by themselves do not make a complete framework. On the other hand, Cycle is a true stream-based JavaScript framework and although it is less popular than RxJS, it might be worth giving it a try.

2. SURF Lessons Learned

RxJS/Cycle is clearly the SURF champion. Is it the Grail I have been seeking for more than a year? I am left with mixed feelings: dissatisfaction about what I found and fulfillment thanks to what I learned.

Without lengthy explanations, here is a non-exhaustive list of the lessons I learned about functional reactive programming for the Web:

🙁 The Grail
Sadly, I came up to the conclusion that the Grail I have been looking for does not exist and never will. Everyone has their strong personal opinions and preferences. On top of that, people tend to throw away things and come up with brand new ones.

🙁 React
After scrutiny, React is a big disappointment to me, especially since it was the very framework that motivated me to start my quest. Lesson learned: “popular” does not mean “unanimous”. Good job, Facebook’s marketing!

🙂 Streams
When I started my quest, I did not know about streams. It was a fascinating encounter, yet, after some consideration, I found two limitations: streams are not a full framework and are not stateless.

🙁 Life cycle
When I hear “life cycle”, I hear “not functional”. Things like “mount/unmount” (class-based React) or “unsubscribe” (RxJS) feel procedural rather that declarative.

🙁 Architecture
More important than choosing a good framework is choosing the right architecture. Although a brilliant idea, the ELM & React+Redux architectures are not entirely satisfying after all.

🙂 SSR (Server Side Rendering)
SSR is gaining popularity. I guess some people cannot stand the severe issues in front-end frameworks any longer. Generating HTML pages on the server (instead of generating the DOM by JavaScript in the browser) is nice as long as these pages remain reactive without round-trips to the server.

😮 Performance
There are numerous performance benchmarks comparing frameworks on thousands of DOM updates. They are mostly relevant for page loading time in case the page is dynamically generated through JavaScript on the client side. For me, what impacts performance the most is dynamic bindings between the DOM and the JavaScript data model, notably, adding event handlers on individual HTML elements.

🙁 Web components
Web components seem cool to abstract the DOM but there are two things I dislike. First, custom elements require JavaScript object classes (too bad for functional programming). Second, each custom element and shadow DOM require some JavaScript to be executed (too bad for declarative pages and SSR). HTML templates seem more functional programming friendly.

🙁 HTML element Ids
Some frameworks, in order to avoid mixing JavaScript event handlers and HTML elements, rely on HTML Ids to implement their bindings. Unfortunately, pure functional programming makes it very tricky to deal with the uniqueness of these Ids.

🙂 Transpilers
Following the success of TypeScript, I think more and more Web frameworks will use specific languages requiring a JavaScript transpilation. ELM and Svelte are noticeable examples. One can do anything with plain JavaScript but maybe not the way one wants. Let me tell you that if the multiplicity of transpilers eventually wrecks JavaScript, I will go down with this ship!

😮 Event handlers
Event programming is not Reactive programming. Event handlers should only notify of changes and not trigger procedural updates. The click event gave me hard times (because I could not figure out what changed) then I realized that a click event is actually a combo of a mousedown event (the button under the mouse changes from unpressed to pressed) and a mouseup event (the button under the mouse changes from pressed to depressed).

🙁 Declarative programming
Functional programming is about using pure functions but also about declarative programming vs procedural programming. While the explanations I found on pure functions were straight forward, the concept of declarative programming has always felt subjective to me. Therefore, I had to come up with my own definition: declarative programming is point-free programming, that is combining functions without explicitly using the arguments nor the returned value (function composition being the typical example). Note that I hardly adhere to definitions of declarative style as being more readable than procedural style. Personally, I often find easier to understand thanks to an example rather than a theoretic explanation.

😮 Observable side-effects
In functional programming, purity means no side-effect and context independence. In functional reactive programming, purity means no observable side-effect: side effects may be applied in a way not breaking the context independence (the read side-effects are isolated from the write side-effects by the framework in separate time-frames).

🙁 Promises
JavaScript Promises look like Monads a lot, the “then” operator being the equivalent of the “bind” operator. Yet, Promises lack the main benefit of Monads: functional purity (no observable side-effects).

😮 Category theory
Monads come from the category theory which is one of the most unexpected encounter that occurred during my quest for functional reactive programming. I would love to thoroughly explore this field but this would require a full-time dedication. Give up programming? No way!

3. Could we SURF beyond the SURFers?

I have not found the Grail but instead learned what is really critical for a Simple Unanimous Reactive Functional framework. So what would the Grail look like if it existed? I have scratched the surface of two areas dear to my heart and here is what I came up with.

Disclaimer: in this last section we will be leaving the troposphere, so if you feel acute mountain sickness symptoms or suffer from vertigo, I suggest you skip directly to the “thanks” paragraph at the end of the article.

3.1. Beyond Monads: Effectuality

Issued from the category theory, Monads are quite fascinating. Monads are parametric types able to abstract any kind of side-effects in a functionally pure way. For functional reactive programming in the context of Web applications, we will focus on the IO Monad (to read from or write to an HTML input element) and the Task Monad (to perform an asynchronous Ajax request for example). Applying computations on Monads is achieved by the bind operator as so (here, using the Hindley-Milner type signatures):

// M is a Monad, t and u are type parameters
// bind: M t → (t → M u) → M u

Unfortunately, Monads have several limitations which lead to devise an alternative: Effectuality.

The first limitation of Monads is the inability/difficulty to mix different kind of Monads (for example the IO Monad and the Task Monad). Note the plural on “Monads“ whereas ”Effectuality“ is singular, meaning that all the kinds of side-effects may be abstracted with the one and only Effectuality parametric type.

A Monad may abstract a value and computations on that value but these computations have certain limitations. For example, a Monad cannot represent a recursive computation. The Hughes Arrow is a generalization of Monads, used to abstract any computation. The same is true for the Effectuality which generalizes the Hughes Arrow. In addition to side-effects, the Effectuality abstracts recurrence, concurrency and classical functional reactive programming patterns such as observers, streams and signals.

Lastly, Monad composition is not straightforward. Conversely, Hughes Arrow composes nicely by design using its arr operator:

// t, u and v are type parameters
// arr: Arrow t u → Arrow u v → Arrow t v

The Effectuality also composes nicely. It goes even further since the Effectuality has no operator at all (neither the arr operator, the first operator nor any other Hughes Arrow operator):

// t, u and v are type parameters
// Effectuality ((v -> ()) -> (u -> ())):
// Effectuality ((u -> ()) -> (t -> ()))
// -> Effectuality ((v -> ()) -> (t -> ()))

The Effectuality has some interesting singularities:

  • Effectuality is a functional object (a recursive function representing a parametric type).
  • “t”, “u” and “v” are multi-valued types, following the stack-based programming paradigm
  • t → ()”, “u → ()” and “v → ()” functions have the same signature as standard callback functions
  • The parametric type of the Effectuality is a third order function: a function from a callback to a callback
  • Composition goes backwards: the cause is inferred from the effect
  • Syntactically, composition is a concatenation while at the JavaScript language level it is a function application

Here is a JavaScript example of an Effectuality composition:

// the output of a 3 second delay square of the input
(output)(square)(delay)(input)

Follow this link to see this example in action plus some implementation snippets and this link to see more examples

Should I care or should I not?
Theoretically, any web-application, even a complex one, could be modeled by a recursive Effectuality. Practically, this would be unmanageable. What we want is to organize the implementation by separating the pure computations from the side-effects. After all, no program causes by itself any side-effect since a program is a text string (interpreted) or a byte stream (compiled). The side-effects are caused by the operating system/interpreter/browser running the program. In addition to Effectuality, a suitable architecture is needed!

3.2. Beyond the Model-View-Update Architecture: the SURF Architecture

Both ELM and React+Redux follow the Model-View-Update architecture in which the View (the HTML user interface) is a function of the Model (sometimes called State). In these architectures, the Model is the “single source of truth”.

// ELM and React+Redux architectures
UI = f(State) // f is a pure function

This idea (also referred to as “unidirectional data flow”) has been a total revolution among the Web development community, then committed to the “two way binding” pattern. It also gave an incredible boost to functional programming in a world of object-oriented programmers.

Despite the beauty, simplicity and benefits of this architecture, we have seen in the previous article some drawbacks and criticisms. Therefore, I had to search for an alternative architecture, without throwing out the baby with the bathwater: the SURF architecture.

// SURF architecture
State = f(UI) // f is a pure function

The SURF architecture turns things upside down: the State is a function of the user interface. The user interface becomes the “single source of truth”.

Here are some other noticeable characteristics of the SURF architecture:

The State may be considered as an abstract DOM (whereas ELM, React+Redux and the like rely on a virtual DOM). The State does not need to contain (nor maintain) all the details of the user interface (for example the position of the cursor inside an edit box).

The level of granularity in the SURF architecture is the component level. A component corresponds to an HTML element and may contain attributes, child elements as well as sub-components. The SURF architecture is fractal as well as unidirectional (see previous article).

Typically, a component has no HTML id, no JavaScript node and no event handler. A component is pure HTML. A Web page containing SURF components may either be generated server-side (SSR) or client-side (front-end).

Each component has a type. Component types are registered in a declarative way with the necessary information to make components reactive: description, states, properties, events, subcomponents, effects, presence, styles.

  • The description is a function returning a JavaScript object describing the HTML of the component and its sub-components.
  • The states are pure functions taking as argument an abstraction of the HTML element corresponding to the component.
  • The properties correspond to values that may be specified by the parent component (React props alike).
  • The events declare the HTML events that notify changes to the component.
  • The subcomponents specify the values of the sub-component properties depending on the values of the states of the component and sub-components.
  • The effects specify the side-effects (asynchronous Ajax requests for example) as Effectualities (see chapter 3.1).
  • The presence function specifies the validity of the component (invalid components are automatically removed).
  • The styles are CSS styles to be applied on the component.

Follow this link to see some examples

Should I care or should I not?
Successfully completing a Web development (even a toy demo) using pure declarative functional reactive is really delightful. The result might look rock solid and beautifully simple, however, the development and maintenance could cost a good deal of sweat and tears. Are we really ready for that?

Thanks
I would like to thank the readers of my articles, mostly those who read the whole series, if any. I would also like to thank all the contributors who post articles to the web, providing food for thought to all of us, especially me.

Farewell and Happy Web SURFing! 🎵

--

--

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.