Curry And Compose

I know I tackled this in another post, but it was more of an aside. I really want to give focus to the awesome dream team, buddy cop movie, and super-hero super-group that is curry and compose.

I've warmed up to the ramda library again (for reasons I'll get into later :) ), so I'll be using it for the following examples.

curry

Here's a refresher on how R.curry works:

var R = require('ramda')

var add3 = R.curry((a, b, c) => a + b + c)

//all of these return 6
add3(1, 2, 3)  
add3(1)(2, 3)  
add3(1, 2)(3)  
add3(1)(2)(3)  

curry takes a function and returns a curried version of it. A curried function takes all or some of its arguments.

If you give it all of its arguments, it returns normally. If you only give it some of its arguments, it returns a version of itself preloaded with those arguments and ready to take the rest.

var add2 = add3(0)  
add2(1, 2)  
//returns 3

What's cool is that ramda auto-curries it's functions:

var doubles = R.map((x) => 2 * x)  
var evens = R.filter((x) => x % 2 === 0)  
var sum = R.reduce((x, y) => x + y, 0)

doubles([1, 2, 3, 4, 5])  
//returns [ 2, 4, 6, 8, 10 ]

evens([1, 2, 3, 4, 5])  
//returns [ 2, 4 ]

sum([1, 2, 3, 4, 5])  
//returns 15

compose

compose takes a list of functions and composes them, like so:

var f = (x) => 2 * x  
var g = (x) => x + 1  
var h = (x) => x / 2

var fogoh = R.compose(f, g, h)

fogoh(2) //returns 4  

compose(f, g, h) returns a function that returns f of g of h.

curry + compose

So, how well do compose and curry work together? Check it out:

var comma_separate = (width) =>  
    R.compose(
        R.join(','),
        R.map(R.reverse),
        R.reverse,
        R.splitEvery(width),
        R.reverse)

var format_dollars =  
    R.compose(
        R.concat('$'),
        comma_separate(3),
        R.toString)

format_dollars(100000)  
//returns $100,000

comma_separate starts by taking a string and reversing it with R.reverse. Here's how it works in our example:

000001  

Then we split that string into 3's with R.splitEvery(width) (width being 3 in this case):

[ '000', '001' ]

Note that since R.splitEvery is auto-curried we can preload it with width. This way it only takes one parameter, allowing us to fit it into our composition chain.

Next we reverse the array again with R.reverse:

[ '001', '000' ]

Then reverse each element in the array with R.map(R.reverse):

[ '100', '000' ]

Again, since we pre-load R.map with R.reverse the function R.map(R.reverse) only takes one more argument, allowing it to be a link in our composition chain.

Finally we join the string pretty much the same way we split it, taking advantage of currying again with R.join(','):

100,000  

format_dollars then uses comma_separate and concat to finish the job (again, taking advantage of currying):

$100,000

You can keep adding functionality this way. Check out format_dollars_cents:

var format_dollars_cents =  
    R.compose(
        R.join('.'),
        (money) => [
            format_dollars(money[0]),
            money[1] ? money[1].slice(0, 2) : '00'],
        R.split('.'),
        R.toString)

format_dollars_cents(100000)  
//returns $100,000

format_dollars_cents(100000.234)  
//returns $100,000.23

format_dollars_cents(.234)  
//returns $0.23

Looking for a software developer?