An Anonymous Function (also known as a lambda experssion) is a function definition that is not bound to an identifier. That is, it is a function that is created and used, but never assigned to a variable.
In a prior post I discussed how I am not completely satisfied with purrr
/rlang
’s
anonymous function syntax as it has no capability for naming the
function arguments anything other than .x
and .y
.
In this post, I’ll look at alternate syntaxes for creating anonymous functions.
Implementations of anonymous functions in R
There are many implementations of helpers to create anonymous functions.
Base R has its own mechanism, and all the other implementations try and keep
the syntax shorter by dropping the requirement to actually type function()
when
creating the anonymous function.
The following table lists the implementations of anonymous functions I could find in R, and their syntax for creating an anonymous function with 2 arguments.
The Formal Arguments column indicates how the arguments for the anonymous
function are derived. Some packages support both implicit and explicit argument
naming (e.g. gsubfn
and pryr
). If the formal arguments are not explicitly stated,
then the argument list may be inferred by:
- convention e.g.
rlang::as_function()
assumes arguments are.x
and.y
- inference e.g.
pryr::f()
uses all variables found in the expression as formal arguments.
Package | Function Name | 1-arg | 2-args | Explicit Arg Names | Formal Arguments | Extra chars for implicit args | Core Syntax | Core Syntax Nchars | formals/body separator | comments | author | URL |
---|---|---|---|---|---|---|---|---|---|---|---|---|
base r | function | function(x) x + 1 | function(x, y) x + y | Yes | Explicit | NA | function() | 10 | NA | NA | R Core | NA |
rlang | as_function | as_function(~.x + 1)) | as_function(~.x+.y) | No | Implicit. Only .x or .y | .x | f(~) | 4 | NA | used within purrr | Lionel Henry et al | https://cran.r-project.org/package=rlang |
pryr | f (implicit args) | f(x + 1) | f(x + y) | No | Implicit. Inferred from expression | NA | f() | 3 | NA | NA | Hadley Wickham | https://cran.r-project.org/package=pryr |
pryr | f (explicit args) | f(x, x + 1) | f(x, y, x + y) | Yes | Explicit | NA | f() | 3 | , | NA | NA | NA |
nofrills | fn | fn(x ~ x + 1) | fn(x, y ~ x + y) | Yes | Explicit | NA | f(~) | 4 | ~ | Fully NSE aware (!!) | Eugene Ha | https://cran.r-project.org/package=nofrills |
gsubfn | as.function.formula (implicit args) | as.function.formula(~ x + 1) | as.function.formula(~ x + y) | No | Implicit. Inferred from RHS of formula | NA | f(~) | 4 | NA | NA | G. Grothendieck | https://cran.r-project.org/package=gsubfn |
gsubfn | as.function.formula (explicit args) | as.function.formula(x ~ x + 1) | as.function.formula(x + y ~ x + y + z) | Yes | Explicit. Inferred from LHS of formula | NA | f(~) | 4 | ~ | NA | NA | NA |
wrapr | lambda | lambda(x, x + 1) | lambda(x, y, x + y) | Yes | Explicit | NA | f() | 3 | , | NA | John Mount et al | https://cran.r-project.org/package=wrapr |
lambda | f | f(.(x) + 1) | f(.(x) + .(y)) | No | Implicit. Inferred from expression | .(x) | f() | 3 | NA | NA | Jim Hester | https://github.com/jimhester/lambda |
lambdass | ~~ | ~~ ..1 + 1 | ~~ ..1 + ..2 | No | Implicit. Only ..N arg syntax allowed | ..1 | ~~ | 2 | NA | NA | TobCap | https://github.com/TobCap/lambdass |
lambdass | f.() | f.(x, x + 1) | f.(x, y, x + y) | Yes | Explicit | NA | f() | 3 | , | NA | NA | NA |
lambdass | %->% | f(x) %->% {x + 1} | f(x, y) %->% {x + y} | Yes | Explicit | NA | f()%->%{} | 9 | `%->% | NA | NA | NA |
functional | “->” | NA | x ~ y -> x + y | Yes | Explicit | NA | NA | NA | NA | messes with <- operator! | Konrad Rudolph | https://github.com/klmr/functional |
Not packaged | lambda | lambda(x ~ x + 1L) | lambda(x + y ~ x + y) | Yes | Explicit. Inferred from LHS of formula | NA | f(~) | 4 | ~ | NA | Edward Visel | https://alistaire.rbind.io/blog/anonymous-functions/ |
Not packaged | lambda | lambda(x:x + 1) | lambda(x, y:x + y) | Yes | Explicit | NA | f(:) | 4 | , | Code no longer available | Koji MAKIYAMA | https://rpubs.com/hoxo_m/lambdaR |
Not packaged | [] -> | [x] -> x + 1 | [x, y] -> x + y | Yes | Explicit | NA | []-> | 4 | `-> | Speculative Future R syntax | Lionel Henry | https://lionel-.github.io/2016/02/15/ideas-for-an-updated-r-syntax/ |
Features of an anonymous function syntax
I’ve tried to come up with a taxonomy to help classify the various syntaxes. I can’t say I was hugely successful, but it helped me organise my thoughts a little.
- Explicit Arguments: The syntax has a list of formal arguments provided separate to the body of the function
- Arguments provided as a separate list
- e.g.
Base R
- e.g.
- Arguments are parsed out of a separate expression (which is not the expression which becomes the body of the anonymous function)
- e.g.
gsubfn
where names are parsed out of the expression on the LHS of the formula
- e.g.
- Arguments provided as a separate list
- Implicit Arguments: The syntax only provides the body of the anonymous function, and the
formal arguments are never separately listed
- Arguments are pre-defined
- e.g.
rlang::as_function
pre-defines.x
and.y
as the formal args. - The
lambdass
package uses the the..1
,..2
argument form in one of its implementations.
- e.g.
- Arguments are parsed from the expression which becomes the function body
- .e.g the one-sided formula from
gsubfn
- .e.g the one-sided formula from
- Arguments are pre-defined
- Implicit Arguments with discrimination: arguments are parsed from the given expression, and there
is some way to indicate whether this is a variable from the enclosing environment or is a formal argument.
- Only Jim Hester’s
lambda
does this. Variables specified using thebquote()
syntax of.(x)
are considered formal arguments, everything else is a variable from the enclosing environment.
- Only Jim Hester’s
- Function Creation
- Function Call: Use a short function call to hide the explicit call to
function()
.- This is the most common route of creation e.g.
rlang::as_function()
,pryr::f()
- This is the most common route of creation e.g.
- Exsiting Operator: Existing operator is repurposed to create function
- e.g.
lambdass
hijacks the operation of~
- e.g. Lionel Henry’s syntax idea hijacks the
->
operator - e.g. Konrad Rudolph’s
functional
package/module also hijacks the->
operator and thus is only useable in code which uses=
for assignment.
- e.g.
- New operator: New operator used to create function
- Only
lambdass
does this with the%->%
infix operator
- Only
- Function Call: Use a short function call to hide the explicit call to
Notes on current implementations
Some of my thoughts/notes on the current implementations
- Base R
- Some would consider having to write out
function() {}
makes this a verbose syntax. Others would say I’m wrong.
- The
function()
text and the separate explicit list of formal arguments seems mostly redundant for a large percentage of cases where anonymous functions must be used i.e. single formal argument and small function body.
- Some would consider having to write out
rlang::as_function()
- Very handy within the
purrr
family of functions. - BUT: No ability to customise argument names!
- Very handy within the
pryr::f()
- Supports both implicit and explicit formal arguments
- Doesn’t use the leading
~
likepurrr/rlang
.
nofrills::fn()
- Only explicit formal arguments
- Uses
~
- Full support for
tidyverse
style Non-standard evaluation! This seems like overkill for my use case.
gsubfn::as.function.formula()
- Supports both implicit and explicit formal arguments
- Uses a leading
~
likepurrr/rlang
. - When using explicit formal args, the LHS (confusingly) looks like an expression itself!
wrapr::lambda()
- Explicit formal arguments only
- Doesn’t use leading
~
- Jim Hester’s lambda
- I like that it allows you to explicitly indicate variables in the expression body
that should be formal arguments using
bquote()
substitution syntax. I just find thebquote()
substitution syntax too fiddly/verbose!
- I like that it allows you to explicitly indicate variables in the expression body
that should be formal arguments using
lambdass::f.()
- Explicit formal arguments only
- The other two forms for creating anonymous functions are a bit too funky for my liking!
- Overriding the operation of
~
seems fraught with issues - the
%->%
operator seems too fiddly/verbose
- Overriding the operation of
functional
module- The use of the forward assignment operator looks nice and mimics how other languages might do an anonymous function.
- However, because it uses the
->
, it actually messes with<-
as well, so you have to use=
to do actual assignment in your code if you use this.
Thoughts on unifying implementation
After having looked at the existing implementations, here is my short list of what I think is needed for a good anonymous function implementation in R
- Use a function call to create the anonymous function - don’t use operator overloading or create a new operator.
- Allow both explicit and implicit formal arguments to give brevity when formal arguments are obvious, but flexibilty to explicitly define things
- Compatibility with
rlang
syntax. Ultimately,purrr::map
is where I want to use anonymous functions the most, so it’d be nice if this could be a drop-in replacement.
Next steps
Future post: What might a unifying implementation look like?