I'm a third-year Computer Science and Mathematics student at Northeastern University interested in programming languages and developer tooling.
(λx.x x) (λx.x x)
The Convenience of Currying
Currying and partial application.
> // A function that adds two numbers in a curried fashion. > const add = (num1) => (num2) => num1 + num2; > add [Function: add] > add(1)(2) 3 > // We can now create a new function that adds 1 to a given number. > const add1 = add(1) > add1 [Function (anonymous)] > // This returns the nested function from inside `add`, with `num1` being > // set to 1. Now we can do something like: > add1(5) 6
But, what’s really the point of currying? Can’t we have a regular binary function as follows?
> const add = (num1, num2) => num1 + num2; > const add1 = (num) => add(1, num); > add1(5) 6
Yeah, we totally could. What currying provides is convenience; we don’t have to write an anonymous function every time to wrap the arguments. Observe the difference between the curried and non-curried versions of
> // Non-curried `add` > const add1 = (num) => add(1, num); > // Curried `add` > const add1 = add(1);
This is particularly useful when we use the curried functions as part of a larger computation. Let’s switch gears to Racket.
> ;; add : Number -> Number -> Number > ;; Add two numbers together, curried > (define add (lambda (num1) (lambda (num2) (+ num1 num2)))) > ;; add5 : Number -> Number > ;; Add 5 to the given number > (define add5 (add 5))
See how simple it was to define the
add5 function with a curried
add function? The catch is that if we wanted to use
add by itself, it’s somewhat less readable:
> ((add 1) 2) 3
Racket, however, is a powerful language and comes with great built-in functions. One of them is specifically for transforming a function into a curried one:
> ;; add : Number Number -> Number > ;; Your standard binary addition function. > (define (add num1 num2) (+ num1 num2)) > ;; (curry func args ...) takes in a function `func` and passes in the `args ...`, > ;; which can be no arguments at all. It returns a curried function of `func` after > ;; the `args ...` are passed in. > (define add5 (curry add 5)) > (add5 5) 10
Addition isn’t really a practical example for showing the power of currying, however. Currying is about delaying the computation of a function for later by only passing in a few of its arguments and letting the rest be passed in later. We call this partial-application — more formally where a function produces another function of a smaller arity (less parameters). Currying is a convenient way of achieving partial application.
Let’s write a function
contains?, specifically for lists of strings that determines if a string is contained within the list of strings.
;; contains? : (Listof String) String -> Boolean ;; Is the string contained in the list of strings? (define (contains? los target-string) (ormap (lambda (s) (string=? target-string s)) los))
Hmm, wait a minute. We can actually introduce currying into this function — observe that in the anonymous function we pass into the
ormap, we our
target-string remains constant. Doesn’t that mean we can partially evaluate our
string=? and wait for each string
s to fully evaluate?
;; contains? : (Listof String) String -> Boolean ;; Is the string contained in the list of strings? (define (contains? los target-string) (ormap (curry string=? target-string) los))
Much cleaner: with currying, we don’t have to wrap our
string=? in an anonymous function. Let’s consider another problem that uses the
contains? function we defined. Let’s say we have a list of people who can enter the club, aptly named
cool-people. We want to filter a list of people trying to enter the club (
queue) for only ones on the list of
;; people-entering-club : (Listof String) (Listof String) -> (Listof String) ;; Filter for people in the `queue` who are in `cool-people`. (define (people-entering-club cool-people queue) (filter (lambda (person) (contains? cool-people person)) queue))
We can transform that lambda into a curried function, of course.
;; people-entering-club : (Listof String) (Listof String) -> (Listof String) ;; Filter for people in the `queue` who are in `cool-people`. (define (people-entering-club cool-people queue) (filter (curry contains? cool-people) queue))
Here’s the function in use:
> (people-entering-club '("John" "Jake" "Joe") '("Jack" "John" "Joey" "Jerry" "Jake")) '("John" "Jake")
In essence, currying is a powerful and convenient way of achieving partial application in a clean manner. It enables you to take “full” functions and delay their evaluation for later for when you get the rest of the arguments to pass in.