Summary :
Take a few higher-order functions, add partial application of functions, spice up fold with a map, and get Javascript DSL to work with the DOM.
')
Human language :
A simple and intuitive introduction to functional programming in pure and clear Javascript.
Unlike
"Through thorns to Haskell" everything is chewed (perhaps even too much) and laid out on the shelves.
Reading the article will unmask the myth of the inapplicability of AF in real life. You will be able to look at the solution of the same problem from different points of view. Just like in the picture.
Functions
Let's start with a simple definition of a function.
function add(a,b){ console.log(a+b); };
You can write the same code differently.
var add = function(a,b){ console.log(a + b); };
One of the great advantages of Javascript is that the functions in it are full-fledged objects. Real First Class Citizen.
In contrast, for example, from Java, where the function cannot exist separately from the object.
The above function works with side effects, that is, changes the state of the external world. This is reflected in the use of
console.log () .
Now consider an example of a pure function.
var add = function(a,b){ return a + b; };
Pure functions do not produce side effects. You transfer some data to them, and they give you data back. They are very easy to analyze. They are easier to test. No need to check external dependencies. Therefore, in most cases, pure functions are preferable to functions with side effects.
But, on the other hand, a program consisting solely of pure functions does not have practical meaning. It does not read or display anything.
Therefore, it will be logical to write programs in such a way as to separate the pure functions from the functions with side effects and thus simplify your life.
So, the first rule of functional programming is to use pure functions.
Higher order functions
We go further. Since functions are objects, we can transfer them to other functions. And higher-order functions are functions that return functions, or that take functions as parameters.
Here is a simple example of a function that also returns a function.
function makeAdder(base){ return function(num){ return base + num; } }
And an example of its use.
var add2 = makeAdder(2); add2(3);
Simple and obvious.
But a fairly well-known example of a higher order function.
var el = document.getElementById("btn"); el.addEventListener("click", function (event){ });
addEventListener takes a function as a parameter. That is,
addEventListener is a higher order function.
And the handler function will be called when an event occurs.
Perhaps you are more familiar with another option:
$("input[type=submit]").on("clink", function(event){
Or another thousand and one ways that jQuery allows you to describe handlers.
So once again the definition:
FVPs are functions that either return functions or accept functions as parameters.
Cycles
Old acquaintances.
By cycle we mean a standard frontal solution. About
for(var i =0; i<n; ++1){
Or so
while(n--){
Why do we use cycles? Let's look at a few standard use cases and see that cycles are not always the best solution.
The first option is bypassing arrays and lists.
for(var i =0; l< arr.length; i<l; ++i){ console.log(arr[i]); }
Typically, this bypass is used in conjunction with side effects. And usually these effects are a little more useful than a simple output to the console.
The second option - pulling data from the lists
var names = []; for (var i =0; l= tweeps.length; i< l; ++i) { names.push(tweeps[i].name); }
In this case - a list of Twitter users.
Using a cycle, we get a list of the names of our users.
Another use case is data aggregation in the list:
var html = ""; for(var i =0; l = items.length, i<l, i++){ html += '<li>' + items[i] + '</li>'; } this.list.innerHTML = html;
That is, we aggregate the data in the list, and get another data structure at the output.
foreach
I said that cycles are not always the best solution, but what alternatives are there at all?
What can replace such a cycle?
for (var i =1; l = arr.length; i< l; ++i){ console.log(arr[i]); }
For example
foreach .
Array.prototype.forEach arr.forEach(function(item){ console.log(item); });
Instead of running through the list with our hands, we can use
array method. Let's transfer there the function processing each element and we will receive the necessary result.
But what is the
fundamental difference between this
for (var i =1; l = arr.length; i< l; ++i){ console.log(arr[i]); }
and by this
arr.forEach(function(item){ console.log(item); });
?
Unfortunately, the syntax for describing functions in JS is quite verbose, so we did not receive significant savings in the amount of written text.
But there is something else. Looking at the code, we can say what attention is paid to in each of the implementations.
The first section is focused on the very mechanics of the cycle. Take a number, increment it by one, get an array element by index, perform an action.
The second example is much easier to understand. We do something with each item in the list.
In the second example, the level of abstraction is much higher. And it allows you to approach the solution of the problem from the other side.
So, for what we can use cycles:
- Side effects
- Transformation
- Filters
- Combination of elements
- Another bunch of options
map
Let's look at another function that is in Javascript.
var names = []; for( var i =0, l= tweeps.length, i< l; ++i){ names.push(tweeps[i].name); }
This is an abstraction that corresponds to the transformation list.
Using
map we can solve this problem much easier.
We got rid of temporary variables, from the description of the cycle. Direct and understandable code. And since the processing function is quite short, we can fit everything in one line.
var names = tweeps.map(function(t){return t.name;});
I am not a fan of writing a single-line code. But how many ideas can be expressed in one line indicates the expressiveness of your API.
Now look for the mention on Twitter.
var str = "mentioned by"; for(var i =0; l= tweeps.length; i < l; ++i){ str += tweeps[i].name; if(i< tweeps.length-1) {str += ", "} }
A rather clumsy example. There may be a bunch of errors with indexing and getting an array element.
Let's analyze what we are really doing in this example:
- We take out user names
- We combine user names (at the end of the list there should not be a comma)
- Use comma as a separator
Rewrite using
map and
join var str = "mentioned by " + tweeps.map(function(t){ return t.name; }).join(", ");
Opportunities to make mistakes became much less.
But can it be done better? :)
Let's introduce another higher order function that we will use to access the properties of objects.
Let's call her
prop function prop(name){ return function (object){ return object[name]; } }
At first glance, it is quite meaningless. We give her a name,
and it returns to us the function where the object is transferred from which we pull out the required field.
Some confusing explanation came out. Let's just try to use this function on a real task.
var str = "Mentioned by " + tweeps.map(prop ("name")).join(", ");
So, another one-liner. Pretty good expressiveness. And the
prop function is not so useless.
reduce
This is a great-grandmother for for, foreach, while and other similar structures. This function is also known as
fold .
Again, let's start with a primitive example.
var totalLength = 0; for(var i=0; i< buffers.length; i++){ total.Length += buffers[i].length; }
Just summarize the length of the buffers.
What steps should we take?
- Get buffer lengths
- Sum lengths
Use the function, Luke.
First we use
map to get a list containing buffer lengths.
var totalLength = buffers. map(function (buffer) {return buffer.length; })
And in the second step, we use
reduce to get their sum.
var totalLength = buffers. map(function (buffer) {return buffer.length; }). reduce(function(sum, curr){return sum+curr;}, 0);
If you are not familiar with reduce, then it works very simply. It transfers the battery function, which will be applied to each element and the initial value for the battery function.
Somehow too difficult again. Let's just see what happens if we apply
reduce to a simple list.
[10, 5, 15, 10, 10].reduce(function(sum, curr){return sum+curr;}, 0);
So with the help of reduce we can easily sum up the elements of the list.
But we already had something similar. Compare.
function (prev, curr){return prev + curr;}
and
function add(a,b){ return a+b; }
Therefore, we can slightly simplify our function of calculating the total length of buffers.
var totalLength = buffers. map(function (buffer) {return buffer.length; }). reduce(add, 0);
Now it became clearer?
reduce simply adds all the elements of the list using the
add function. The initial value of the sum is zero. What could be easier?
But this simplification does not end there. Compare
function (buffer) {return buffer.length; }
and
prop("length")
Pants are turning ...
var totalLength = buffers. map(prop("length")). reduce(add, 0);
In elegant shorts.
Well and, naturally, we can write it in one line.
var totalLength = buffers.map(prop("length")).reduce(add, 0);
Using convolution (reduce) instead of cycles allows us to think at a different level of abstraction. We perform operations on the list, and not at the level of each element.
Asynchronous calls
But using
reduce aka fold to sum lists is a very simplified example. The idea is much more powerful. Let's take another example.
One of the problems with using Javascript in the browser is that everything runs in one thread, and therefore we must use callbacks.
Task.
- Download multiple scripts
- Glue them
- preserve the order of scripts when gluing
That is, you need to write a function like this:
combine(["/jquery.js", "/underscore.js", "/backbone.js"], function(content){
Let's write the implementation of the
combine function. First - the frontal approach.
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){
To get scripts, it would be logical to use jQuery.ajax:
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ jQuery.ajax({ url: scripts[i], success : function(response){
Such code will not slow down the browser, since requests to the server will be sent asynchronously. That is, there will be 3 parallel queries during execution.
Let's write a handler for successful script download.
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ jQuery.ajax({ url: scripts[i], success : function(response){ data[i] = response; if(data.length === scripts.length){ callback(data.join("")); } } }); } }
It seems to be a function ready. But there are two but.
Firstly, it is ugly, secondly - it will not work.
What could be the problem? Javascript scopes. In this language, the area of visibility is not block by block, but functional. that is, all 3 functions will see the same value of the variable i. Since the cycle will work before the responses from the server arrive, all three functions will work with i == 3;
This problem is solved in the standard way - we cache the value of the loop variable. But it cannot be said that the code from this has become more beautiful.
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ (function (i){ jQuery.ajax({ url: scripts[i], success : function(response){ data[i] = response; if(data.length === scripts.length){ callback(data.join("")); } } }); }(i)); } }
Almost even works. In order to get rid of closures and tricky variables, you can use foreach
function combine(scripts, callback){ var data []; scripts.forEach(function(script,i){ jQuery.ajax({ url: scripts[i], success : function(response){ data[i] = response; if(data.length === scripts.length){ callback(data.join("")); } } }); }); } }
Better, of course, but still scary. By the way, the code will still not work correctly. It can be finished to working condition, but this will create additional difficulties in the development and subsequent support.
Continuation Passing Style
To get rid of headaches, use the library.
github.com/caolan/asyncFor work we will use such a thing as CPS.
It sounds much worse than it actually is. This is a function that takes another function as a parameter, and when the first function completes, it calls the parameter function instead of retrun.
Wrap jQuery.ajax in such a way as to get the desired result.
function ajax(url, callback){ jQuery.ajax({url: url, success: callback}); }
The function receives callback as a parameter, and we have not described an error handler. In the real code, it must be, but for simplicity, we will forget about it.
What happens if you use the async library? It turns out something like this:
function combine(scripts, callback){ async.map(scripts, ajax, function(contents){ callback(contents.join("")); }); }
We have a ready-made map function that works in an asynchronous world. By the way, the current implementation will ensure the correct order of gluing the scripts, unlike our frontal example.
Compare with what was:
function combine(scripts, callback){ var data []; for(var i =0; l = scripts.length; i< l; ++i){ (function (i){ jQuery.ajax({ url: scripts[i], success : function(response){ data[i] = response; if(data.length === scripts.length){ callback(data.join("")); } } }); }(i)); } }
Since map is already a natural way of writing programs for me, I would never write the code above. I would think how to adapt a map to an asynchronous environment. And if there were no async library, I would have written an asynchronous map myself.
The functional approach makes it much easier to look at things. And implement more beautiful solutions.
Partial application of functions
Another idea that came from functional programming, and can be very useful if you know how to prepare it correctly.
As an example, we will create DOM elements.
(Approx. Translator: cull.dom - library, author, which he created for one of the projects. But the functions in it are obvious and simple.)
var ul = cull.dom.el("ul");
You can also set property attributes.
var ul = cull.dom.el("ul", {className: "bands"});
And specify child elements
var li = cull.dom.el("li", "Tom Waits"); var ul = cull.dom.el("ul", {className: "bands"}, li);
If you use them inside each other, you can get some kind of DSL for HTML.
va ul = cull.dom.el("ul", className:"bands"}, cull.dom.el("li", "Tom Waits"));
And now let's get down to the discussion of the partial application of functions. Remember one of the first examples?
function makeAdder(base){ return function(num){ return base + num; } }
It returns a function that will add two numbers. Naturally, if necessary, we can use named functions.
function makeAdder(base){ return function(num){ return add(base, num); } }
And now we see that the function
makeAdder takes the function
add and fixes one of its arguments. You get an addition function, in which one of the arguments is a constant
var add2 = cull.partial(add, 2); add2(5);
Now we have a rather interesting opportunity - to make our DSL for creating DOM elements even more beautiful.
var ul = cull.partial(cull.dom.el, "ul"); var li = cull.partial(cull.dom.el, "li");
And we can build HTML lists like this
var list = ul({className: "bands"}, [li("Diamanda Galas"), li(" "), li("John Zorn")]);
If you, like me, don't like programming at the level of string variables, this is a great way to simplify your life. You will now have auto-completion code, and other nice things. And your code is very similar to plain HTML.
And since our approach is quite beautiful, we can create functions for all elements of the document in advance:
["a", "br", "code", "div", ...].forEach(function(tagName){ cull.dom.el[tagName] = cull.partial(cull.dom.el, tagName); });
Thus, we will create a function for each HTML element.
Of course, the namespace is not always convenient to use completely, so we will simplify even further.
var e = cull.dom.el; var list = ul({className: "bands"}, [e.li("Pan Sonic"), e.li(" "), e.li("Muslimgauze")]);
Now we are not tied to global variables and functions, which is good.
Function composition
Here is another example of a simple questionnaire application.

You must answer each block. Each block contains several questions. After answering one block, we proceed to the next.
Each block can be thought of as a panel, which can be in query mode, result mode, or inactive.

Each panel can have different fields. String, numeric, dates.
Fields can be in two modes - editing or result.
We will see how to approach this problem using a functional approach.
Remember our favorite
prop function?
tweeps.map(prop("name"));
She has a twin brother
func .
tweeps.map(func("to.String"));
It returns a function that you can apply to objects.
Now we calculate the result of each block in the questionnaire
buildSummary: function(){ return div(this.components.map(func("buildSummary"))); }
The principle should be obvious. We return a div in which there will be elements created by the
buildSummary function for each block of the questionnaire.
In this example, each component itself knows how to present its result. But sometimes the panel should display the result in a specific way.
Therefore, we can write 2 functions:
buildSummary and
getSummary .
The first is building a complete presentation, including html tags.
The second returns an object that contains the required results.
And as soon as we needed clever processing of the results, all the beauty began to crumble.
buildSummary: function(){ var div = document.createElement("div"); for(var i =0; l=this.components.length; i<l; ++i) { p = document.CreateElement("p"); p.innerHTML = this.components[i].getSummary().text; div.appendChild(p); } return div; }
However, we are already functionally oriented enough to improve this piece of code. The first obvious improvement is to use foreach.
buildSummary : function(){ var div = document.createElement("div"); this.components.forEach(function(component){ var p = document.createElement("p"); p.innerHTML = component.getSummary().text; div.appendChild(p); }); return div; }
We got rid of loop variables, but is it possible to use
map ?
buildSummary : function(){ return div(this.components.map(function(component){ var p = document.createElement("p"); p.innerHTML = component.getSummary().text; return p; })); }
Short, but far from ideal. The main problem in this expression:
component.getSummary().text;
The problem is, not one thing happens here, but three things:
- Getting the result via getSummary ()
- Getting the text property
- Wrapping the result in the p tag
What about several map functions?
buildSummary: function() { return div(this.components. map(function(component){ return component.getSummary(); }).map(function(summary){ return summary.text; }).map(function(text){ var p = document.createElement("p"); p.innerHTML = text; return p; })); }
The functional style is obvious, but it looks scary. And it is very inconvenient to read.
But let's look at the code once more. What do we have here?
return component.getSummary();
Here we call the object method. But we created a special function for this,
func .
buildSummary: function() { return div(this.components. map(func("getSummary")). map(function(summary){ return summary.text; }).map(function(text){ var p = document.createElement("p"); p.innerHTML = text; return p; })); }
And here?
function(summary){ return summary.text; }
We get access to the property of the object. And for this, too, there is a convenient function.
buildSummary: function() { return div(this.components. map(func("getSummary")). map(prop("text")). map(function(text){ var p = document.createElement("p"); p.innerHTML = text; return p; })); }
Remained last site.
function(text){ var p = document.createElement("p"); p.innerHTML = text; return p; }
Here we create a DOM element and set its internal property. We have something similar in our DSL, isn't it?
buildSummary: function() { return div(this.components. map(func("getSummary")). map(prop("text")). map(p)); }
It's almost beautiful now. But there is one nuance. We make 3 passes through the list. In some cases this may be normal, but in general it is somewhat suboptimal. What can be done?
It's time to use composition of functions. We want to make one function do what the three do.
var summarize = compose( [p, prop("text"), func("getSummary")]);
How do we implement compose?
Piecemeal. To begin, create synonyms to not write a lot of code.
var callGetSummary = func("getSummary"); var getText = prop("text"); var summarize = compose([p, getText, callGetSummary]);
Everything is simple and obvious. We go further. Let us see what happens when we call the
summarize function.
Step one
var callGetSummary = func("getSummary"); var getText = prop("text"); var summarize = compose([p, getText, callGetSummary]);
The object is transferred to the last function from the list, namely
getSummary . It returns an object of type
summary . And this object is passed to the next function,
getText.Step Two
var callGetSummary = func("getSummary"); var getText = prop("text"); var summarize = compose([p, getText, callGetSummary]);
As a result of the second step, we get the string that is contained in the
text property. And after that the line will fall into the function that will create a DOM object
p for us .
Step Three
var callGetSummary = func("getSummary"); var getText = prop("text"); var summarize = compose([p, getText, callGetSummary]);
This is an example of a simple composition, when a parameter is passed from a function to a function sequentially. You can create a composition, when the parameter will be passed to each function, and the output will be a list of results. Or something else.So back to our long-suffering example. builSummary: function() { var summarize = compose( [p, prop("text"), func("getSummary")]); return div(this.components.map(summarize)); }
First we created a function to calculate the results. And then applied the map.Note that the summarize function does not know at all what object it works with. These are three different abstractions that are connected exclusively by the compose function. Therefore, we can take summarize to a separate entity. var summarize = compose( [p, prop("text"), func("getSummary")]);
It looks cool and beautiful, but what about performance?Performance issues
for - 5M operations per secondforEach - 1.5M operations per secondreduce - 1.5M operations per secondWorking with DOM - 50K operations per secondSo you should not worry about the functional approach, but about the brakes of working with DOM. Of course, it all depends on your task, so if in doubt, take measurements. Especially on mobile devices.Conclusion
We use pure functions.We use higher order functions (map, reduce).We use small abstractions.Many small abstractions can be easily assembled into one big powerful thing.PS You can watch slides of the original performance at cjohansen.no/talks/2012/javazonePPS And why isn’t there a functional programming programming hub?