Typescript and curried functions

  • TypeScript
  • Lodash
  • FP
If you are looking to learn functional programming, currying is likely one of the first things you will stumble across. In a nutshell, it enables us to pass arguments to a function one at a time. Upon passing each argument, it effectively mutates the function such that it can perform different operations. I recently came across a perfect opportunity to put this to use.
What if you need to convert a string to title case but you suspect you will have to convert strings in different ways further down the line? If you think about most transformations of data, they can be broken down into steps. Sometimes those individual steps can prove useful on their own. For example, the process of converting a string to title case begins with splitting the string into words.
Once you have an array of words, you can perform any operation on each individual word. Granted, you may then have to split the word into letters but that can be just another step in the currying chain. So as you can see, I created a curried function called formatWords that first splits a string into words and then accepts a callback to run on each word and then the actual string to format.
This is fine, but what if you are using Typescript? The formatWords function can be called 'overloaded', which means it can have different parameters and return types depending on how it is used. Each line in the FormatWords type corresponds to a step in the currying process. Firstly, the function takes a callback function that takes a string and returns a string. Then the function returns another function that also takes a string and returns a string. Secondly, the function takes a callback function that takes a string and returns a string and a string. Then, the function returns a string.
Phew, that one was a bit of a puzzle to understand at first so don't feel disheartened if you are confused. I think when you look at the type, it can easily be adapted to fit most kinds of curried functions. Personally, I use curried functions wherever it makes sense.
type FormatWords = {
  (callback: (word: string) => string, str: string): string;
  (callback: (word: string) => string): (str: string) => string;
};
const formatWords: FormatWords = _.curry(
  (callback: (word: string) => string, str: string) =>
    str.split(" ").map(callback).join(" "),
);
const titleCase: (str: string) => string = formatWords(
  (word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
);