fn: Function Declarations with Quasiquotation

fnR Documentation

Function Declarations with Quasiquotation

Description

fn() enables you to create (anonymous) functions, of arbitrary call signature. Use it in place of the usual function() invocation whenever you want to:

  • Be concise: The function declarations

      fn(x, y = 1 ~ x + y)
    
      function(x, y = 1) x + y
    

    are equivalent.

  • Enforce immutability: By enabling Tidyverse quasiquotation, fn() allows you to “burn in” values at the point of function creation. This guards against changes in a function's enclosing environment. (See ‘Use Unquoting to Make Robust Functions’.)

fn_() is a variant of fn() that does not comprehend quasiquotation. It is useful when you want unquoting (`!!`) or splicing (`!!!`) operators in the function body to be literally interpreted, rather than immediately invoked. (See ‘Quasiquotation’ for a complementary way to literally interpret unquoting and splicing operators in fn().)

Usage

fn(..., ..env = parent.frame())

fn_(..., ..env = parent.frame())

Arguments

...

Function declaration, which supports quasiquotation.

..env

Environment in which to create the function (i.e., the function's enclosing environment).

Value

A function whose enclosing environment is ..env.

Function Declarations

A function declaration is an expression that specifies a function's arguments and body, as a comma-separated expression of the form

  arg1, arg2, ..., argN ~ body

or

  arg1, arg2, ..., argN, ~body

(Note in the second form that the body is a one-sided formula. This distinction is relevant for argument splicing, see ‘Quasiquotation’.)

  • To the left of ~, you write a conventional function-argument declaration, just as in function(<arguments>): each of arg1, arg2, ..., argN is either a bare argument (e.g., x or ...) or an argument with default value (e.g., x = 1).

  • To the right of ~, you write the function body, i.e., an expression of the arguments.

Quasiquotation

All parts of a function declaration support Tidyverse quasiquotation:

  • To unquote values (of arguments or parts of the body), use !!:

      z <- 0
      fn(x, y = !!z ~ x + y)
      fn(x ~ x > !!z)
    
  • To unquote argument names (with default value), use := (definition operator):

      arg <- "y"
      fn(x, !!arg := 0 ~ x + !!as.name(arg))
    
  • To splice in a (formal) list of arguments, use !!!:

      # NB: Body is a one-sided formula
      fn(!!!alist(x, y = 0), ~ x + y)
    

    Splicing allows you to treat a complete function declaration as a unit:

      soma <- alist(x, y = 0, ~ x + y)
      fn(!!!soma)
    
  • To write literal unquoting operators, use QUQ(), QUQS(), which read as “quoted unquoting,” “quoted unquote-splicing,” resp. (cf. fn_()):

      library(dplyr)
    
      my_summarise <- fn(df, ... ~ {
        groups <- quos(...)
        df %>%
          group_by(QUQS(groups)) %>%
          summarise(a = mean(a))
      })
    

    (Source: Programming with dplyr)

Use Unquoting to Make Robust Functions

Functions in R are generally impure, i.e., the return value of a function will not in general be determined by the value of its inputs alone. This is because, by design, a function may depend on objects in its lexical scope, and these objects may mutate between function calls. Normally this isn't a hazard.

However, if you are working interactively and sourcing files into the global environment, or using a notebook interface like Jupyter or R Notebook, it can be tricky to ensure that you haven't unwittingly mutated an object that an earlier function depends upon.

You can use unquoting to guard against such mutations.

Example

Consider the following function:

  a <- 1
  foo <- function(x) x + a

What is the value of foo(1)? It is not necessarily 2, because the value of a may have changed between the creation of foo() and the calling of foo(1):

  foo(1)  #> [1] 2

  a <- 0

  foo(1)  #> [1] 1

In other words, foo() is impure because the value of foo(x) depends not only on the value of x but also on the externally mutable value of a.

With fn(), you can unquote a to “burn in” its value at the point of creation:

  a <- 1
  foo <- fn(x ~ x + !!a)

Now foo() is a pure function, unaffected by changes to a in the lexical scope:

  foo(1)  #> [1] 2

  a <- 0

  foo(1)  #> [1] 2

Examples

fn(x ~ x + 1)
fn(x, y ~ x + y)
fn(x, y = 2 ~ x + y)
fn(x, y = 1, ... ~ log(x + y, ...))

# to specify '...' in the middle, write '... = '
fn(x, ... = , y ~ log(x + y, ...))

# use one-sided formula for constant functions or commands
fn(~ NA)
fn(~ message("!"))

# unquoting is supported (using `!!` from rlang)
zero <- 0
fn(x = !!zero ~ x > !!zero)

# formals and function bodies can also be spliced in
f <- function(x, y) x + y
g <- function(y, x, ...) x - y
frankenstein <- fn(!!!formals(f), ~ !!body(g))
stopifnot(identical(frankenstein, function(x, y) x - y))

# mixing unquoting and literal unquoting is possible
# (Assume dplyr is available, which provides group_by() and `%>%`.)
summariser <- quote(mean)
my_summarise <- fn(df, ... ~ {
  groups <- quos(...)
  df %>%
    group_by(QUQS(groups)) %>%          # literal unquote-splice
    summarise(a = `!!`(summariser)(a))  # substitute `mean`
})
my_summarise

# Use fn_() with fn() as a concise way to force ("pin down") bindings
# For example, the 'x' is immutable in the function produced by call_upon():
call_upon <- fn_(x ~ fn(f ~ f(!!x)))
sapply(list(sin, cos), call_upon(0))  # [1] 0 1

# Return-value checking, as a functional transformation
enforce <- fn_(condition ~
  fn(x ~ {
    stopifnot(!!substitute(condition))
    x
  })
)
no_nan <- enforce(!is.nan(x))
log_strict <- fn(x ~ no_nan(log(x)))
log_strict(2)        # [1] 0.6931472
try(log_strict(-1))  # Error: !is.nan(x) is not TRUE


gestalt documentation built on Aug. 22, 2022, 5:08 p.m.