This blog post has been submitted for reddit discussion.
I like using array iterators. For example to multiply every number by 2 into a new array I would write
1 | [1, 2, 3].map(function (x) { |
Because I love functional programming and readability, I will reuse the function that multiplies given argument by 2
1 | function double(x) { |
Notice that argument x is passed directly from iterator callback function to double function.
We can shorten this to:
1 | function double(x) { |
We just eliminated 1 function and most importantly 1 variable. Method Array.prototype.map calls double
directly, passing each item, index and array directly without going through the intermediate dummy function.
This elimination of unnecessary variables that are just passed from one function to another is called point-free programming 1. It eliminates unnecessary code, leading to smaller and simpler code. I also believe that it leads to more robust code by eliminating possible sources of misspelled variable names.
What if our callback function does not match exactly the signature of the array iterator function? For example, what if instead of multiplying (associative) by a number, what if we wanted to divide by a constant?
function divideBy3(x) {
return x / 3;
}
[3, 6, 9].map(divideBy3);
// [1, 2, 3]
Why do we need to explicitly define double and divideBy3 functions? We could construct double
on the fly by partially applying first argument
function mul(a, b) {
return a * b;
}
[1, 2, 3].map(mul.bind(null, 2));
// [2, 4, 6]
Using mul.bind(null, 2) gives a function that calls mull(2, _) where the first argument is applied and
will have value 2. The second argument remains free and will be provided by the array iterator.
The same approach does not work with division by 3, because we need to apply second argument!
function div(a, b) {
return a / b;
}
[3, 6, 9].map(div.bind(null, 3));
// [1, 0.5, 0.3333]
Instead of getting partially applied function that divides by 3, we got a function that divides 3 div(3, _).
Of course, we could write a partial right function, or use _.partialRight. Sometimes though
the argument we want to bind is the middle one!
To avoid this problem and allow convenient point-free programming style in JavaScript I wrote spots. It only has single function that partially applies a given function, and leaves spots unbound. Just use the reference to the function itself as a placeholder.
var S = require('spots');
[3, 6, 9].map(S(div, S, 3));
// [1, 2, 3]
Expression S(div, S, 3) returns new function that will call div(_, 3). Thus
S(div, S, 3)(6) is the same as div(6, 3).
Spots is one of the libraries that allow selective partial application.
parseInt
The point-free style allows for quick solution to another curious problem described in The Madness of King JavaScript.
['1', '2', '3'].map(parseFloat); // [1, 2, 3]
['1', '2', '3'].map(parseInt); // [1, NaN, NaN]
This is due to Array.prototype.map passing 3 arguments to the callback function: the item,
the index (starting with 0) and the reference to the array itself. Our previous functions mul and
div ignored second and third arguments. Function parseFloat also only needs first argument,
but function parseInt expects two arguments: a string and a radix. If radix is 0, base 10 is assumed.
Thus the first pass of the map calls parseInt('1', 0) producing 1, but the second
pass calls parseInt('2', 1) which is impossible, producing NaN.
Typically I use unary function adaptor to avoid this problem, or Ramda.map
iterator that does NOT pass index. Using spots allows me to avoid the problem by placing placeholder
['1', '2', '3'].map(S(parseInt, S, 10));
// [1, 2, 3]
Pipelining
If you use spots with my functional-pipeline library you can write even more complex expressions, that leave placeholders that will be filled, causing other functions to be called, etc.
For example, lets us create a function that will convert then divide by 3.
var fp = require('functional-pipeline');
var S = require('spots');
function div(a, b) {
return a / b;
}
['3', '6', '9'].map(fp(
S(parseInt, S, 10),
S(div, S, 3)
));
// [1, 2, 3]
The function returned from fp( S(parseInt, S, 10), S(div, S, 3) ) has a single free argument
(the first placeholder S in parseInt). Once we provide a value to this placeholder, we immediately
have a value to be plugged into the second step S(div, S, 3). Here is the pipeline by itself.
var convertAndDivideBy3 = fp( S(parseInt, S, 10), S(div, S, 3) )
convertAndDivideBy3('300');
// 100
Conclusion
Eliminating unnecessary functions using flexible argument binding cuts down on amount of code necessary. Shorter code with fewer moving parts (fewer variables) is simpler to read and test.
Update 1
I wrote rule potential-point-free that can detect functions that can be made point-free.
/* eslint potential-point-free:1 */
function print(x) {
console.log(x);
}
[1, 2, 3].forEach(function printX(x) {
print(x);
});
Running eslint
$ eslint --rulesdir .. test.js
test.js
7:18 warning printX potential-point-free
✖ 1 problem
One has to be careful to avoid passing extra arguments in this case. I would recommend switching to Ramda's iterator and transform the above example
R.forEach(print, [1, 2, 3]);
Update 2
Sometimes the signatures of the callback and the caller do not match and we have to use adapt the callback function. If the callback needs more information, we use selective application. But if the caller has more arguments than the callback, we can use ignore-argument function. For example, if the caller passes a string first, and our work function does not need it
function foo(a, b) {
console.assert(arguments.length === 2);
}
function bar(cb) {
// calls cb with 3 arguments!
cb('foo', 'a', 'b');
}
We cannot just pass foo to bar in point-free style
bar(foo); // does not work, throws an error
We typically see small anonymous adaptor functions
bar(function (first, a, b) {
// first argument is ignored
return foo(a, b);
});
Using ignore-argument we can create point-free code in single line
bar(ignoreArgument(foo, true));
You can ignore any number of arguments, for example to ignore first and third
bar(ignoreArgument(foo, true, false, true));
We can even solve the same parseInt problem by ignoring the radix (instead of specifying 10).
['1', '2', '3'].map(ignoreArgument(parseInt, false, true)); // [1, 2, 3]
Update 3 - point-free by composition
So far, the examples only showed point-free callbacks. Point-free also can be achieved using the composition of functions. To review, we can write a function to login a user when a form is submitted by telling the computer the explicit steps.
1 | // imperative |
Notice that the imperative form declared an input variable form and uses a temporary variable
user to work. We can shorten this and avoid the temporary variable
1 | function authenticate(form) { |
Now we notice that all authenticate does is call 2 functions, passing the input form
to the toUser first, then taking the result and passing it to logIn. Applying logIn
to the result of toUser is equivalent to creating a new function that is a composition
of logIn and toUser if the functions are pure. Most functional libraries include
a general compose function, and the above authenticate can be rewritten to avoid any
variables!
1 | // declarative point-free using compose |
The example is taken from Mostly adequate guide to FP.