Learn R Programming

nofrills (version 0.3.2)

fn: Low-cost anonymous functions

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:

  • type less:

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

    are equivalent

  • guard against changes in lexical scope: by enabling tidyverse quasiquotation, fn() allows you to “burn in” values at the point of function creation (see Pure functions via quasiquotation)

Usage

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

Arguments

...

Function declaration, which supports quasiquotation.

..env

Environment in which to create the function (i.e., the function<U+2019>s enclosing environment).

Value

A function whose enclosing environment is ..env.

Function declarations

A function declaration is an expression that specifies a function<U+2019>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 below.)

  • 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 !!!:

        fn(!!!alist(x, y = 0), ~ x + y)

    (Note that the body, in this case, must be given as a one-sided formula.)

  • To write literal unquoting operators, use QUQ(), QUQS():

        library(dplyr)

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

(Source: Programming with dplyr)

Pure functions via quasiquotation

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 a function may depend on mutable objects in its lexical scope. Normally this isn<U+2019>t an issue. But if you are working interactively and sourcing files into the global environment, say, or using a notebook interface (like Jupyter or R Notebook), it can be tricky to ensure that you haven<U+2019>t unwittingly mutated an object that an earlier function depends upon.

Example <U+2014> 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.

fn() enables you to write pure functions by using quasiquotation to eliminate such indeterminacy.

Example <U+2014> 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 in its lexical scope:

    foo(1)  #> [1] 2
    a <- 0
    foo(1)  #> [1] 2

See Also

as_fn(), make_fn_aware(), curry_fn()

Examples

Run this code
# NOT RUN {
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
if (suppressWarnings(require(dplyr))) {
  summariser <- quote(mean)

  my_summarise <- fn(df, ... ~ {
    group_by <- quos(...)
    df %>%
      group_by(QUQS(group_by)) %>%
      summarise(a = `!!`(summariser)(a))
  })

  my_summarise
}

# }

Run the code above in your browser using DataLab