The Convenience of Currying
A curried function is a relatively simple idea: it's a series of unary functions returning unary functions, with the deepest unary function returning the computed final result. When currying is explained, usually a simple JavaScript example like below is used:
> // 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 add1
again:
> // 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: curry
.
> ;; 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 cool-people
.
;; 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.