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 | var app = { |
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 | var _foo = app.foo; // preserve original method |
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 toapp
object. A better code would beaddEventListener('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 | document.getElementById('btn').addEventListener('click', app.foo); |
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 | var button = document.getElementById('btn'); |
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 | function getNumbers() { |
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 | getNumbers() |
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 | function compose(f, g) { |
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 | function isNumber(x) { |
We could use the Array.prototype.every
method and call it on the argument, but that does not
work either.
1 | var every = Array.prototype.every |
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 | function every(cb, items) { |
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 | // assume compose3 supports 3 functions f(g(h(x))) |
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 | function partial(fn, a) { |
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 | var fn = partial(every, isNumber) |
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 | getNumbers() |
Now it is working as expected. But now let us compare the above code vs ES6 arrow function version.
1 | getNumbers() |
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.