Too much point-free

A use case where point-free event callback does not work well.

Early binding problem example

I love point-free style of programming and gave lots of examples in the Point-free is not pointless blog post. But sometimes point-free code becomes an obstactle to the runtime code modification. Here is an example: suppose we call a method on an object when the user clicks a button

1
2
3
4
5
6
7
8
var app = {
foo: function () {
alert('foo');
}
};
document.getElementById('btn').addEventListener('click', function onClicked() {
app.foo();
});

Suppose we want to log to the console a message when foo() runs. We can easily add new functionality at runtime. Just execute the following from the browser console

1
2
3
4
5
var _foo = app.foo; // preserve original method
app.foo = function () {
console.log('calling app.foo');
_foo.call(app);
};

If you click the button now, you will see console message, then an alert.

Notice that the onClicked event handler is not point-free. I could have written

1
document.getElementById('btn').addEventListener('click', app.foo);

This does not work for two reasons:

  • app.foo does not have the context (this) set to app object. A better code would be addEventListener('click', app.foo.bind(app));.
  • Even worse is that we can no longer replace or wrap the method, because we have registered the original method reference as the event callback

Here is where it fails

1
2
3
4
5
6
7
8
9
document.getElementById('btn').addEventListener('click', app.foo);
var _foo = app.foo; // preserve original method
app.foo = function () {
console.log('calling app.foo');
_foo.call(app);
};
// click the button
// no console message
// alert popup

The fact that we overwrote app.foo method will not change the app.foo reference already registered as the event listener. We would need something like this for new code to work

1
2
3
4
5
6
7
8
9
var button = document.getElementById('btn');
button.addEventListener('click', app.foo);
var _foo = app.foo; // preserve original method
app.foo = function () {
console.log('calling app.foo');
_foo.call(app);
};
button.removeEventListener('click', _foo);
button.addEventListener('click', app.foo);

This is of course too much complexity to add.

Complicated logic example

Here is another example where we can start with a simple point-free callback, but as it becomes progressively more complex, we are better of with an actual function.

Imagine we have a function returning a promise, resolved with an array. We would like to assert that the result is really an array.

1
2
3
4
5
6
7
function getNumbers() {
return Promise.resolve([1, 3, 7])
}
var onError = console.error.bind(console)
getNumbers()
.then(console.assert(Array.isArray))
.catch(onError)

This works, but is incorrect. We really need to pass an argument to the Array.isArray function, plus we need to return a function with assertion. Using ES6 syntax

1
2
3
getNumbers()
.then(x => console.assert(Array.isArray(x)))
.catch(onError)

Of course we try to make the check a point-free function. Since the passed variable is inside two calls console.assert(Array.isArray(x)) this is simple. Just use the composition

1
2
3
4
5
6
7
8
function compose(f, g) {
return function (x) {
return f(g(x))
}
}
getNumbers()
.then(compose(console.assert, Array.isArray))
.catch(onError)

If we need to add more features to this pipeline things become more complicated. For example, what if we want to check if each item in the array is a number? This does not work, since we cannot leave a "blank" object when making method call

1
2
3
4
5
6
function isNumber(x) {
return typeof x === 'number'
}
getNumbers()
.then(console.assert({ ? }.every(isNumber)))
.catch(onError)

We could use the Array.prototype.every method and call it on the argument, but that does not work either.

1
2
3
4
var every = Array.prototype.every
getNumbers()
.then(console.assert(every.call({ ? }, isNumber)))
.catch(onError)

Even if we make our own every function to avoid using object-oriented Array.prototype.every and every.call(array, ...) method, things do not work

1
2
3
4
5
6
function every(cb, items) {
return items.every(cb)
}
getNumbers()
.then(console.assert(every(isNumber, { ? })))
.catch(onError)

Notice, I even put the callback at the first position in every(cb, items) to make the partial application simpler, yet we still have the array variable NOT at the right position. Because the point-free variable that gets passed into the callback is not at the right-most position, we cannot easily compose calls; we cannot simply do compose3 below

1
2
3
4
5
6
7
// assume compose3 supports 3 functions f(g(h(x)))
var cb = compose3(console.assert, every, isNumber)
getNumbers()
.then(cb)
.catch(onError)
// cb(items) is console.assert(every(isNumber(items)))
// NOT console.assert(every(isNumber, items)))

We can continue down this rabit hole and solve this by introducing partial application. It will allow us to factor out the actual items variable into separate function call

1
2
3
4
5
6
7
8
function partial(fn, a) {
return function (b) {
return fn(a, b)
}
}
getNumbers()
.then(console.assert(partial(every, isNumber)))
.catch(onError)

Yet even this is incorrect! What is the assertion checking right now? Let us write down the result of the partial application - it is a function, waiting for another argument

1
2
3
4
5
var fn = partial(every, isNumber)
// fn([1, 2]) returns true
// fn([1, 'foo']) returns false
console.assert(fn);
// ALWAYS true

Instead of asserting the result of calling every(isNumber, items) we just returned immediately from console.assert(fn)! Notice how the bug just creeped in, we thought we were doing something similar to the code compose(console.assert, Array.isArray) we wrote a few lines above, but this time the result is very different.

Ok, let us just compose the assertion and the check callback.

1
2
3
getNumbers()
.then(compose(console.assert, partial(every, isNumber)))
.catch(onError)

Now it is working as expected. But now let us compare the above code vs ES6 arrow function version.

1
2
getNumbers()
.then(numbers => console.assert(numbers.every(isNumber)))

Call me a functional heretic, but the arrow version is much easier to understand in my opinion.

Conclusion

  • if you expect the need to dynamically change methods, avoid point-free style.
  • if you have complex logic, with the argument to the callback used anywhere but at the right-most position, avoid point-free style also.