Skip to content

Instantly share code, notes, and snippets.

@nkabrown
Last active June 8, 2022 18:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nkabrown/e47b884c532bd7998ad19fa8af540b1e to your computer and use it in GitHub Desktop.
Save nkabrown/e47b884c532bd7998ad19fa8af540b1e to your computer and use it in GitHub Desktop.

Currying Demonstrated and Explained

const getCommand = commands => word => key => key in commands ? commands[key](word) : null;

Wow! What is that? It sure looks cool but what is actually going on there?

That is a curried function and I hope to explain to you over the next page or two what that is and why you might prefer to write this function in that way rather than this:

function getCommand(commands, word, key) {
  if (key in commands) {
    return commands[key](word);
  } else {
    return null;
  }
}

Currying is the process of rewriting a function that takes multiple arguments into a sequence of functions each of which takes one argument.

First a diversion into math. For every mathematical formula that contains variables x + y + 2 we can define functions that will substitute specific value(s) for the unknown value(s) represented by the variable placeholder. For example we can define two functions of two variables for the above formula h(x,y) = x + y + 2 and k(y,x) = x + y + 2. But we can also define two one variable functions and compose them f(x) = x + y + 2 and g(y) = x + y + 2 composed as h = g . f and applied as h(x)(y).

f(x) = x + y + 2
g(y) = x + y + 2
h = g . f // g after f

i = h(3) = 3 + y + 2 // application of first argument returns a function awaiting the second argument
i(4) = 3 + 4 + 2 // application of second argument returns value
9

As long as our programming language has first-class support for functions we also have these same two choices in how we'd like to define our functions.

Uncurried function that takes three parameters

const commands = {
  repeat: (str) => str + str,
  exclaim: (str) => `${str}!`,
  reverse: (str) => str.split('').reverse().join('')
};

function getCommand(commands, word, key) {
  if (key in commands) {
    return commands[key](word);
  } else {
    return null;
  }
}

getCommand(commands, 'hello', 'repeat');
//=> "hellohello"

Same function written as a curried function through function declarations

function getCommand(commands) {
  return function(word) {
    return function(key) {
      if (key in commands) {
        return commands[key](word);
      } else {
        return null;
      }
    }
  }
}

getCommand(commands)('hello')('repeat');
//=> "hellohello"

A curried function that takes one argument at a time opens the door to partial application. Our function will return its value if we apply all three arguments, but if we apply only one or two arguments it will return a function that is awaiting the next argument. This function we can bind to a name and/or pass into other functions or reuse throughout our program. This is powerful!

const commandDispatch = getCommand(commands);
//=> ƒ (word) {
//     return function(key) {
//       if (key in commands) {
//         return commands[key](word);
//       } else {
//         return null;
//       }
//     }
//   }

commandDispatch('hello')('repeat');
//=> "hellohello"

commandDispatch('hello')('exclaim');
//=> "hello!"

The curried function written above is the longhand and long-winded version of this

const getCommand = commands => word => key => key in commands ? commands[key](word) : null;

getCommand(commands)('hello')('repeat');
//=> "hellohello"

Replace a JavaScript switch statement with curried function composition as a practical example of the power of currying .

Original example from https://gist.github.com/Elli-P/cccaacfae68515c0cdab0e62aaa60538

// change the value of `education` to test your code
var education = "an Associate's degree";

// set the value of this based on a person's education
var salary = "";

// your code goes here
switch (education) {
    case "no high school diploma":
        salary += "$25,636";
        break;
    case "a high school diploma":
        salary += "$35,256";
        break;
    case "an Associate's degree":
        salary += "$41,496";
        break;
    case "a Bachelor's degree":
        salary += "$59,124";
        break;
    case "a Master's degree":
        salary += "$69,732";
        break;
    case "a Professional degree":
        salary += "$89,960";
        break;
    case "a Doctoral degree":
        salary += "$84,396";
        break;
  }

console.log ("In 2015, a person with " + education + " earned an average of " + salary.toLocaleString("en-US") + "/year.");

Make it functional

const education = "an Associate's degree";

const switchcase = cases => key => key in cases ? cases[key] : 'unknown amount';

const salary = switchcase({
  "no high school diploma": "$25,636",
  "a high school diploma": "$35,256",
  "an Associate's degree": "$41,496",
  "a Bachelor's degree": "$59,124",
  "a Master's degree": "$69,732",
  "a Professional degree": "$89,960",
  "a Doctoral degree": "$84,396"
})(education);

console.log(`In 2015, a person with ${education} earned an average of ${salary}/year.`);

There are a lot of advantages to replacing the switch statement with currying functions. For example it's very reusable. We can apply any object with strings for values as the first argument.

const ageGroup = {
  "20-35": "$32,687",
  "36-45": "$51,433",
  "46-55": "$58,701",
  "56-70": "$55,378"
};

const educationLevel = {
  "no high school diploma": "$25,636",
  "a high school diploma": "$35,256",
  "an Associate's degree": "$41,496",
  "a Bachelor's degree": "$59,124",
  "a Master's degree": "$69,732",
  "a Professional degree": "$89,960",
  "a Doctoral degree": "$84,396"
};

const salaryByEducation = switchcase(educationLevel);
const salaryByAge = switchcase(ageGroup);

This is an example of partial application. We can pass these values around into other functions and then apply the key whenever we'd like to get a value back. But that is just the start, even better if we have just these two cases we can do a little rewriting and do this:

const switchcase = cases => optionalCases => key => key in cases ? cases[key] : switchcase(optionalCases)(cases)(key);

const bothCases = switchcase(educationLevel)(ageGroup)

bothCases("20-35") // => "$32,687"
bothCases("a Master's degree") // => "$69,732"

Awesome right!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment