Last updated: 2018-11-11
“Currying” and “partial application” are just a couple of many terms that you’ll need to get used to; it’s best to face up to this now: computer science and mathematics hold many concepts that don’t directly relate to the real world, so while we might be able to provide analogies using real world examples, the concepts themselves don’t have a normal name we can easily use so we end up with new names - which makes more sense than re-using inappropriate names; that would get confusing. So brace yourself for that, write the weird names down, and write down what they mean to you - expecting to alter them as you go and realise that you may have been subtly wrong (I speak from experience!)
So on with the definitions:
Currying is a term that means being able to deal with inputs one at a time, in batches, or all at once - this probably seems natural to you if you’re not a programmer, but in most programming languages it’s all-or-nothing! If you want to make a curry you need to have all the ingredients ready right now or you absolutely refuse to make it at all. Maybe this is why programmers get confused about the real world. Actually the name is unrelated to curry-the-food and is actually named after Curry-the-mathematician whose first name was Haskell :)
Partial application, then, is a term used to describe only having a few ingredients right now - you probably have (1) onions, potatoes, cinnamon and maybe some garlic, but aiming to complete the process some time; maybe after nipping to the shops to buy (2) cumin and ghee, realising that Tesco doesn’t sell the latter, and hopping on a bus to your local south-Asian supermarket (3) to pick some up before getting home to do the actual cooking. At each numbered point you partially-applied ingredients.
There are many ways in which this analogy breaks down, so you’ll just have to suspend disbelief and go with it!
Now we need to start using another word: “function” - it’s used in a mathematical sense where we consider it a machine into which we put things and it produces something out of the other end, if the things we put in are called “inputs” and the thing we get out is called an “output” then we say: the output is a function of its inputs.
In the analogy I’m saying (in mathsy terms) that curry-the-food is a function of its ingredients, and that we can keep adding these ingredients as we have them available.
In Haskell we’d capture the above in some notation, you shouldn’t expect to understand all the odd characters used but you can hopefully see the parallels between the paragraphs above and the code provided to capture it:
-- first we encode the recipe for curry: makeCurry :: Onions -> Potatoes -> Cinnamon -> Garlic -> Cumin -> Ghee -> Curry makeCurry = undefined -- sorry, this is not a cookery website, so I'm skipping making the actual curry! -- as described above, step-wise step1 = makeCurry onions potatoes cinnamon garlic step2 = step1 cumin -- note that we're dependent upon step1 curryMeal = step2 ghee -- and again, we're dependent upon step2 -- in the style required in most languages: curryMealAllInOne = makeCurry onions potatoes cinnamon garlic cumin ghee
Currying is the process of taking a multi-parameter function and turning it into a single-parameter function.
const add = (x, y) => x + y; add(1, 2); // result: 3
To curry this function, all we need to do is add one more step to the definition, so instead of returning the result straight away, we instead take the first argument and squirrel it away for later use, giving back a new function. Let’s have a look:
const add_c = x => y => x + y; add_c(1)(2); // result: 3
The definition is almost the same, but we added another arrow - looks weird eh? And now calling it looks weird too, because we can only pass in one argument per function. But we get the right result so at least nothing is broken!
So what’s the point? Well, it allows you do nifty things like this:
const add_one = add_c(1); add_one(2); // result: 3
Ok, maybe “nifty” is over-egging it. Think of it like the Factory pattern - in OO languages we make special abstractions to add bits of data when convenient, producing “factories” that can later be executed to produce the object we really want. This basic idea is common to the Builder concept, too.
Let’s consider that we want to allow a user to specify how wide the columns on their spreadsheet are, and pretend we’re outputting that spreadsheet into a fixed-width ascii field so we have to make sure that short strings get padded out.
const leftpad = (width, str) => str.padStart(width);
So we can remember this user configuration and use our
leftpad function when we’re outputting our spreadsheet:
const user_config = 5; // ... many lines of code later ... leftpad(user_config, "foo"); // result: " foo"
an alternative might be to use this currying technique - note that we have two arrows in our definition this time:
const leftpad = width => str => str.padStart(width);
which we can then preload with this user configuration:
const user_config = 5; const configured_padder = leftpad(user_config); // ... many lines of code later ... configured_padder("foo"); // result: " foo"
Ok, now let’s compare Haskell vs. JS, where Haskell functions are automatically curried for us which is a bit neater than in JS where we manually curried our
add :: Int -> Int -> Int -- given two ints, give me an int back add x y = x + y -- JS: add_c(1)(2); // result: 3 add 1 2 -- result: 3 -- JS: const add_one = add_c(1); add_one = add 1 -- JS: add_one(2); // result: 3 add_one 2 -- result: 3
And whilst the Haskell type notation probably looks a bit weird right now, it has the interesting effect that you can put parentheses around the types to illustrate what the type might look like in another language, note that these parentheses are valid syntax but are not required:
add :: (Int -> Int) -> Int -- given two ints, give me an int back add x y = x + y add 1 2 -- result: 3
You’ve probably figured this out already, but this term means that a function has received some, but not all of its arguments, so it’s effectively a Factory (or a Builder, if it’s waiting for a few more arguments!) until it’s been fully applied and can return its result. The parenthesizing technique can be used to illustrate this:
add :: Int -> (Int -> Int) -- given an int, return a function add x y = x + y add 1 2 -- result: 3
Think back to the first and second examples:
const add = (x, y) => x + y; const add_c = x => y => x + y;
add function, but in Haskell all functions are curried automatically so there’s no need to differentiate between them, and because this is built into the language, application of the functions is the same whether they are accepting one or more parameters.