Tacit Ramda

Slobodan Blazeski
4 min readMar 7, 2015

--

Some programming languages, especially among those which haven't gained great popularity, are puzzles. James Hague (Puzzle Languages)

Functional programming in JavaScript took a foothold with Underscore, which as of today March 7th 2015, is the most depended package on npm, fallowed by Lodash as #3. Both Jeremy Ashkenas and John-David Dalton did a great job bringing functional flavor to the JavaScript world, for that I’m very grateful.

Lately I've been playing a lot with Ramda, JavaScript library that promises purer functional style wrapped in a practical package that could be used in your daily projects. Plus I have a bitter sweet relationship with its authors, who never gave me what I've asked, but they always delivered what I need.

Ramda arguments are flipped for the reason best presented by Brian Lonsdorf talk Hey Underscore, You’re Doing It Wrong! and its functions are partially applied by default. Together with lack of optional arguments and no dispatching on type Ramda enables writing code in tacit aka pointfree style. Those things though not new, remained in the domain of the experimental and academic libraries.

You could find Ramda at http://ramdajs.com/ which for convenience has an online REPL that enables you to try the below code examples at http://ramdajs.com/repl/ . OK enough talking, lets start coding.

Curry

Ramda functions are automatically curried, thus enabling you to create new functions by omitting arguments.

var add5 = R.add(5); // http://ramdajs.com/docs/#add
add5(4) //=> 9
var double = R.multiply(2)
double(6);//=> 12

And if you need your own functions to be curryed use R.curry

// http://en.wikipedia.org/wiki/Heron%27s_formula 
var heron = R.curry(function heron(a,b,c){
var s= (a+b+c)/2;
return Math.sqrt(s*(s-a)*(s-b)*(s-c));
});
R.map(heron(3,4),[5,7,9])//=> [6,0,NaN]

The first issue with currying comes with anti-commutative functions, such as divide, subtract & modulo. In curried form its usually the first argument that we want to omit. For such uses Ramda provides R.__ placeholder

var minus5 = R.subtract(R.__,5);
minus5(9) //=> 4
var half= R.divide(R.__,2);
half(6); //=> 3
var isOdd = R.modulo(R.__,2);
isOdd(3) //=> 1

Though its usage is consistent throughout the library this R.__ placeholder introduces some kind of ugliness thus having a version with flipped arguments looks better for this trio of functions.

For this purpose Ramda provides R.flip which returns a new function much like the supplied one, except that the first two arguments’ order is reversed.

var subtractBy = R.flip(R.subtract)
var minus5 = subtractBy(5)
minus5(9) //=> 4
var divideBy = R.flip(R.divide)
var half = divideBy(2)
half(6); //=> 3
var moduloBy = R.flip(R.modulo)
var isOdd = moduloBy(2)
isOdd(3) //=> 1

Ramda actually had above three functions in the previous versions but they were omitted to keep the library leaner.

Classics

The most well known higher order functions map, filter & reduce are curried as well in Ramda.

// double elements
var doubleAll = R.map(R.multiply(2))
doubleAll([1,2,3]) //=> [2,4,6]
var odds = R.filter(R.modulo(R.__,2))
odds([1,2,3,4]) //=> [1,3]
var flatten = R.reduce(R.concat,[]);
flatten([[1,2],[3,4]])//=>[1,2,3,4]

Compose

Now that we covered the basics lets get to the part where we are creating more interesting functions. Our first tool is compose which creates a new function that runs each of the functions supplied as parameters in turn, passing the return value of each function invocation to the next function invocation, beginning with whatever arguments were passed to the initial invocation. Yes I've copy pasted the documentation but example will clear the concept.

var triplePlusOneAbs = R.compose(Math.abs,R.add(1),R.multiply(3))
triplePlusOneAbs(-2) //=> 5
// which is more or less
function triplePlusOneAbs(x){
return Math.abs(R.add(1,R.multiply(3,x)));
}
triplePlusOneAbs(-2) //=> 5

Note that rightmost function could take several arguments, the rest are limited to one.

var rectanglePerimeter = R.compose(R.multiply(2),R.add)
rectanglePerimeter(2,3);//=>10

One nice technique when using pure functions is being able to memoize the results.

var oneToN = R.compose(R.range(1),R.add(1))
oneToN(5) //=> [1,2,3,4,5]
var factorial = R.memoize(R.compose(R.product,oneToN))
factorial(5) //=> 120

How about splitting the string into uppercase tokens

var tokenize = R.compose(R.split(' '),R.toUpper);
tokenize("Mary had a little lamb")
//=>['MARY','HAD','A','LITTLE','LAMB']

Converge

Converge also known as monadic fork in the J world is probably the most well known of the higher order forms

var avg = R.converge(R.divide,R.sum,R.length)
avg([1,2,3,4,5,6,7])//=> 4
var isDuplicate = R.converge(R.not(R.eq),R.indexOf,R.lastIndexOf)
isDuplicate(1,[2,1,3,1,6]) //=> true
isDuplicate(2,[2,1,3,1,6]) //=> false

The best explanation how it works is probably to see how its implemented

var converge = R.curry(function(f,g,h,x,y) {
return f(g(x,y),h(x,y));
});

The other higher order form I’ve found quite useful is monadic noun fork

var monadNounFork = R.curry(function monadicNounFork(f, g,x, y){
return f(g(x),y);
});
var listPlusN = monadNounFork(R.map,R.add);
listPlusN(10, [1,2,3,4,5]);//=> [11,12,13,14,15]

But let’s try it on accessing data. Here’s an example I’ve shamelessly ripped of the Hardcore Functional Programming in JavaScript. We have an array containing articles. We want to create a function that extracts all the author names and another one that checks is someone an author.

var monadNounFork = R.curry(function monadNounFork(f, g,x, y){
return f(g(x),y);
});
var articles = [
{
title: 'Everything Sucks',
url: 'http://do.wn/sucks.html',
author: {
name: 'Debbie Downer',
email: 'debbie@do.wn'
}
},
{
title: 'If You Please',
url: 'http://www.geocities.com/milq',
author: {
name: 'Caspar Milquetoast',
email: 'hello@me.com'
}
}
];
var authors = R.map(R.compose(R.prop('name'), R.prop('author')));
authors(articles);
// =>['Debbie Downer','Caspar Milquetoast']
var isAuthor = R.flip(monadNounFork(R.flip(R.contains), authors));
isAuthor('New Guy', articles)
isAuthor('Caspar Milquetoast', articles)

The higher form worked and we achieved our goal without using parameters. However all those flips are sign of a code smell. We are trying to fit tacit style into the problem where the tacit solution isn’t obvious. That makes it a puzzle.

As a parting gift I’ll left with you with an exercise for the reader.

We have a list of elements, some are duplicates. We’re trying out how to find the duplicate elements and increase a counter value by 1 for each instance of the element found. The list consists of lists with two elements, the first being the incremental counter, the second being the string sought.

http://www.nsl.com/papers/kisntlisp.htm

var example= [[1,"one"],[1,"two"],[1,"three"],[1,"one"],[1,"four"],[1,"two"]];
var brunberg = R.compose(R.mapObj(R.length),R.groupBy(R.nth(1)))
brunberg(example)
// =>"{"one":2,"two":2,"three":1,"four":1}"

Your solution should produce

var sought = [[2,"one"],[2,"two"],[1."three"],[1,"four"]];

Update:

As suggested by Scott Sauyer @CrossEye there is a simpler definition for isAuthor using R.useWith :

var isAuthor = R.useWith(R.contains, R.identity, authors);

Here’s my solution for the final exercise:

var example= [[1,”one”],[1,”two”],[1,”three”],[1,”one”],[1,”four”],[1,”two”]];
var brunberg = R.compose(R.map(R.reverse),R.toPairs,R.mapObj(R.length),R.groupBy(R.nth(1)))
JSON.stringify(brunberg(example));
// => [[2,"one"],[2,"two"],[1,"three"],[1,"four"]]

--

--