Converging to Point Free

As mentioned before, I’m a big fan of the RamdaJS library. It doesn’t get you as close to Haskell as Purescript (which arguably even takes you beyond Haskell), but when your dayjob requires Javascript, it’s often the next best thing.

One pattern that seems to come up a lot at work is a pattern where you need to call a function on some values, one of which is constructed by a function that takes one of the other ones as input.

I’ll give you an example.

When sanitizing data retrieved from a database before returning it to the client as JSON, it often happens that I need to rename fields on the object to hide the fact that they’re database-generated. For example, CouchDB indexes its documents by the _id field. To hide from the end user of, say, a REST API the fact that the unique identifier for the object they’re referencing is generated by the database (rather than assigned by some other method), I want to rename this to, say, entity_id – or even just id.

The obvious function for this would be something like:

rename_field = (old, new, obj) ->
  obj[new] = obj[old]
  delete obj[old]

Of course, it’s more useful if it’s curried a bit – becuase then we can map the same renaming over a list of objects, and needless to say it happens frequently that you want to return a list of objects that all need to be "fixed" in the same way to an API client.

rename_field = (old, new) ->
  (obj) ->
    obj[new] = obj[old]
    delete obj[old]

Now I can construct a function that takes an object (obj) and renames its _id field to id like so:

rename_id = rename_field('_id', 'id')

And if I have a list of objects all of which need an _id field to replace their id field, the "looping" over it to do that to each of them is now as simple as:


OOOORRRRR, since we’re using Ramda:, list_of_objects)

And since Ramda curries everything by default, it’s easy to make one that I can apply to any list later:

Nice and compact!

Of course, it just ends up begging the question of what I can do to get rid of the explicit reference to obj in rename_field. It really shouldn’t be there, since it’s the thing that changes – and is interchangeable – with each call. Put differently, the "old" and "new" names of the fields are involved in the construction of the function we want, whereas the "obj" argument is what we want to apply this constructed function to. Repeatedly. To different ones. So, it’d be nice to get rid of the obj reference – in what’s typically called point-free programming style.

But there’s a problem. The obvious way to make this point-free using Ramda requires me to pass, as one of the arguments to the constructor portion, a thing that it itself constructed from the argument we eventually want to apply this thing to!

See for yourself. Here’s the obvious way to do it using the Ramda library’s assoc function:

rename_field = (old, new, obj) -> 
  R.assoc(new, R.prop(old)(obj), obj)

assoc takes the name of a property to add, the value of the property to add, and the object to add it to as arguments. The problem is that the "value of thing to add" bit itself depends on the "object to add it to" bit. So, making the whole thing "point free" seems hopeless.

Fortunately, Ramda has a few tricks up its sleeve.

To the rescue comes converge, a bit of an odd bird that … this is complicated … takes a function and a list of functions as parameters and applies them to whatever arguments you supply it.

"Applies them" in the following way…

The "list of functions" is each applied to the arguments supplied individually. The results of doing this are returned in a list.

So, for example, if my "list of fucntions" were…

[add, subtract]

And my arguments were (1,2)

The "list returned" would be: [3,-1] – because that’s what [add(1,2),subtract(1,2)] is.

And then [3,-1] will become the arguments to the other function supplied as the first argument.

It’s probably not obvious how this is helpful for my case, but consider … what I’d like to do is just supply the argument to the final function once, and have whatever transformations depend on it happen before those arguments get passed to the final return function (which, in our case, is R.assoc(new).

So, it’s really most of the way there, when you think about it.

I have a function that I want to return the final result: R.assoc('id') – which, when given a value and an object, will add a field id with that value to that object.

And I need the value to be generated by a function based on the object before R.assoc('id') is applied. Welp, there’s no law of nature that says lists can’t be single-valued, so:

R.converge(R.assoc('id'), [R.prop('_id')])

Which is like 95% of the way there! I’ve successfully gotten rid of my obj argument!

But of course, I’m missing an argument – I need obj passed in to R.assoc('id') along with (the output of) R.prop('_id')(obj). Oops.

It’s almost like you need a function that takes something and just returns it…

And THAT gives us the solution:

R.converge(R.assoc('id'), [R.prop('_id'), R.identity])

Abstract the field names, and the final, named version is just:

rename_field = (new, old) -> R.converge(R.assoc(new), [R.prop(old), R.identity])

And now rename_field is exactly what I wanted: a function that takes the name of the new field and the name of the old field and returns a function that, when supplied with an object, will copy the value from the old to the new.

It doesn’t destroy the old field, of course, which is an essential ingredient here, but that’s easy – just compose with dissoc.

rename_field = (new, old) -> 
  R.compose(R.dissoc(old), R.converge(R.assoc(new), [R.prop(old), R.identity]))

And there you have it! A function that makes functions that rename fields on an object. Whew!

Now, the complaints about this are obvious: "it’s hard to read" and "why go to all the trouble for something that can easily, and more legibly, be done with Javascript primitives?"

And the answers to those complaints are what they always were: this kind of style is actually more legible once you go a bit down the functional programming path, and the point of exercises like this is to build enough skill to make this kind of more intuitive programming easier to compose – to "think in" – as well. If it’s not for you, it’s not for you.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>