DEV Community

Discussion on: Introduction to Currying in JavaScript

Collapse
vonheikemen profile image
Heiker

Need is a strong word. I guess it becomes necessary when you're heavily invested in function composition, higher order functions and point free style.

Say you have a process where you get name and lastname from a list of users that comes from a server. If I'm in a hurry I'll just do something like this.

fetch('/users').then(users => user.map(({name, lastname}) => ({name, lastname})));
Enter fullscreen mode Exit fullscreen mode

If you want to make it more readable you'll put every step in a function.

const public_info = ({name, lastname}) => ({name, lastname});
const user_list = (users) => users.map(public_info);

fetch('/users').then(user_list);
Enter fullscreen mode Exit fullscreen mode

In the code above we are already using some sort of partial application and we see an improvement. But if we take a step further and implement a bunch of utilities that are curried by default we could end up with something like this.

const public_info = pick(['name', 'lastname']);
const user_list = map(public_info);

fetch('/users').then(user_list);
Enter fullscreen mode Exit fullscreen mode

Or, again if you're in a hurry, put everything in one line.

fetch('/users').then(map(pick(['name', 'lastname'])));
Enter fullscreen mode Exit fullscreen mode

See how well these things work together. Really, any functional programming pattern works best when you combine them with others.

And this is the part where people say "I could just do X", and that's ok. Javascript is an imperative language, you don't need fancy patterns.

Collapse
kalitine profile image
Philippe Kalitine

There is no carrying in your example, am I wrong ?

Thread Thread
vonheikemen profile image
Heiker • Edited on

Let's just say is not explicit. I didn't want to put to much code into that comment.

In my example map and pick should be curried.

function pick(keys) {
  return function(data) {
    let result = {};

    for(let idx of keys) {
      if (idx in data) {
        result[idx] = data[idx];
      }
    }

    return result;
  }
}

function map(fn) {
  return function(data) {
    return data.map(fn);
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice how those two functions are curried and their last argument is data? This is important because it what allows this type of composition.

const process_users = map(pick(['name', 'lastname']));
Enter fullscreen mode Exit fullscreen mode

And so we have created a new function (process_users) in a way that feels declarative.

If map and pick were not curried you'll have to do this.

const process_users = (users) => map(user => pick(['name', 'lastname'], user), users);
Enter fullscreen mode Exit fullscreen mode

Currying will bring you the most benefits when you're trying to do function composition. That is combining two or more functions to create a new one. If you're not interested in function composition then currying will just feels useless.


EDIT: So I just remembered I wrote something about this topic here on dev:
Partial application
Composition techniques

Thread Thread
kalitine profile image
Philippe Kalitine

Ok, now I see the currying explicitly, thank you for the explanations and I also read your "Functional programming for your everyday javascript: Partial application" to understand better the currying approach.

If we come back to @ridays2001 comment, I still don't see the point of currying. It feels like the main point is better code readability/complexity ? A different way of thinking at how we look at and approach the data transformation process which is somehow more interesting and fun ? Is that is about ?

Thread Thread
vonheikemen profile image
Heiker • Edited on

What I'm about to say is just my opinion. Not going to claim this is the absolute truth.

"The point" of currying is to enable better function composition.

Let me present a very popular function called pipe.

function pipe(...chain_of_functions) {
  return (init) => {
    let state = init;

    for(let func of chain_of_functions) {
      state = func(state);
    }

    return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

This allows us to convert this.

last_fun(second_fun(first_fun('something')));
Enter fullscreen mode Exit fullscreen mode

Into this.

const final_fun = pipe(first_fun, second_fun, last_fun);

final_fun('something');
Enter fullscreen mode Exit fullscreen mode

This seems cool... until you realise each function just takes one argument. We got ourselves a real problem here. We want to create functions with pipe (we can't change its implementation) and we also want to use functions with multiple arguments.

One answer to this problem is currying. If literally all your functions take one argument you'll never have to face this issue while using pipe.

Let's say that second_fun needs two arguments. Fine. Curry it.

const second_fun = (one) => (two) => {
  // do stuff
}
Enter fullscreen mode Exit fullscreen mode

Now you're free to do this.

const final_fun = pipe(first_fun, second_fun('WOW'), last_fun);
Enter fullscreen mode Exit fullscreen mode

It'll work like a charm.

But is this better? More readable? I can't answer that for you. It's a matter of style. Do what makes you happy.