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:
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
lambda
call altogether, but as far as I have been able to find, the only way to do so would be to redefinepurrr:::as_mapper.default
to calllambda
instead ofrlang::as_closure
when 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