Easy pipeline debugging with curried console.log

Posted on by in Development, JavaScript

These days I’ve been writing a lot more functional javascript, using tools like RxJS and Ramda. They allow for beautiful, declarative pipelines of functions, like:

The one problem I’ve been running into with pipelines is debugging — I’ll have a test tell me a pipeline is outputting unexpected data, but I’m not sure where the problem is.

For instance, let’s say you’re writing a function to get the average area of a list of triangles. Each triangle is represented as an array of [height, width]. Knowing the formula for the area of a triangle is width * height / 2, you write a little pipeline function like this:

Nice and clean, looks reasonable. Then you try testing the code:

Aaaaand Expected 0.17824074074074073 to be close to 8.167. Boo.

So at this point, what I want to do is put a console.log after every step, and see how the data is changing along the pipeline. That’s easy enough; Ramda has tap (and RxJS has .do). They both let you pass a function that’ll perform a side effect, but then will return the original value. That lets us do this:

So now when we run our test, we get some extra output:

Well okay, that’s probably enough to debug the problem, but it’d be nice to be able to tag those logs so it’s easy to see what step they’re referring to. Javascript lets you pass multiple arguments to console.log, but then we can’t just pass console.log as a callback anymore, we’d have to do something like this:

That’s fine, but typing those fat arrow functions, so annoying! Happily, there’s a solution for the lazy programmer — a curried console.log.

On my last project, we added a tiny helper function for this very problem.

log takes any number of arguments to tag your log with, and then a last argument that’s your actual data, passes it all to console.log, and returns the original data. So now we can really clean up our code, to just:

But wait, log takes a value, performs a side effect with it, and then returns a value. That sounds like tap! And indeed, we can drop the calls to tap, and just use log directly:

Nice. And now looking at the test output, we see it’s definitely the division step that’s failing:

It turns out that Ramda’s divide function takes the numerator as the first argument, while we’re trying to pass it the denominator, 2. So const divideByTwo = R.divide(2) should be const divideByTwo = R.flip(R.divide)(2).

And now our test reports success!

log also works well with RxJS’s .do method:

So there you go! A handy way to inject some loggers into functional pipelines with a minimum of boilerplate.