The first book I have bought on JavaScript was unfortunately not for beginners.
The book is "the joy of JavaScript" from Luis Atencio. I am still trying to understand some concept in this book 2 years after…
There is a code I still don’t understand today. Can you lehp me to understand how is this possible?
const identity = a => a;
const fortyTwo = () => 42;
const notNull = a => a !== null;
const square = a => a ** 2;
const safeOperation = (operation, guard, recover) =>
input => guard(input, operation) || recover();
const onlyIf = validator => (input, operation) =>
validator(input) ? operation(input) : NaN;
const orElse = identity;
const safeSquare = safeOperation(
square,
onlyIf(notNull),
orElse(fortyTwo)
);
console.log( safeSquare(2) ); // 4
console.log( safeSquare(null) ); // 42
How is this possible to do safeSquare(2)
when safeSquare
is a function with 3 arguments (square
, onlyIf(null)
, orElse(fortyTwo)
). I think the 2 here is the input in safeOperation
, but how the 2 is passed in the safeOperation
function. I just don’t figure this out.
I know the basic of currying, but here I don’t get it.
2
Answers
A bit of theory
First, let’s talk about a bit of theory. Namely, what is beta reduction. Wikipedia has a lot of theory but the simple explanation is that you can replace a function with its application. For example if you have
the last part can be replaced with the function body:
The two pieces of code are equivalent. The name of beta reduction is less important than the principle here. The call of the function is replaceable with the body but with the parameter replaced.
This holds true even when the function returns another function:
This is a function that takes one parameter, returns a second function which takes another parameter. When the second function is called, then the final result is the first parameter
a
added to the second parameterb
.For clarity, that is the same as this longer way of writing the same:
However, if we apply beta reduction the same rules apply as before, calling
add(2)
can be replaced with the body wherea = 2
:Now we can look at how this can be used.
Now some practice
The principle of beta reduction can now inform how this code works:
The
safeOperation
function is the one that takes three parameters. Therefore, its call can be substituted with the bodyMaking
We can continue beta reducing the other functions. However, that is left for an exercise to the reader. The main point here is that when
safeOperation
is called with three parameters it returns a function with only one parameter. That one parameter function is assigned tosafeSquare
, which is whysafeSquare(2)
is then a legal call.Back to theory and nomenclature
First-class functions
The thing that enables this behaviour is called first-class functions (see also on Wikipedia). Simply put "first-class" means this is an type of value the programming language can handle directly – it can be assigned to variables, it can be passed into function calls, etc. Other first class types are strings or numbers, for example. We can do the same things with them:
When first-class functions are possible, we also get the following:
Higher order functions
These are functions that do at least one of: take a function as parameter or return a function as a result. These are the basis of writing reusable code where one part of the operation can change. Good examples where higher order functions are used are
Array#filter()
andArray#map()
(among other array methods). These two go over an array and do something with each member:.filter()
will include or exclude it, while.map()
will change it. The logic for going over the array is the same for all calls to.filter()
or.map()
but the way the item is checked is determined by the callback:Functional programming relies on higher order functions to build up applications and handle data with.
Currying
This is a special kind of a higher order function, not just any. Specifically a function that takes multiple parameters is converted to an equivalent series of functions that require one parameter each:
And so on. This type of conversion is useful when working mainly in functions. But I will not go too deep in it – it is just worth keeping in mind that not any higher order function is curried. Just if it is a series of one parameter functions.
Modern curry implementations are less rigorous than this, though. A modern way to curry will accept one or more parameters at each step until all expected parameters are satisfied. For example using
curry
from Ramda which converts a function to a curried variant:abstraction distraction
VLAZ has done a great job explaining the mechanics of the code and the background theory. I wanted to remark on the ergonomics of the original code and why it’s not a great example for beginners –
identity
– what is this function? Aside from being renamed toorElse
, it’s unused.fortyTwo
– some constant value, ok, but why is it inside a thunk?safeOperation
– unfamiliar pattern is justif..else
in disguiseonlyIf
– sometimes returnsNaN
, why?orElse
– a copy ofidentity
?I would like to demonstrate the same program but with different lines drawn in the sand –
It may not be a perfect demonstration that leaves no question in the learner’s mind, but hopefully the knowledge you acquired from VLAZ can help you see these abstractions as more intuitive and generally useful –
always
makes a function which returns a constant valueeq
is==
as a function, in curried formwhen
is the familiarif..else
as a functionidentity crisis
For those wondering about
identity
, it is the identity element in the function monoid, wherecompose
is the binary operation –The function monoid –
Upholds the monoid laws –
Maybe the text already covered all of this before presenting you with the code in your post. If not, we can see why including
identity
introduces more questions than it answers.