Ramda: Practical Functional Javascript

 

  • first class functions
  • higher order functions
  • purity
  • immutability
  • composition
  • partial application & curry
  • recursion & optimization
  • Lazy evaluation

Reduce

Map

 

Filter

Recognize most loops are one of 

another _ ???

 

Ramda is NOT a drop-in replacement for Underscore (or LoDash). It is intended to work with a different style of coding.

Underscore/LoDash
_.map([1, 2, 3], multiply3);
// → [3, 6, 9]
 
  • Ramda methods are automatically curried.

  • Ramda takes the function(s) first, and the data last.

Ramda
R.map(multiply3, [1, 2, 3]);
// → [3, 6, 9]

R.curry
R.compose

Currying

Currying is the process of turning a function that expects multiple parameters into one that, when supplied fewer parameters, returns a new function that awaits the remaining ones.

 


var getProperty = R.curry(function(prop, obj) {
    return obj[prop];
});



var getProperty = function(prop, obj) {
    return obj[prop];
};


var name = getProperty('name', {'name' : 'damillionaire'});



var getName = getProperty('name');
var double = R.multiply(2);

double(13);  // → 26

var greater = function(a,b) {
  return a > b ? a : b;
};

var max = R.reduce(greater, -Infinity);

max([1,-3483,9,7,2]) // 9
max([-21,-3483,-2,-1]) // -1
R.multiply(2, 13); // → 26

_ vs Ramda

 

underscore/lodash

var firstTwoLetters = function(words) {
  return _.map(words, function(word) {
    return _.first(word, 2);
  });
}

firstTwoLetters(['andy', 'nero']); 
// [‘an’, ‘ne’]
Ramda.js

var firstTwoLetters = R.map(R.first(2));

firstTwoLetters(['andy', 'nero']); 
// [‘an’, ‘ne’]

_'s API prevents you from currying, b/c its arguments are backwards

compose


function compose(g, f) {
  return function(x) {
    return g(f(x));
  };
}

var wordCount = R.compose(R.length, R.split(' '));

wordCount("There are seven words in this sentence"); // 7
var wordCount = function(str) {
    var words = _.split(' ', str);
        return _.length(words);
}

wordCount("There are seven words in this sentence"); // 7

compose

"Cool. But does it do promises?"

R.composeP()

R.pipeP()

var data = {
    result: "SUCCESS",
    interfaceVersion: "1.0.3",
    requested: "10/17/2013 15:31:20",
    lastUpdated: "10/16/2013 10:52:39",
    tasks: [
        {id: 104, complete: false,            priority: "high",
                  dueDate: "2013-11-29",      username: "Scott",
                  title: "Do something",      created: "9/22/2013"},
        {id: 105, complete: false,            priority: "medium",
                  dueDate: "2013-11-22",      username: "Lena",
                  title: "Do something else", created: "9/22/2013"},
        {id: 107, complete: true,             priority: "high",
                  dueDate: "2013-11-22",      username: "Mike",
                  title: "Fix the foo",       created: "9/22/2013"},
        {id: 108, complete: false,            priority: "low",
                  dueDate: "2013-11-15",      username: "Punam",
                  title: "Adjust the bar",    created: "9/25/2013"},
        {id: 110, complete: false,            priority: "medium",
                  dueDate: "2013-11-15",      username: "Scott",
                  title: "Rename everything", created: "10/2/2013"},
        {id: 112, complete: true,             priority: "high",
                  dueDate: "2013-11-27",      username: "Lena",
                  title: "Alter all quuxes",  created: "10/5/2013"}
        // , ...
    ]
};

Suppose we expect to get some data like this:


getIncompleteTaskSummaries("Scott").then(log, log);




// OUTPUT
[
    {id: 110, title: "Rename everything", 
        dueDate: "2013-11-15", priority: "medium"},
    {id: 104, title: "Do something", 
        dueDate: "2013-11-29", priority: "high"}
]
getIncompleteTaskSummaries = function(membername) {
    return fetchData()
        .then(function(data) {
            return data.tasks;
        })
        .then(function(tasks) {
            var results = [];
            for (var i = 0, len = tasks.length; i < len; i++) {
                if (tasks[i].username == membername) {
                    results.push(tasks[i]);
                }
            }
            return results;
        })
        .then(function(tasks) {
            var results = [];
            for (var i = 0, len = tasks.length; i < len; i++) {
                if (!tasks[i].complete) {
                    results.push(tasks[i]);
                }
            }
            return results;
        })
        .then(function(tasks) {
            var results = [], task;
            for (var i = 0, len = tasks.length; i < len; i++) {
                task = tasks[i];
                results.push({
                    id: task.id,
                    dueDate: task.dueDate,
                    title: task.title,
                    priority: task.priority
                })
            }
            return results;
        })
        .then(function(tasks) {
            tasks.sort(function(first, second) {
                var a = first.dueDate, b = second.dueDate;
                return a < b ? -1 : a > b ? 1 : 0;
            });
            return tasks;
        });
};






var getIncompleteTaskSummaries = 

R.composeP(R.sortBy(R.get('dueDate')),
           R.map(R.pick(['id', 'dueDate', 'title', 'priority'])),
           R.reject(R.propEq('complete', true)) 
           R.filter(R.propEq('username', username)),
           R.prop('tasks'),
           fetchData);


getIncompleteTaskSummaries("Scott").then(log, log)


var sortByDueDate = R.composeP(R.sortBy(R.porp('dueDate')));

var getRequiredFields = R.composeP(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])));

var removeStatusComplete = R.composeP(R.reject(R.propEq('complete', true));

var filterUsername = R.filter(R.propEq('username', username));

var getTasks = R.prop('tasks');

var getIncompleteTaskSummaries = 

R.pCompose(sortByDueDate,
           getRequiredFields,
           removeStatusComplete) 
           filterUsername,
           getTasks,
           fetchData);


getIncompleteTaskSummaries("Scott").then(log, log)

function log(x) {
    console.log(x);
    return x;
};


var getIncompleteTaskSummaries = 

R.pCompose(sortByDueDate,
           getRequiredFields,
           removeStatusComplete) 
           log,
           filterUsername,
           getTasks,
           log,
           fetchData);


getIncompleteTaskSummaries("Scott").then(log, log)
"var vanillia = ['10', '10', '10'];"
"vanilia.map(parseInt)"
[10, NaN, 2]

"_.map(['10','10','10'], parseInt);"
[10, NaN, 2]

"lodash.map(['10','10','10'], parseInt);"
[10, NaN, 2]

"R.map(parseInt, ['10', '10', '10']);"
[10, 10, 10]

 

  • Encourages lean functions 

  • More declarative

  • More testable code

  • Build new functions from other functions

  • Avoid specifying sequence

  • Avoid unnecessary variables and functions

  • Mathematically backed

why CURRY / COMPOSE?

map, reduce, filter 

+ Functor's

=

Resources

</>





01 function curry(fn) {
02   return function() {
03     if (fn.length > arguments.length) {
04       var slice = Array.prototype.slice;
05       var args = slice.apply(arguments);
06       return function() {
07         return fn.apply(
08           null, args.concat(slice.apply(arguments)));
09       };
10     }
11     return fn.apply(null, arguments);
12   };
13 }

var setResults = setHtml('#results');

var renderVideos = R.map(render);

var getVideos = R.pCompose(R.prop('entry'),
                          R.prop('feed'),     
                          R.prop('data'));
var queryYoutube = R.pCompose(setResults, 
                              renderVideos, 
                              getVideos, 
                              http.getJSON);



queryYoutube(query).then(log, log);

Ramda.js - curry & compose

By andydrew

Ramda.js - curry & compose

Functional Programming in Javascript using Ramda

  • 10,311