Anonymous Functions in R - Part 3: Introducing the 'anon' package

An Anonymous Function (also known as a lambda expression) 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 ssatisfied with purrr/rlang’s anonymous function syntax as it has no capability for naming the function arguments anything other than .x and .y.

In a follow-up post, I looked at alternate syntaxes for creating anonymous functions.

In this post, I

  • outline what I think would unify the anonymous function implementations
  • introduce the ‘anon’ package

Thoughts on a unifying anonymous function implementation

After looking at the other syntaxes for creating anonymous functions, my ideas for a unifying anonymous function implementation are as follows:

  1. Use a short function to create other anonymous functions
    • This is mostly personal preference.
    • Overloading an existing operator seems fraught with compatibility issues.
  2. Support both implicit and explicit formal arguments.
    • Supporting only one or the other limits the flexibility.
    • I want the generation of arguments to work automatically when it’s obvious what is meant, but still retain the ability to be explicit when more clarifying information is needed about the ordering/existence of variables as the formal arguments.
  3. For explicit formal arguments allow multiple representations i.e. to be familiar across more existing packages, the following should all create the same anonymous function
    • f(x, y, x + y)
    • f(x, y ~ x + y)
    • f(x, y, ~x + y)
    • f(x + y ~ x + y)
  4. Try and match purrr/rlang’s syntax as close as possible
    • This seems to be the most popular anonymous function syntax, so don’t rock the boat by being too different.
    • Yet still be valid without the leading tilde
    • I.e. the following should all create the same anonymous function
      • f(x + y)
      • f(~x + y)
      • f(~.x + .y)

Introducing the anon package for anonymous functions in R

The anon package provides two functions to help define succinct anonymous functions (also called lambdas).

Features

anon provides two user-facing functions:

  1. lambda() for easily and compactly creating lambda expressions in R, while being broadly compatible with a number of existing lambda syntaxes. This function is also aliased to L() for the sake of brevity.
  2. patch_purrr_mapper() to patch purrr to allow anon to convert formulas to functions instead of rlang::as_function(). Using anon to convert formulas-to-functions enables:
    • automatic argument extraction (rather than forcing the arguments to be .x and .y)
    • explicit arguments through the use of 2 sided formulas i.e. the formal arguments are extracted from the expression on the LHS of a two-sided formula e.g. a:b ~ a + b + 1 becomes function(a, b) {a + b + 1}

Installation

You can install anon from github with:

remotes::install_github("coolbutuseless/anon")

Example use of lambda()/L() for creating anonymous functions

The following calls all create the same anonymous function:

  • function(x, y) x + y
  • L(.x + .y)
  • L(x + y)
  • L(~x + y)
  • L(x, y, x + y)
  • L(x, y ~ x + y)
  • L(x, y, ~x + y)
  • L(x:y ~ x + y)

Example use of patch_purrr_mapper() to enable extended functionality for lambdas in purrr

By running patch_purrr_mapper() an extra S3 method is added for purrr::as_mapper() i.e. as_mapper.formula().

By doing so, any formula-used-as-function in purrr will be passed to anon::as_function_formula() which allows for more expressiveness than rlang::as_function(). i.e.

  1. Can use any name for the variables, not just .x and .y
  2. Can set explicit formal arguments (by specifying the LHS of the formula)
  3. If no explicit formal arguments are given, then all variable names on the RHS of the formula are used as formal arguments. The order of the formal arguments is the same as the order of appearance of the variables.
  4. If .x or .y appear on the RHS and no explicit formal arguments are given on the LHS, then they are used as the first formal arguments (regardless of the order in which they appear on the RHS)
  5. You can use . as a stand-in for the first argument.
library(purrr)
anon::patch_purrr_mapper()

z <- 10L

map_int(1:3, ~. + 1L)
[1] 2 3 4

map_int(1:3, ~.x + 1L)
[1] 2 3 4

map_int(1:3, ~ value + 1L)
[1] 2 3 4

map_int(1:3, val ~ val + 1L)
[1] 2 3 4

map_int(1:3, val ~ val + z)
[1] 11 12 13

map2_dbl(1:3, 4:6, ~ .x / .y)
[1] 0.25 0.40 0.50

map2_dbl(1:3, 4:6, ~ a / b)
[1] 0.25 0.40 0.50

map2_dbl(1:3, 4:6, ~ b / a)
[1] 0.25 0.40 0.50

map2_dbl(1:3, 4:6, b:a ~ a / b)
[1] 4.0 2.5 2.0

pmap_int(list(1:2, 3:4, 5:6, 7:8), ~a * b * c * d)
[1] 105 384