Adapted point-free callbacks

Ignoring some arguments and partial binding for methods calls.

I described point-free programming in a separate blog post entry. This blog post gives 2 more examples that come up in my day to day programming.

Adapting callback to extra arguments

In most situations, the callback function requires more arguments that the caller supplies. For example sum needs 2 arguments, while foo supplies only 1

1
2
3
4
5
6
function sum(a, b) {
return a + b;
}
function foo() {
return sum(1);
}

We usually solve this problem using partial application with built-in JavaScript method

1
2
3
4
5
var add10 = sum.bind(null, 10); // add10 needs only 1 argument
function foo() {
return add10(1);
}
// returns 11

In other cases, we want to limit and not pass arguments from the caller to the callback function. For example parseInt(str, radix) requires more (or non-default) arguments that we provide, leading to the confusion

1
2
3
4
['1', '2', '3'].map(parseInt); // [1, NaN, NaN]
// because caller passes wrong / more arguments to the callback fn
// parseInt (callback) expects (str, radix)
// Array.prototype.map calls with (item, index, array)

Typically I solve this problem by binding (partial application), providing some values to the arguments. For example, I can create a new function around parseInt that always provides 10 as the second argument (using spots).

1
2
3
var S = require('spots');
['1', '2', '3'].map(S(parseInt, S, 10));
// [1, 2, 3]

What if we want to let parseInt pick a radix? We need to create a callback that ignores values passed from the caller. We could create a small anonymous adaptor function, whose only purpose is to call parseInt

1
2
3
['1', '2', '3'].map(function (x) {
return parseInt(x);
});

This is not point-free approach I like, instead this is verbose and error-prone extra code.

I wrote a small function ignore-argument that skips certain arguments and allows quickly adapting a function to be called point-free from the chatty caller.

1
2
3
var ignore = require('ignore-argument');
['1', '2', '3'].map(ignore(parseInt, false, true)); // ignore second argument
// [1, 2, 3]

The same approach can be used to skip the first argument when setting callback for Angular event listener on the scope object. Instead of this:

1
2
3
4
function onFoo(a, b) { ... }
$scope.$on('foo-event', function (event, arg1, arg2) {
onFoo(arg1, arg2);
});

We adapt onFoo to ignore the first argument in place:

1
2
3
var ignore = require('ignore-argument');
function onFoo(a, b) { ... }
$scope.$on('foo-event', ignore(onFoo, true));

Partial application for point-free methods

A common case in my code lately has been calling the same method, with the same first argument, but varied second argument. For example when adding the same class to bunch of elements by selector we can write:

1
2
3
4
5
var selectors = ['.navbar', '#footer', '#help'];
function addLiteClass(sel) {
$(sel).addClass('lite');
};
selectors.forEach(addLiteClass);

Can we avoid creating addLiteClass adaptor and use point-free approach? Yes, and here is how it would work. Let us take smaller example, without jQuery call $(...) to simplicity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var objects = [
{
foo: function (a, b) {
console.log('first object, a', a, 'b', b);
}
},
{
foo: function (a, b) {
console.log('second object, a', a, 'b', b);
}
}
];
// explicit adaptor version
objects.forEach(function (object, k) {
object.foo('A', k);
});
// prints
first object, a A b 0
second object, a A b 1

We are calling method foo, and passing two arguments "A", k. Let us use one of my favorite libraries Ramda to call method 'foo' and apply "A"

var fooA = R.invokerN(2, 'foo')('A');
objects.forEach(function (object, k) {
  fooA(k, object);
});

The temporary function fooA will call the actual method .foo but needs it the second argument and the actual instance. We cannot use it to use in a point-free forEach callback, because the arguments are flipped. Since there are only two arguments (anything with more than 2 arguments requires an explicit adaptor function in my opinion), we can flip the order using R.flip function. This allows us to write a point-free callback in this case

objects.forEach(
    R.flip(R.invokerN(2, 'foo')('A'))
);

One can argue if this is a worse coding style than an explicit adaptor version, but I wanted to show that this is possible.