compose {gestalt}R Documentation

Compose Functions

Description

To compose functions,

Use as.list() to recover the list of composite functions. For example, both

  as.list(compose(paste, capitalize = toupper))

  as.list(paste %>>>% capitalize: toupper)

return the (named) list of functions list(paste, capitalize = toupper).

Usage

compose(...)

fst %>>>% snd

Arguments

...

Functions or lists thereof to compose, in order of application. Lists of functions are automatically spliced in. Unquoting of names, via ⁠!!⁠ on the left-hand side of ⁠:=⁠, and splicing, via ⁠!!!⁠, are supported.

fst, snd

Functions. These may be optionally named using a colon (:), e.g., f %>>>% nm: g names the g-component "nm" (see ‘Exceptions to the Interpretation of Calls as Functions’). Quasiquotation and the magrittr `%>%` semantics are supported (see ‘Semantics of the Composition Operator’, ‘Quasiquotation’ and ‘Examples’).

Value

Function of class CompositeFunction, whose formals are those of the first function applied (as a closure).

Semantics of the Composition Operator

The `%>>>%` operator adopts the semantics of the magrittr `%>%` operator:

  1. Bare names are matched to functions: For example, in a composition like

      ... %>>>% foo %>>>% ...
    

    the ‘foo’ is matched to the function of that name.

  2. Function calls are interpreted as a unary function of a point (.): A call is interpreted as a function (of a point) in one of two ways:

    • If the point matches an argument value, the call is literally interpreted as the body of the function. For example, in the compositions

        ... %>>>% foo(x, .) %>>>% ...
      
        ... %>>>% foo(x, y = .) %>>>% ...
      

      the ‘foo(x, .)’, resp. ‘foo(x, y = .)’, is interpreted as the function function(..., . = ..1) foo(x, .), resp. function(..., . = ..1) foo(x, y = .).

    • Otherwise, the call is regarded as implicitly having the point as its first argument before being interpreted as the body of the function. For example, in the compositions

        ... %>>>% foo(x) %>>>% ...
      
        ... %>>>% foo(x, y(.)) %>>>% ...
      

      the ‘foo(x)’, resp. ‘foo(x, y(.))’, is interpreted as the function function(..., . = ..1) foo(., x), resp. function(..., . = ..1) foo(., x, y(.)).

  3. Expressions {...} are interpreted as a function of a point (.): For example, in a composition

      ... %>>>% {
        foo(.)
        bar(.)
      } %>>>% ...
    

    the ‘{foo(.); bar(.)}’ is interpreted as the function function(..., . = ..1) {foo(.); bar(.)}.

    Curly braces are useful when you need to circumvent `%>>>%`'s usual interpretation of function calls. For example, in a composition

      ... %>>>% {foo(x, y(.))} %>>>% ...
    

    the ‘{foo(x, y(.))}’ is interpreted as the function function(..., . = ..1) foo(x, y(.)). There is no point as first argument to foo.

Exceptions to the Interpretation of Calls as Functions

As a matter of convenience, some exceptions are made to the above interpretation of calls as functions:

Quasiquotation

The `%>>>%` operator supports Tidyverse unquoting (via ⁠!!⁠). Use it to:

Operate on Composite Functions as List-Like Objects

You can think of a composite function as embodying the (possibly nested) structure of its list of constituent functions. In fact, you can apply familiar index and assignment operations to a composite function, as if it were this list, getting a function in return. This enables you to leverage composite functions as structured computations.

Indexing

For instance, the ‘sum’ in the following composite function

  f <- abs %>>>% out: (log %>>>% agg: sum)

can be extracted in the usual ways:

  f[[2]][[2]]
  f[[c(2, 2)]]

  f$out$agg
  f[["out"]][["agg"]]
  f[["out"]]$agg

  f$out[[2]]
  f[[list("out", 2)]]

The last form of indexing with a mixed list is handy when you need to create an index programmatically.

Additionally, you can excise sub-composite functions with [, head(), tail(). For example:

Subset Assignment

Similarily, subset assignment works as it does for lists. For instance, you can replace the ‘sum’ with the identity function:

  f[[2]][[2]] <- identity

  f$out$agg <- identity
  f[["out"]][["agg"]] <- identity

  f$out[[2]] <- identity
  f[[list("out", 2)]] <- identity

Multiple constituent functions can be reassigned using [<-. For example

  f[2] <- list(log)

  f["out"] <- list(log)

  f[c(FALSE, TRUE)] <- list(log)

all replace the second constituent function with log, so that f becomes abs %>>>% log.

Other List Methods

The generic methods unlist(), length(), names() also apply to composite functions. In conjunction with compose(), you can use unlist() to “flatten” compositions. For example

  compose(unlist(f, use.names = FALSE))

gives a function that is identical to

  abs %>>>% log %>>>% sum

Composite Functions Balance Speed and Complexity

The speed of a composite function made by compose() or `%>>>%` (regardless of its nested depth) is on par with a manually constructed serial composition. This is because compose() and `%>>>%` are associative, semantically and operationally. For instance, triple compositions,

  compose(f, g, h)
  f %>>>% g %>>>% h

  compose(f, compose(g, h))
  f %>>>% (g %>>>% h)

  compose(compose(f, g), h)
  (f %>>>% g) %>>>% h

are all implemented as the same function. Lists of functions are automatically “flattened” when composed.

Nevertheless, the original nested structure of constituent functions is faithfully recovered by as.list(). In particular, as.list() and compose() are mutually invertible: as.list(compose(fs)) is the same as fs, when fs is a (nested) list of functions. (But note that the names of the list of composite functions is always a character vector; it is never NULL.)

See Also

constant(); combined with `%>>>%`, this provides a lazy, structured alternative to the magrittr `%>%` operator.

Examples

# Functions are applied in the order in which they are listed
inv <- partial(`/`, 1)  # reciprocal
f0 <- compose(abs, log, inv)
stopifnot(all.equal(f0(-2), 1 / log(abs(-2))))

# Alternatively, compose using the `%>>>%` operator
f1 <- abs %>>>% log %>>>% {1 / .}
stopifnot(all.equal(f1(-2), f0(-2)))

## Not run: 
# Transform a function to a JSON function
library(jsonlite)

# By composing higher-order functions:
jsonify <- {fromJSON %>>>% .} %>>>% {. %>>>% toJSON}

# By directly composing with input/output transformers:
jsonify <- fn(f ~ fromJSON %>>>% f %>>>% toJSON)
## End(Not run)

# Formals of initial function are preserved
add <- function(a, b = 0) a + b
stopifnot(identical(formals(compose(add, inv)), formals(add)))

# Compositions can be provided by lists, in several equivalent ways
f2 <- compose(list(abs, log, inv))
f3 <- compose(!!! list(abs, log, inv))
f4 <- compose(abs, list(log, inv))
f5 <- compose(abs, !!! list(log, inv))
stopifnot(
  all.equal(f2, f0), all.equal(f2(-2), f0(-2)),
  all.equal(f3, f0), all.equal(f3(-2), f0(-2)),
  all.equal(f4, f0), all.equal(f4(-2), f0(-2)),
  all.equal(f5, f0), all.equal(f5(-2), f0(-2))
)

# compose() and as.list() are mutally invertible
f6 <- compose(abs, as.list(compose(log, inv)))
stopifnot(
  all.equal(f6, f0), all.equal(f6(-2), f0(-2))
)
fs <- list(abs, log, inv)
stopifnot(all.equal(check.attributes = FALSE,
  as.list(compose(fs)), fs,
))

# `%>>>%` supports names, magrittr `%>%` semantics, and quasiquotation
sep <- ""
scramble <- shuffle: sample %>>>% paste(collapse = !!sep)
nonsense <- scramble(letters)
stopifnot(
  nchar(nonsense) == 26L,
  identical(letters, sort(strsplit(nonsense, sep)[[1]])),
  identical(scramble$shuffle, sample)
)


[Package gestalt version 0.2.0 Index]