Anonymous Functions, Part II: gsubfn
gsubfn's as.function and fn$* notation
This is a follow-up to Anonymous Functions, Not Variables. For context, read that first.
After my previous post, Brodie Gaslam pointed me in an interesting direction on Twitter:
Have you seen https://t.co/KTChI5NMYb as.function.formula? Very similar concepts.
— BrodieG (@BrodieGaslam) March 26, 2018
He’s right. Gabor Grothendieck’s gsubfn
package is centered
around its gsubfn function, an extended version of gsub whose replacement
parameter can accept functions, e.g. to do arithmetic with numbers in a string.
That anonymous function can be written as a formula, a capability enabled by a
as.function.formula function which is also exported. It provides very similar
functionality to the lambda function I defined, so the nested map example
could be written
library(purrr)
map(c("a", "b", "c"),
gsubfn::as.function.formula(x ~ map_chr(1:3,
gsubfn::as.function.formula(y ~ paste0(x, y))))) %>%
str()
#> List of 3
#> $ : chr [1:3] "a1" "a2" "a3"
#> $ : chr [1:3] "b1" "b2" "b3"
#> $ : chr [1:3] "c1" "c2" "c3"
As it appears, it is a method for the S3 generic base::as.function, so when
gsubfn is loaded, the syntax can be condensed to
library(gsubfn)
#> Loading required package: proto
map(c("a", "b", "c"),
as.function(x ~ map_chr(1:3,
as.function(y ~ paste0(x, y))))) %>%
str()
#> List of 3
#> $ : chr [1:3] "a1" "a2" "a3"
#> $ : chr [1:3] "b1" "b2" "b3"
#> $ : chr [1:3] "c1" "c2" "c3"
and the generic will dispatch to it when passed a formula.
The best part of gsubfn::as.function.formula, though, is the alternative way
it can be called. At the end of the previous post, I concluded
Ideally, it would be nice to drop the
lambdacall altogether, but as far as I have been able to find, the only way to do so would be to redefinepurrr:::as_mapper.defaultto calllambdainstead ofrlang::as_closurewhen passed a formula with anything on the left-hand side.For now, then, extra keystrokes abide.
But gsubfn has found a clever way to minimize keystrokes. It defines an fn
object with its own class. That class has a method for $ that is rather
different than its usual subsetting duties. Instead, it takes a call with a
formula in it, and returns that call with the formula converted to a function
by as.function.formula (with some safeguards to ignore actual formulas). So
while it looks a little weird, at first, you can write
fn$sapply(1:3,
i ~ paste0(letters[i], i))
#> [1] "a1" "b2" "c3"
fn$map2_chr(letters[1:3],
1:3,
l + n ~ paste0(l, n))
#> [1] "a1" "b2" "c3"
or for the nested map example,
fn$map(c("a", "b", "c"),
x ~ fn$map_chr(1:3,
y ~ paste0(x, y))) %>%
str()
#> List of 3
#> $ : chr [1:3] "a1" "a2" "a3"
#> $ : chr [1:3] "b1" "b2" "b3"
#> $ : chr [1:3] "c1" "c2" "c3"
effectively modifying functions on the fly to accept full formula lambda notation in a beautiful bit of code.

Share this post