Two attitudes about Ramda

When Buzzdecafe, a major contributor to the Ramda library, recently introduced Ramda to the world, there were two very different reactions. Those who are used to functional technologies — whether in JavaScript or other languages — will mostly answer “cool.” They might get excited about it, or just casually notice another potential tool, but they understand its use.

The second reaction is, “Huh?”

For those who are not used to functional programming, Ramda may seem useless. Most of its major functions are already implemented by libraries like Underscore and Lodash.

These people are right. If you want to code in the imperative and object-oriented style you’ve been using, Ramda doesn’t have much to offer you.

It does, however, offer a different coding style that is taken for granted in purely functional programming languages: Ramda makes it easy to build complex logic by composing functions. Note that any library that has the compose function allows you to compose functions; But Ramda’s real point is: “Keep it simple.”

How does Ramda work

Let’s see how Ramda works.

Web frameworks always use “Todolist” as an example, so let’s use it as an example: Imagine a scenario where todolist is filtered to remove all completed items.

Using the built-in array prototype method, we can do this:

// Plain JS
var incompleteTasks = tasks.filter(function(task) {
    return! task.complete; });Copy the code

Using LoDash, it’s a little easier:

// Lo-Dash
var incompleteTasks = _.filter(tasks, {complete: false});

Copy the code

In both cases, we get a filtered list of tasks.

Now with Ramda, we can do this:

var incomplete = R.filter(R.where({complete: false});

Copy the code

Did you notice anything missing? The task list tasks are gone. This Ramda code just gives us a function.

To get the result, we still need to call it with the task list tasks.

var incompleteTasks = incomplete(tasks);
Copy the code

That’s the point.

Because we now have a function, we can easily combine it with other functions and then manipulate the data. Suppose we have a function, groupbyuser, that groups todo items byuser. Then we can simply create a new function:

var activeByUser = R.compose(groupByUser, incomplete);
Copy the code

The code above allows you to select unfinished tasks and group them by user.

If you don’t use Ramda’s compose and instead implement the function composition yourself manually, you’ll need to write a function like this:

/ / (if created by hand)
var activeByUser = function(tasks) {
    return groupByUser(incomplete(tasks));
};
Copy the code

The advantage of using Ramda is that you don’t have to manually implement function combinations every time. Composition is one of the key techniques of functional programming. Let’s think about a few more cases. What if we needed to sort each user’s Todolist by expiration date?

var sortUserTasks = R.compose(R.map(R.sortBy(R.prop("dueDate"))), activeByUser);
Copy the code

I’m going to merge all the functions into one function, right?

Observant readers may have noticed that we can combine all of the above. Since our compose function allows more than two arguments, why not do all this in one step?

var sortUserTasks = R.compose(
    R.mapObj(R.sortBy(R.prop('dueDate'))),
    groupByUser,
    R.filter(R.where({complete: false}));Copy the code

If you have nowhere else to call the functions ActiveByUser and incomplete, this might make sense. However, it also makes debugging more difficult and does not increase the readability of the code.

In fact, I don’t think we should combine all functions into one function. You should break up the reusable parts. It might be better if we did this:

var sortByDateDescend = R.compose(R.reverse, sortByDate);
var sortUserTasks = R.compose(R.mapObj(sortByDateDescend), activeByUser);
Copy the code

If we determine that we only want to sort by the most recent date first, then we can keep the SortByDatedDescend function alone. If services need to sort data in ascending or descending order, both sortByDate and sortByDateDescend functions should be reserved for subsequent combination.

Where is the data?

We haven’t processed the data yet. What’s going on here? Data processing without data is just process. Be patient; when you use functional programming, all you get are the functions that make up the pipe. One function provides data to the next, and the next provides data to the next, and so on, until the desired result flows out at the end.

So far, we have built the following functions:

incomplete: [Task] -> [Task]
sortByDate: [Task] -> [Task]
sortByDateDescend: [Task] -> [Task]
activeByUser: [Task] -> {String: [Task]}
sortUserTasks: {String: [Task]} -> {String: [Task]}
Copy the code

We have used the previous functions to build sortUserTasks, or we can use these functions alone. Here is the activeByUser function, where the groupByUser function, I have not implemented. How do we write it?

Here is an implementation of the groupByUser function:

var groupByUser = R.partition(R.prop('username'));
Copy the code

Wait and see what Ramda can do

To select the first five elements from the task list, we can use Ramda’s take function. We can do this:

var topFiveUserTasks = R.compose(R.mapObj(R.take(5)), sortUserTasks);
Copy the code

We only need to return a subset of the attributes of the object, such as the title and expiration date. In this data structure, the username is obviously redundant and we don’t want to pass it on to other systems.

We can do this using Ramda’s method similar to the SQL select function called project:

var importantFields = R.project(['title'.'dueDate']);
var topDataAllUsers = R.compose(R.mapObj(importantFields), topFiveUserTasks);
Copy the code

Now, in our Todolist application, we might have the following functions:

var incomplete = R.filter(R.where({complete: false}));
var sortByDate = R.sortBy(R.prop('dueDate'));
var sortByDateDescend = R.compose(R.reverse, sortByDate);
var importantFields = R.project(['title'.'dueDate']);
var groupByUser = R.partition(R.prop('username'));
var activeByUser = R.compose(groupByUser, incomplete);
var topDataAllUsers = R.compose(R.mapObj(R.compose(importantFields, 
    R.take(5), sortByDateDescend)), activeByUser);
Copy the code

All this talk about functions? Data?

Now it’s time to pass the data into the function. These functions all accept the same type of data, which is an array of TODO items. We did not specify the structure of these projects, but we do know that they must have at least the following attributes:

complete: Boolean

dueDate: String, formatted YYYY-MM-DD

title: String

userName: String

So, if we have an array of tasks, how do we use it? As follows:

var results = topDataAllUsers(tasks);
Copy the code

It’s that simple to use. The result is an object like this:

{
    Michael: [
        {dueDate: '2014-06-22', title: 'Integrate types with main code'},
        {dueDate: '2014-06-15', title: 'Finish algebraic types'},
        {dueDate: '2014-06-06', title: 'Types infrastucture'},
        {dueDate: '2014-05-24', title: 'Separating generators'},
        {dueDate: '2014-05-17', title: 'Add modulo function'}
    ],
    Richard: [
        {dueDate: '2014-06-22', title: 'API documentation'},
        {dueDate: '2014-06-15', title: 'Overview documentation'}
    ],
    Scott: [
        {dueDate: '2014-06-22', title: 'Complete build system'},
        {dueDate: '2014-06-15', title: 'Determine versioning scheme'},
        {dueDate: '2014-06-09', title: 'Add `mapObj`'},
        {dueDate: '2014-06-05', title: 'Fix `and`/`or`/`not`'},
        {dueDate: '2014-06-01', title: 'Fold algebra branch back in'}}]Copy the code

Similarly, we can pass an array of tasks to the incomplete function to get a filtered list:

var incompleteTasks = incomplete(tasks);
Copy the code

The results are as follows:

[
    {
        username: 'Scott',
        title: 'Add `mapObj`',
        dueDate: '2014-06-09',
        complete: false,
        effort: 'low',
        priority: 'medium'
    }, {
        username: 'Michael',
        title: 'Finish algebraic types',
        dueDate: '2014-06-15',
        complete: false,
        effort: 'high',
        priority: 'high'
    } /*, ... */
]
Copy the code

Of course, you can also pass the task array to SortByDate, SortByDateDescend, ImportantFields, ByUser, or ActiveByUser. Because these all run on a similar type — task arrays — we can build a large set of tools from simple combinations.

The new demand

Now that we have a new requirement, our project will support a new feature that filters the task list for a specific user. Has the same filtering, sorting and other functions as above.

var gloss = R.compose(importantFields, R.take(5), sortByDateDescend);
var topData = R.compose(gloss, incomplete);
var topDataAllUsers = R.compose(R.mapObj(gloss), activeByUser);
var byUser = R.use(R.filter).over(R.propEq("username"));
Copy the code

Here’s how to use it:

var results = topData(byUser('Scott', tasks));
Copy the code

I don’t want to use function merge, just manipulate data, ok?

Yes, for example:

var incomplete = R.filter(R.where({complete: false}));
Copy the code

Instead of getting the composite function first and then manipulating it, we get the data directly:

var incompleteTasks = R.filter(R.where({complete: false}), tasks);
Copy the code

The same is true for all other major functions: Simply add a Tasks parameter at the end of the call to return data.

What just happened?

One of the main features of Ramda. That is, all functions are automatically Currified. This means that if you do not supply all the parameters expected by the function, a new function is returned that caches the parameters already passed and expects the remaining parameters. For example, r.filter expects two arguments and passes only one. Then it returns a new function and expects another argument to be passed to the new function before it executes the filtered final data.

The automatic currification feature, combined with Ramda’s function-first, data-last API design style, makes Ramda ideally suited for writing functional programming styles.

Using Ramda

Install node environment using NPM:

npm install ramda
var R = require('ramda')
Copy the code

Browser environment:

<script src="path/to/yourCopyOf/ramda.js"></script>
Copy the code

or

<script src="path/to/yourCopyOf/ramda.min.js"></script>
Copy the code

Or use some CDN links.

Original Address: fr.umio.us/why-ramda/