Delicious R Curry
In R, functional::Curry
is a misnomer at best. Let’s implement currying in R.
I’ve always wondered why the function Curry
in package functional
for the language R is named that way when it actually implements partial application. What it does is transfroming a function into another one with a smaller number of arguments, which is very useful when a program contains several calls to the same function with some arguments varying and some fixed. Instead of cutting and pasting them, a no-no of ascetic programming, we can create a function that doesn’t need these repetitive arguments via partial application.
Let’s say you want to simulate dice throwing. You can define a dice
function:
suppressMessages(library(bettR))
dice = function(size) sample(1:6, size)
dice(3)
# [1] 4 3 2
Or you can use partial application:
dice = partial(sample, 1:6)
dice(3)
# [1] 4 1 3
The advantage of the latter may not be obvious when there are so few arguments involved. But you may have noticed that in the first case we had to write size
twice, with the only goal of forwarding the argument from the dice
function to sample
. If we wanted to model biased dice, we would have to forward also the prob
argument, and so on for every other argument that’s not applied to right away. With partial, we are just specifying the value of some arguments and let the other “pass through” with no red-tape.
Currying uses partial application to the extreme, if you wish, transforming a function of many arguments into a function of a single argument, that returns a function of a single argument … until the last function which returns whatever value. It allows to write f(1,2,3)
as curry(f)(1)(2)(3)
. Is that useful for programming? The evidence is in favor of a positive answer. If partial application is useful, curried functions make partial application seamless. For instance (* 5)
is a function in Haskell implementing multiplication by 5 of its single argument. *
is a function of two arguments but is also implicitly curried – provide one argument and you get a function of the remaining one. Scala also allows to define methods with multiple argument lists, that is curried functions (it actually is a generalization of the concept). That said, one can make a reasonable living programming without ever currying a single function. Nonetheless, implementing currying for any language is a great exercise in functional programming.
Partial application
While partial application is not currying, it’s a first step. If we have a function with many arguments and we can remove one, we are one step closer to a function of a single argument. As we discussed at the beginning, R has a function called Curry
that performs partial application. Unfortunately, Curry
also zaps the argument list which becomes only ...
. While it seems reasonable to expect a function of n arguments to have n − 1 arguments upon partial application to a single argument, Curry
makes that a variable number. So does function partial
in packages purrr
and pryr
. Not only it’s nice to have a list of arguments for argument checking, documentation and automatic completion, but, with the specific goal of implementing currying, we need to remove arguments one at a time. Once we have the single ...
argument, we are stuck. So I gave it a shot and the result is the following.
bettR::partial
function(f, ..., .args = alist()) {
#get args to apply f to first from ... and .args via matching
.applied =
as.list(
match.call(
f,
make_call("f", c(dots(...), .args))))[-1]
#rest to be applied to later
formf = formals(f)
ii = discard(match(names(.applied), names(formf)), is.na)
ii = if(length(ii) > 0) -ii else TRUE
.unapplied = formf[ii]
#make function of later args
pf = parent.frame()
make_function(
.unapplied,
make_call(
f,
c(.applied, lapply(names(.unapplied), as.name))),
env = pf)}
<environment: namespace:bettR>
It may look simple and hopefully it is, but there was a certain amount of trial-and-error and research involved to take care of both standard and non-standard evaluation and named and unnamed arguments. I can’t exclude I have overlooked something, but here are some examples.
ff = function(a, b, c) list(a, b, c)
pff = partial(ff, a = 1)
pff
# function (b, c)
# (function (a, b, c)
# list(a, b, c))(a = 1, b, c)
pff(c = 3, 2) #named and unnamed
# [[1]]
# [1] 1
#
# [[2]]
# [1] 2
#
# [[3]]
# [1] 3
suppressMessages(library(dplyr))
ff = partial(select, mtcars, mpg) #one lazy and one regular argument
ff
# function (...)
# (function (.data, ...)
# {
# select_(.data, .dots = lazyeval::lazy_dots(...))
# })(.data = mtcars, mpg, ...)
ff(carb)[1:5, ]
# mpg carb
# Mazda RX4 21.0 4
# Mazda RX4 Wag 21.0 4
# Datsun 710 22.8 1
# Hornet 4 Drive 21.4 1
# Hornet Sportabout 18.7 2
From partial
to curry
Let’s say we want to curry function ff
. The general plan is to build a function of the first argument of ff
that uses partial
to lock the first argument of ff to the value of its only argument and then, recursively, calls curry on the function thus created. Eventually, we are left with a function of a single argument, which we can return as is. The special case for the ...
argument and a zero-argument invocation is explained later.
bettR::curry
function(f) {
formf = formals(f)
lff = length(formf)
if(lff == 0 || (lff == 1 && names(formf) != "..."))
f
else {
make_function(
formf[1],
quote({
args = arglist(lazy = TRUE)
if(length(args) > 0)
curry(
partial(
f,
.args = args))
else
f()}))}}
<environment: namespace:bettR>
ff = function(a,b,c ) list(a,b,c)
cuff = curry(ff)
cuff(1)
# function (b)
# {
# args = arglist(lazy = TRUE)
# if (length(args) > 0)
# curry(partial(f, .args = args))
# else f()
# }
# <environment: 0x7f87e0c82f08>
cuff(1)(2)
# function (c)
# (function (b, c)
# (function (a, b, c)
# list(a, b, c))(a = 1, b, c))(b = 2, c)
# <environment: 0x7f87e10ddaa0>
cuff(1)(2)(3)
# [[1]]
# [1] 1
#
# [[2]]
# [1] 2
#
# [[3]]
# [1] 3
And there you have it, hot delicious R curry! With the ...
argument it’s harder to decide when to stop the recursion. I decided to extend currying to that case by ending the sequence of application with a zero-argument call, that is
curry(select)(mtcars)(mpg)(carb)(disp)
# function (...)
# {
# args = arglist(lazy = TRUE)
# if (length(args) > 0)
# curry(partial(f, .args = args))
# else f()
# }
# <environment: 0x7f87e324dd40>
curry(select)(mtcars)(mpg)(carb)(disp)()[1:5, ]
# mpg carb disp
# Mazda RX4 21.0 4 160
# Mazda RX4 Wag 21.0 4 160
# Datsun 710 22.8 1 108
# Hornet 4 Drive 21.4 1 258
# Hornet Sportabout 18.7 2 360
If you wonder what the bettR
package is, where this delicious stuff is cooking, it’s my playground of ideas to make R into a better language. It’s on github but I need to warn you that it’s in “research” mode and it’s not ready for either prime or subprime time.