Skip to content

Currying

Bill Hails edited this page Oct 21, 2018 · 11 revisions

Functions can be curried, For example:

fn map(f, l) {
    if (l == []) {
        []
    } else {
        f(head(l)) @ map(f, tail(l))
    }
}

fn mul(x, y) {
    x * y
}

map(mul(2), [1, 2, 3, 4]); // [2, 4, 6, 8]

Note that mul is defined with two arguments but called with only one, resulting an a closure that accepts the second argument and performs the computation.

There are better ways to write map, see later.

Curried Binary Operators

Binary operators can also be curried. That means the previous example could be more succinctly written as

map(2 *, [1, 2, 3, 4]);

Internally this is achieved completely by the parser. On detecting an incomplete binary operator, it wraps it in a closure, with a generated argument symbol. So writing

map(3 * 2 *, [1, 2, 3, 4]);

is just a shorthand for:

map(fn (_x) { 3 * 2 * _x }, [1, 2, 3, 4]);

Because this is done at a syntactic level, there are some oddities. For example this works

map(2 * 3 + 1 +, [1, 2, 3, 4]); // => [8, 9, 10, 11]

That is because the curried expression parses as ((2 * 3) + 1) +. However something like 2 + 2 * would parse as 2 + (2 *) and would fail to type check because the curried (2 *) is a function int -> int and you can't add an int to a function (see Type Notation for an explanation of int -> int.)

Similarly, this works:

map(1 @, [[2], [3], [4]]); // => [[1, 2], [1, 3], [1, 4]]

but this doesn't:

map(1 @ 2 @, [[3], [4], [5]]);

Because @ is right-associative the expression 1 @ 2 @ parses as 1 @ (2 @), i.e. an attempt to cons 1 with a function list(int) -> list(int).

So curried binary operators are an occasional convenience, and you can always provide a wrapper function if they won't parse the way you want them to.

Up: Functions

Next: Over-complete Function Application

Clone this wiki locally