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
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