← Articles
Dark theme

Point-free JavaScript with R.converge / _.over

☕ 4 min read

cover
Get to simplicity, converging functions.

The context

I’m working on a side-project which is mainly a playground for me to practice a more functional approach to JavaScript. There I’m mostly playing with ES6, Cycle.js and Ramda.

Along this journey, I’m doing my best to practice point-free style programming, aka tacit programming, when relevant.

To put it in a nutshell: it means that you never have to say your data. When used wisely, it can help keeping your code clear and concise, avoiding unnecessary intermediate variable declarations. See for yourself:

// From https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch5.md#pointfree

// Not point-free because we mention the data: name
let initials = name =>
  name
    .split(' ')
    .map(compose(toUpperCase, head))
    .join('. ')

// Point-free style
let initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '))

initials('hunter stockton thompson')
// 'H. S. T'

Then, I found myself stuck in a situation that may sound familiar if you ever tried to achieve that.

The problem

I’ve got a daysSpent function that takes a Date, another Date and then return the Number of days spent between those.

daysSpent : Date -> Date -> Number

Then, I have a list of items, each of which having a date attribute:

[
  { "list": "Backlog", "date": "2016-04-01" },
  { "list": "Card Preparation [2]", "date": "2016-04-01" },
  { "list": "Production [3]", "date": "2016-04-02" },
  { "list": "Tests QA [2]", "date": "2016-04-05" },
  { "list": "Mise en live [1]", "date": "2016-04-05" },
  { "list": "In Production", "date": "2016-04-06" },
  { "list": "Live (April 2016)", "date": "2016-04-08" }
]

What I want is to calculate the number of days spent between the first item of the list and the last one − which is something that will be called Lead Time in my context.

Well, I’m almost there. What I need is probably to use the daysSpent function. However, the format doesn’t quite match. I guess the naive attempt to do so, using Ramda, would look like:

import { pipe, prop, head, last } from 'ramda'
import { daysSpent } from './utils/dates'

// leadTime : [{date: Date}] -> Number
const leadTime = items =>
  daysSpent(pipe(head, prop('date'))(items), pipe(last, prop('date'))(items))

It sures work, but… something feel weird here.

I’m doomed to declare items and spread it around because I need to parse it into different ways before I can process it. I can’t make items flow nicely since I need to branch it to respect the arity of daysSpent.

This used to be a common issue… which Ramda solves!

R.converge to the rescue

Here is the thing:

// What I ended up with
const getX = input => getY(parseA(input), parseB(input))

// What I was trying to achieve, some sort of:
const getX = anyFunction(getY(parseA, parseB))

Among Ramda toolset, there is a function for that: R.converge. Syntax is the following:

const getX = R.converge(getY, [parseA, parseB])

Aaaand, there we go:

import { converge, pipe, prop, head, last } from 'ramda'
import { daysSpent } from './utils/dates'

// leadTime : [{date: Date}] -> Number
const leadTime = converge(daysSpent, [
  pipe(head, prop('date')),
  pipe(last, prop('date')),
])

This will call daysSpent with properly expected arguments from input. Needless to declare it with a variable, I focus on operations.

Hence, refactoring got event easier:

import { pipe, pluck, converge, head, last } from 'ramda'
import { daysSpent } from './utils/dates'

// leadTime : [{date: Date}] -> Number
const leadTime = pipe(
  pluck('date'), // first convert input into [Date]
  converge(daysSpent, [head, last]) // then pick proper ones for calculation
)

leadTime pluck input dates, then converge the head and last of the list into daysSpent to get the result. I found some beauty in such simplicity.

Oh and if you’re much a lodash person − I am, but I always wanted to experiment what Ramda got − lodash-fp is doing a pretty good job too. John-David Dalton suggested another approach to do so:

import { pipe, pluck, over, head, last, spread } from 'lodash/fp'
import { daysSpent } from './utils/dates'

// leadTime : [{date: Date}] -> Number
const leadTime = pipe(
  pluck('date'), // first convert input into [Date]
  over([head, last]), // then turns them to our arguments array [ FirstDate, LastDate ]
  spread(daysSpent) // finally spread arguments to the function
)

If you’d like to explore further:

Published 11 Apr 2016Discuss this article on Twitter

Did you know?

I’m the author of understandlegacycode.com and I’m building an interactive course to teach you to refactor any JavaScript application: refactoringjavascript.dev.

Every week, I share practical tips to help people work with Legacy Code.


I write about VS Code, web development and life in general.