Ascending Functional Reactive Programming 2/7
Functional Programming to the Rescue?
After establishing my base camp, in the previous part of the series, I need to choose my route to the summit, the one to depart from reactive programming complexity. Functional programming is surely the right path, the one everybody talks about. Why is that?
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: this part in the series is large. I hope it will be large enough to convince the ignorant, the doubter, the unbeliever, the skeptic and the unfaithful! Hard work ahead…
Disclaimer: the term “functional” contains the word “function” and I suppose everybody is familiar with functions in the standard programming sense but functional programming goes far beyond programming with functions. This article describes the most commonly mentioned characteristics of functional programming…
1. Pure functions
In the functional programming world you hear a lot the expression “pure function” which means “function” in the mathematical sense by opposition to “function” in the standard programming sense. A tacky synonym of “pure function” is “referential transparency”.
1.1. Referential transparency means no side-effect
JavaScript impure function example:
function increment(){
count++; // side-effect
}
increment();
JavaScript pure function example:
var count = 0;
function increment(count){
return count + 1;
}
var incrementedCount = increment(counter);
1.2. Referential transparency means determinism
The result of a function only depends on the function argument (the so called “context-independence”).
1.3. Referential transparency means that a pure function call may be replaced by its cached value
The so-called “memoization” optimization.
1.4. A call to a pure function that does not return any value may simply be removed
Note that this does not really applies to JavaScript since a function always returns a value which by default is of type “undefined”.
Should I care or should I not?
Many bugs comes from side-effects getting out of hand.
These bugs are generally difficult to pin-point.
You want to avoid wasting your time and tearing your hair out on debugging these (unless your are a true masochist of course who you totally have the right to be, of course).
Context-independence simplifies unit-testing. No mock, no context initialization, no nothing. You only need to provide the arguments to the function to test and check its result (unless you are a true masochist of course who does not do unit-test, of course).
2. Function as first-class citizens
2.1. You can pass functions as arguments to another function (called a “higher-order” function)
JavaScript callback example:
function formatTitle(text){ // callback function
return text.toUpperCase();
}
function generateTitle(words, format){ // higher-order function
return format(words.join(" "));
}
var title = generateTitle(["Simple", "Unanimous", "Functional", "Reactive", "Programming"], formatTitle);
2.2. You can return a function as the result of a function (also called a “higher-order” function)
JavaScript curried function example (a curried function is a function with a single argument which is handy for function composition):
function sum(a, b){ // function taking two arguments
return a + b;
}
// curry (defined elsewhere) is a higher-order function
// returning a function
// increment is a function taking a
// single argument (curried function)
var increment = curry(sum, 1);
var count = 0;
var incrementedCount = increment(count); // 1
2.3. You can define functions dynamically
You can use “higher-order” functions or other means provided by your favorite programming language.
Example of implementation of the curry function seen earlier, using closure (an important aspect of functional programming that I will not explain here) and ES6 arrow and spread syntaxes (alternatives to older JavaScript syntax that I will not explain here either):
// returns a single-argument function from a multi-argument function
// the missing argument values are taken from the closure
function curry(multiArgumentFunction, …args){
return arg => multiArgumentFunction(arg, …args);
}
Should I care or should I not?
Actually, you do not need to care too much about this peculiar aspect of functional programming:
All modern languages (now) have support for most aspects of functions-as-fist-class-citizens.
Moreover, JavaScript masters this beautifully well indeed (Yes, I love JavaScript. So what?).
3. Declarative programming vs. procedural programming
In declarative programming you specify what you want whereas in procedural programming you specify how you get what you want.
The typical example is mapping an array:
// procedural programming
for(var i = 0; i < array.length; i++){
array[i] = array[i] * array[i];
} // declarative programming
array.map(function(item){
return item * item;
});
Should I care or should I not?
Array filter, map and reduce are quite fashionable these days. Are you sure you want to be left behind?
The declarative style is more concise, more expressive (focused on what is important), less error prone.
The procedural style uses a lot of side-effects (which you have already accepted to avoid after reading paragraph 2.1.).
4. Immutability
Functional programming is often associated with the concept of “immutability”. Immutability results from the concept of referential transparency seen at paragraph 2.1.
In functional programming, data structures are immutable. A pure function is not allowed to change the values contained in a data structure nor its internal references. Instead, the data structure is passed as argument to the pure function which returns an updated version of the data structure.
Should I care or should I not?
If you do functional programming, you will be confronted to immutability as it is the most noticeable practical difference between the functional paradigm and the procedural one.
JavaScript is a multi-paradigm language which supports declarative programming as well as procedural programming. Most built-in functions dealing with objects and arrays do not modify their arguments (map, filter, reduce, concat, slice, …) but some other do (sort, splice, push, pop, …). You better know which one to use. RTFM! In some cases you might need to copy the data structure first:
// slice() without argument copies the array so
// in-place sort() does not alter the original array
return array.slice().sort();
Immutability impacts performance. That much is true. Sometimes it goes unnoticed, sometimes it does not, for heavy processing on heavy data structure. Don’t get bitten. Be aware that in case you need it, medications exist, loads of them, either generic (techniques) or brand-named (libraries). Type “immutability libraries” in your favorite search engine and you will see what I mean by “loads of them“!
5. Function combinators
A function combinator is a higher-order function in which each argument is a function and the result is a function.
Combinators are a convenient way to combine functions into new functions. Here is an example of using the function composition combinator where increment and double are functions and compose is a combinator:
var doubleAndIncrement = compose(increment, double);
var x = doubleAndIncrement(10); // 21
A combinator may even generate combinators. Just for fun, check the famous (at least in the lambda calculus realm) recursive strict fixed-point Z combinator in JavaScript!
Should I care or should I not?
Working with higher-order functions is fun and fulfilling (I have no proof other than my own experience so try it for yourself).
Humanity level of consciousness is (slowly) rising and so is the level of abstraction in programming. That’s life!
6. Point-free style
Programming with the point-free style, you do not mention the elements of the function domain.
As this is not obvious to explain and to grasp, I will give an example about function composition.
// point-full style: x is an element of the g function domain
function FoG(x){ return f(g(x)); } // point-free style: using the compose function is a combinator,
// the x element is not needed
let FoG = compose(f, g);
Point-free style is related to function-level programming as opposed to value-level programming (the two programming paradigms identified by John Backus in 1977).
Point-free is also related to concatenative programming paradigm (typically stack-oriented programming languages using the reversed polish notation such as Forth, PostScript and Joy).
Should I care or should I not?
Point-free is not point-less. Stated like that, the content may not be very convincing but I hope the form will impress you!
Point-free style goes hand in hand with combinators and notably function composition.
7. Lambda calculus
The lambda calculus is a formalism invented by Alonzo Church in the 1930’s which may be considered as the gist of today’s functional programming.
It as been proved that the lambda calculus is Turing-complete which means that you can rewrite any procedural style program into a functional style program.
The term “combinator” used in this presentation is borrowed from the lambda calculus. Here is the definition of the strict fixed-point Z combinator, mentioned earlier:
λf.(λx.f(λy.x x y))(λx.f(λy. x x y))
Another important concept for functional programming, recursion, has also emerged from the lambda calculus.
There are many interesting formalisms derived from the original lambda calculus, my two favorite ones being the minimalist jot language and the pi calculus which formalizes asynchronicity.
Should I care or should I not?
If you are only interested in practical matters, I advise you to bypass the lambda calculus (a real world program written in this formalism would be too scary for humans).
You can see the term “lambda” everywhere nowadays in all major development environment. You know better.
When some expert tells you that a “lambda” means an “anonymous function” because of the French expression “individu lambda”, you know better (hijack of the caret ^ notation from Principia Mathematica).
It is important to know bird names!
8. Function type signature
The idea is to declare the type of each argument of a function as well as the type of its result.
Often, functional programming makes heavy use of function type signature.
Because of higher-order functions, function type signature may quickly become complex as the type of an argument or result may itself be a complex function type, including recursive types.
The most widespread formalism is the Hindley–Milner type system. As an example, here is the function type signature of the JavaScript reduce Array function (an arrow represents a function, letters represents generic types, the square brackets represents an array of a generic type and parentheses disambiguate precedence):
// reduce :: (b -> a -> b) -> b -> [a] → b
It reads: reduce is a function returning a value of type b and taking three arguments, the first argument is a function returning a value of type b and taking an argument of type b and an argument of type a, the second argument is a value of type b, the third argument is an array of type a.
Other formalisms exists such as Algebraic Data Types which allows recursive composition of types (a product of type is a tuple and a sum of types is a union in more classical typing system) and Generalized Algebraic Data Types (a function type signature is considered as a type function which may be applied to types to generate types).
Should I care or should I not?
Although producing, understanding and maintaining function type signatures takes time, patience and self-sacrifice, you will find (at least I did) the benefit far overtakes the pain as it helps reasoning at a higher level of abstraction.
Note that some functional language are loosely typed (JavaScript) or not typed at all (the original lambda calculus). You may however use type signatures (in code comments or on paper) to help your reasoning.
If you believe typed languages are superior to untyped languages (because it helps preventing bugs, I guess), think a minute about the following: the untyped lambda calculus is Turing-complete, the typed lambda calculus is not.
You wake up one morning and your lovely .js JavaScript files are all gone, replaced by those ugly .ts TypeScript files: do not panic, you are a type signature master!
9. Category theory
A major benefit with the formalism of pure functions is ability to apply mathematical proof on programs.
When it comes to functional programming, the commonly used theory is the category theory.
The category theory is altogether stated with very simple, very powerful and general concepts.
Should I care or should I not?
As already mentioned, array filter, map and reduce are quite fashionable these days. All of these and more are part of the category theory.
You may have heard about Monads which is THE buzz-word associated to category theory in relation to functional programming (in the next post I will talk more about Monads in a paragraph about the functional programming curse).
Be careful when deep-diving into category theory esoteric concepts:
A monad in a category X is a monoid object in the monoidal category of endofunctors of X with the monoidal structure given by composition.
My quest continues with the next post in the series, Why is Functional Programming so Controversial?
Thanks
I would like to thank my wife Sophie for her support during my quest and for sharing me with JavaScript.
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.