The equivalence of the ellipsis argument and an infinite set of closures

[This article was first published on Cartesian Faith » R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This post is about a practical application of a topic I discuss in my book. In my book, I prove mathematically that the ellipsis in a function signature is equivalent to passing a closure with the same arguments bound to the closure.

I’m currently modeling consumer spending behavior by modeling their credit and debit card transactions. I model each type of transaction (for example coffee shops or utilities) as a transaction stream. One such function produces a sequence of daily transactions as f(\mu, \sigma, p) = \begin{cases}0, & U(0,1) < p \\ N(\mu,\sigma), & otherwise \end{cases} .

x.daily(mu=0, sigma=1, p=0.1) %as%
    ifelse(runif(length(t)) < p, rnorm(length(t), mu,sigma), 0)

I had a situation where I needed to generate a transaction stream using a uniform random number instead. Since the above function exposes parameters specific to a normal distribution, it’s not possible to just use a function reference for the random number generator. Instead the parameters to the RNG must be passed along. This is a good use of the ellipsis argument since it will capture any unreferenced parameters.

x.daily(p=0.1, rng=rnorm, ...) %as%
    ifelse(runif(length(t)) < p, rng(length(t), ...), 0)

Now this can be called as f <- x.daily(.4, rng=rnorm, mean=11, sd=2); f(1:20), which will generate a sequence of events where each non-zero event is N(11,2)

Lunch purchases - normal

On the other hand to generate a uniform distribution between [10, 20] is accomplished with f <- x.daily(.4, rng=runif, min=10, max=20); f(1:20).

Lunch purchases - uniform

What is the intuition around the use of the ellipsis? This is the core of the discussion and stems from how one would solve this problem if the ellipsis argument were not present. In this scenario the function would be defined

x.daily.1(p, rng) %::% numeric : Function : Function
x.daily.1(p=0.1, rng) %as%
    ifelse(runif(length(t)) < p, rng(length(t)), 0)

Calling the function is then done by passing a closure with the arguments predefined, such as x.daily.1(.4, function(x) runif(x, min=10, max=20)) or x.daily.1(.4, function(x) rnorm(x, mean=11, sd=2)). What’s remarkable is that both of these calls are equivalent to calling the original version that uses the ellipsis argument instead. Hence,

x.daily(.4, runif, min=10, max=20) ~ x.daily.1(.4, function(x) runif(x, min=10, max=20))

x.daily(.4, rnorm, mean=11, sd=2) ~ x.daily.1(.4, function(x) rnorm(x, mean=11, sd=2))

These relationships show that the ellipsis argument maps to the free variables of runif and rnorm. Clearly the function reference is arbitrary so this relationship maps to any specified function.

Armed with this equivalence relationship we can then symbolically transform a function from one representation to another. This becomes another tool used to reason about our programs and ultimately about the behavior of our models.

To leave a comment for the author, please follow the link and comment on their blog: Cartesian Faith » R. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)