JavaScript treats functions as first class objects: you can easily pass them around and combine into new functions. Functions are often used as callbacks, for example in array iterations
1 | [1, 2, 3].forEach(function (value) { |
When composing several functions into a callback function, we either write a new function explicitly (like the example above), or use helpers, like lodash.compose.
1 | [1, 2, 3].forEach(function (value) { |
Notice that the second approach has certain advantages: it removes
actual code and forces the function double
to be defined separately.
This has a side benefit: you can easily unit test double
.
I like compose
function, except it is hard to read.
If we look at the order of operations in the example above, the steps
first read left to right, but the callback reads right to left (inside
out):
1 | [1, 2, 3].forEach(_.compose(console.log, double)); |
It reads: for each value (1), double it (2), then log to console (3).
The operation order is even harder to read without composition helper. Here is a typical callback to sort values by a property
1 | items = _.sortBy(items, function(item) { |
It reads: take property latest_event
(1), then its property datetime
,
then construct a new Date object (3) and then return it (4).
The switching of reading order plus writing code makes the code:
- Harder to understand
- More prone to errors (which correlates with #1)
Remember, you can only make money programming, if you write code once and reuse it for a long time. Thus you should strive to make the code as readable as possible.
functional-pipeline
I noticed a common pattern in callbacks: usually there is a single argument passed between functions, and the callback alternates between property access, method call or a function call. I wrote functional-pipeline - it is a single function that makes left to right functional composition super easy.
You load the function from node or from the browser:
var fp = require('functional-pipeline');
// or
<script src="bower_components/functional-pipeline/fp.js"></script>
However you load fp
function, it can construct new functions that operate
on first argument
1 | var newFunction = fp('property name', 'or method name', orFunction, ...); |
Instead of writing the code to sort values as above, you can define single pipeline in a single shot:
1 | function newDate(a) { return new Date(a); } |
Notice the clear operation order: grab property latest_event
, then datetime
,
then apply newDate
function. The return operation is implicit at each step.
I liked using the verb define
when talking about functional pipelines, since we
remove actual code. If there are errors in the data, we can easily debug the pipeline
by using its debug configuration (see
README.md).
The debug fp
function will check each argument during the definition step,
or during actuall callback execution to give you a very detailed error message.
Bonus
I wrote d3-helpers that has a few little utility functions, like creating a new Date object as a function which you can use with the functional pipeline.