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
The obvious function for this would be something like:
rename_field = (old, new, obj) -> obj[new] = obj[old] delete obj[old] obj
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] obj
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:
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
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…
And my arguments were
The "list returned" would be:
[3,-1] – because that’s what
[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
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:
Which is like 95% of the way there! I’ve successfully gotten rid of my
But of course, I’m missing an argument – I need
obj passed in to
R.assoc('id') along with (the output of)
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])
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!
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.