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.