R/fn.R

Defines functions op_as_closure prim_args as_closure is_lambda print.rlang_lambda_function as_function `fn_env<-` fn_env is_primitive_lazy is_primitive_eager is_primitive is_closure is_function fn_body_node `fn_body<-` fn_body `fn_fmls_names<-` `fn_fmls<-` fn_fmls_syms fn_fmls_names fn_fmls prim_name is_prim_eval new_function

Documented in as_closure as_function fn_body fn_env fn_fmls fn_fmls_names fn_fmls_syms is_closure is_function is_lambda is_primitive is_primitive_eager is_primitive_lazy new_function prim_name

#' Create a function
#'
#' @description
#'
#' This constructs a new function given its three components:
#' list of arguments, body code and parent environment.
#'
#' @param args A named list or pairlist of default arguments. Note
#'   that if you want arguments that don't have defaults, you'll need
#'   to use the special function [pairlist2()]. If you need quoted
#'   defaults, use [exprs()].
#' @param body A language object representing the code inside the
#'   function. Usually this will be most easily generated with
#'   [base::quote()]
#' @param env The parent environment of the function, defaults to the
#'   calling environment of `new_function()`
#' @export
#' @examples
#' f <- function() letters
#' g <- new_function(NULL, quote(letters))
#' identical(f, g)
#'
#' # Pass a list or pairlist of named arguments to create a function
#' # with parameters. The name becomes the parameter name and the
#' # argument the default value for this parameter:
#' new_function(list(x = 10), quote(x))
#' new_function(pairlist2(x = 10), quote(x))
#'
#' # Use `exprs()` to create quoted defaults. Compare:
#' new_function(pairlist2(x = 5 + 5), quote(x))
#' new_function(exprs(x = 5 + 5), quote(x))
#'
#' # Pass empty arguments to omit defaults. `list()` doesn't allow
#' # empty arguments but `pairlist2()` does:
#' new_function(pairlist2(x = , y = 5 + 5), quote(x + y))
#' new_function(exprs(x = , y = 5 + 5), quote(x + y))
new_function <- function(args, body, env = caller_env()) {
  .Call(ffi_new_function, args, body, env)
}

prim_eval <- eval(quote(sys.function(0)))
is_prim_eval <- function(x) identical(x, prim_eval)

#' Name of a primitive function
#' @param prim A primitive function such as [base::c()].
#' @keywords internal
#' @export
prim_name <- function(prim) {
  stopifnot(is_primitive(prim))

  # Workaround because R_FunTab is not public
  name <- format(prim)

  # TERR formats primitives as `.Native("name")` (#958)
  name <- sub("^\\.(Primitive|Native)\\(\"", "", name)
  name <- sub("\"\\)$", "", name)
  name
}

#' Extract arguments from a function
#'
#' `fn_fmls()` returns a named list of formal arguments.
#' `fn_fmls_names()` returns the names of the arguments.
#' `fn_fmls_syms()` returns formals as a named list of symbols. This
#' is especially useful for forwarding arguments in [constructed
#' calls][lang].
#'
#' Unlike `formals()`, these helpers throw an error with primitive
#' functions instead of returning `NULL`.
#'
#' @param fn A function. It is looked up in the calling frame if not
#'   supplied.
#' @seealso [call_args()] and [call_args_names()]
#' @export
#' @examples
#' # Extract from current call:
#' fn <- function(a = 1, b = 2) fn_fmls()
#' fn()
#'
#' # fn_fmls_syms() makes it easy to forward arguments:
#' call2("apply", !!! fn_fmls_syms(lapply))
#'
#' # You can also change the formals:
#' fn_fmls(fn) <- list(A = 10, B = 20)
#' fn()
#'
#' fn_fmls_names(fn) <- c("foo", "bar")
#' fn()
fn_fmls <- function(fn = caller_fn()) {
  check_closure(fn)
  formals(fn)
}
#' @rdname fn_fmls
#' @export
fn_fmls_names <- function(fn = caller_fn()) {
  args <- fn_fmls(fn)
  names(args)
}
#' @rdname fn_fmls
#' @export
fn_fmls_syms <- function(fn = caller_fn()) {
  fmls_nms <- fn_fmls_names(fn)
  if (is_null(fmls_nms)) {
    return(list())
  }

  nms <- set_names(fmls_nms)
  names(nms)[match("...", nms)] <- ""
  syms(nms)
}

#' @rdname fn_fmls
#' @param value New formals or formals names for `fn`.
#' @export
`fn_fmls<-` <- function(fn, value) {
  check_closure(fn)
  attrs <- attributes(fn)

  formals(fn) <- value

  # Work around bug in base R
  attributes(fn) <- attrs

  fn
}
#' @rdname fn_fmls
#' @export
`fn_fmls_names<-` <- function(fn, value) {
  check_closure(fn)
  attrs <- attributes(fn)

  fmls <- formals(fn)
  names(fmls) <- value
  formals(fn) <- fmls

  # Work around bug in base R
  attributes(fn) <- attrs

  fn
}

#' Get or set function body
#'
#' `fn_body()` is a simple wrapper around [base::body()]. It always
#' returns a `\{` expression and throws an error when the input is a
#' primitive function (whereas `body()` returns `NULL`). The setter
#' version preserves attributes, unlike `body<-`.
#'
#' @inheritParams fn_fmls
#'
#' @export
#' @examples
#' # fn_body() is like body() but always returns a block:
#' fn <- function() do()
#' body(fn)
#' fn_body(fn)
#'
#' # It also throws an error when used on a primitive function:
#' try(fn_body(base::list))
fn_body <- function(fn = caller_fn()) {
  check_closure(fn)

  body <- body(fn)

  if (is_call(body, "{")) {
    body
  } else {
    call("{", body)
  }
}
#' @rdname fn_body
#' @export
`fn_body<-` <- function(fn, value) {
  attrs <- attributes(fn)

  body(fn) <- value

  # Work around bug in base R. First remove source references since
  # the body has changed
  attrs$srcref <- NULL
  attributes(fn) <- attrs

  fn
}

fn_body_node <- function(fn) {
  body <- body(fn)
  if (is_call(body, "{")) {
    node_cdr(fn)
  } else {
    pairlist(body)
  }
}

#' Is object a function?
#'
#' The R language defines two different types of functions: primitive
#' functions, which are low-level, and closures, which are the regular
#' kind of functions.
#'
#' Closures are functions written in R, named after the way their
#' arguments are scoped within nested environments (see
#' <https://en.wikipedia.org/wiki/Closure_(computer_programming)>). The
#' root environment of the closure is called the closure
#' environment. When closures are evaluated, a new environment called
#' the evaluation frame is created with the closure environment as
#' parent. This is where the body of the closure is evaluated. These
#' closure frames appear on the evaluation stack, as opposed to
#' primitive functions which do not necessarily have their own
#' evaluation frame and never appear on the stack.
#'
#' Primitive functions are more efficient than closures for two
#' reasons. First, they are written entirely in fast low-level
#' code. Second, the mechanism by which they are passed arguments is
#' more efficient because they often do not need the full procedure of
#' argument matching (dealing with positional versus named arguments,
#' partial matching, etc). One practical consequence of the special
#' way in which primitives are passed arguments is that they
#' technically do not have formal arguments, and [formals()] will
#' return `NULL` if called on a primitive function. Finally, primitive 
#' functions can either take arguments lazily, like R closures do, 
#' or evaluate them eagerly before being passed on to the C code. 
#' The former kind of primitives are called "special" in R terminology, 
#' while the latter is referred to as "builtin". `is_primitive_eager()` 
#' and `is_primitive_lazy()` allow you to check whether a primitive 
#' function evaluates arguments eagerly or lazily.
#'
#' You will also encounter the distinction between primitive and
#' internal functions in technical documentation. Like primitive
#' functions, internal functions are defined at a low level and
#' written in C. However, internal functions have no representation in
#' the R language. Instead, they are called via a call to
#' [base::.Internal()] within a regular closure. This ensures that
#' they appear as normal R function objects: they obey all the usual
#' rules of argument passing, and they appear on the evaluation stack
#' as any other closures. As a result, [fn_fmls()] does not need to
#' look in the `.ArgsEnv` environment to obtain a representation of
#' their arguments, and there is no way of querying from R whether
#' they are lazy ('special' in R terminology) or eager ('builtin').
#'
#' You can call primitive functions with [.Primitive()] and internal
#' functions with [.Internal()]. However, calling internal functions
#' in a package is forbidden by CRAN's policy because they are
#' considered part of the private API. They often assume that they
#' have been called with correctly formed arguments, and may cause R
#' to crash if you call them with unexpected objects.
#'
#' @inheritParams type-predicates
#' @export
#' @examples
#' # Primitive functions are not closures:
#' is_closure(base::c)
#' is_primitive(base::c)
#'
#' # On the other hand, internal functions are wrapped in a closure
#' # and appear as such from the R side:
#' is_closure(base::eval)
#'
#' # Both closures and primitives are functions:
#' is_function(base::c)
#' is_function(base::eval)
is_function <- function(x) {
  .Call(ffi_is_function, x)
}

#' @export
#' @rdname is_function
is_closure <- function(x) {
  .Call(ffi_is_closure, x)
}

#' @export
#' @rdname is_function
is_primitive <- function(x) {
  .Call(ffi_is_primitive, x)
}
#' @export
#' @rdname is_function
#' @examples
#'
#' # Many primitive functions evaluate arguments eagerly:
#' is_primitive_eager(base::c)
#' is_primitive_eager(base::list)
#' is_primitive_eager(base::`+`)
is_primitive_eager <- function(x) {
  .Call(ffi_is_primitive_eager, x)
}
#' @export
#' @rdname is_function
#' @examples
#'
#' # However, primitives that operate on expressions, like quote() or
#' # substitute(), are lazy:
#' is_primitive_lazy(base::quote)
#' is_primitive_lazy(base::substitute)
is_primitive_lazy <- function(x) {
  .Call(ffi_is_primitive_lazy, x)
}


#' Return the closure environment of a function
#'
#' Closure environments define the scope of functions (see [env()]).
#' When a function call is evaluated, R creates an evaluation frame
#' that inherits from the closure environment. This makes all objects
#' defined in the closure environment and all its parents available to
#' code executed within the function.
#'
#' `fn_env()` returns the closure environment of `fn`. There is also
#' an assignment method to set a new closure environment.
#'
#' @param fn,x A function.
#' @param value A new closure environment for the function.
#' @export
#' @examples
#' env <- child_env("base")
#' fn <- with_env(env, function() NULL)
#' identical(fn_env(fn), env)
#'
#' other_env <- child_env("base")
#' fn_env(fn) <- other_env
#' identical(fn_env(fn), other_env)
fn_env <- function(fn) {
  if (is_primitive(fn)) {
    return(ns_env("base"))
  }

  if(is_closure(fn)) {
    return(environment(fn))
  }

  check_function(fn)
}

#' @export
#' @rdname fn_env
`fn_env<-` <- function(x, value) {
  check_function(x)
  environment(x) <- value
  x
}

#' Convert to function
#'
#' @description
#' `as_function()` transforms a one-sided formula into a function.
#' This powers the lambda syntax in packages like purrr.
#'
#' @param x A function or formula.
#'
#'   If a **function**, it is used as is.
#'
#'   If a **formula**, e.g. `~ .x + 2`, it is converted to a function
#'   with up to two arguments: `.x` (single argument) or `.x` and `.y`
#'   (two arguments). The `.` placeholder can be used instead of `.x`.
#'   This allows you to create very compact anonymous functions (lambdas) with up
#'   to two inputs. Functions created from formulas have a special
#'   class. Use `is_lambda()` to test for it.
#'
#'   If a **string**, the function is looked up in `env`. Note that
#'   this interface is strictly for user convenience because of the
#'   scoping issues involved. Package developers should avoid
#'   supplying functions by name and instead supply them by value.
#'
#' @param env Environment in which to fetch the function in case `x`
#'   is a string.
#' @inheritParams args_dots_empty
#' @inheritParams args_error_context
#' @export
#' @examples
#' f <- as_function(~ .x + 1)
#' f(10)
#'
#' g <- as_function(~ -1 * .)
#' g(4)
#'
#' h <- as_function(~ .x - .y)
#' h(6, 3)
#'
#' # Functions created from a formula have a special class:
#' is_lambda(f)
#' is_lambda(as_function(function() "foo"))
as_function <- function(x,
                        env = global_env(),
                        ...,
                        arg = caller_arg(x),
                        call = caller_env()) {
  check_dots_empty0(...)

  if (is_function(x)) {
    return(x)
  }

  if (is_quosure(x)) {
    mask <- eval_tidy(call2(environment), env = quo_get_env(x))
    fn <- new_function(pairlist2(... = ), quo_get_expr(x), mask)
    return(fn)
  }

  if (is_formula(x)) {
    if (length(x) > 2) {
      abort_coercion(
        x,
        x_type = "a two-sided formula",
        to_type = "a function",
        arg = arg,
        call = call
      )
    }

    env <- f_env(x)
    if (!is_environment(env)) {
      abort("Formula must carry an environment.", arg = arg, call = call)
    }

    args <- list(... = missing_arg(), .x = quote(..1), .y = quote(..2), . = quote(..1))
    fn <- new_function(args, f_rhs(x), env)
    fn <- structure(fn, class = c("rlang_lambda_function", "function"))
    return(fn)
  }

  if (is_string(x)) {
    return(get(x, envir = env, mode = "function"))
  }

  abort_coercion(x, "a function", arg = arg, call = call)
}
#' @export
print.rlang_lambda_function <- function(x, ...) {
  cat_line("<lambda>")
  NextMethod()
}
#' @rdname as_function
#' @export
is_lambda <- function(x) {
  inherits(x, "rlang_lambda_function")
}

#' Transform to a closure
#'
#' `as_closure()` is like [as_function()] but also wraps primitive
#' functions inside closures. Some special control flow primitives
#' like `if`, `for`, or `break` can't be wrapped and will cause an
#' error.
#'
#' @inheritParams as_function
#'
#' @examples
#' # Primitive functions are regularised as closures
#' as_closure(list)
#' as_closure("list")
#'
#' # Operators have `.x` and `.y` as arguments, just like lambda
#' # functions created with the formula syntax:
#' as_closure(`+`)
#' as_closure(`~`)
#'
#' @keywords internal
#' @export
as_closure <- function(x, env = caller_env()) {
  x <- as_function(x, env = env)

  if (is_closure(x)) {
    return(x)
  }
  if (!is_primitive(x)) {
    abort_coercion(x, "a closure")
  }

  fn_name <- prim_name(x)
  fn <- op_as_closure(fn_name)

  if (!is_null(fn)) {
    return(fn)
  }

  fmls <- formals(args(fn_name))
  prim_call <- call2(x, !!!prim_args(fmls))

  # The closure wrapper should inherit from the global environment
  # to ensure proper lexical dispatch with methods defined there
  new_function(fmls, prim_call, global_env())
}

prim_args <- function(fmls) {
  args <- names(fmls)

  # Set argument names but only after `...`. Arguments before dots
  # should be positionally matched.
  dots_i <- match("...", args)
  if (!is_na(dots_i)) {
    idx <- seq2(dots_i + 1L, length(args))
    names2(args)[idx] <- args[idx]
  }

  syms(args)
}

utils::globalVariables(c("!<-", "(<-", "enexpr<-"))

op_as_closure <- function(prim_nm) {
  switch(prim_nm,
    `<-` = ,
    `<<-` = ,
    `=` = function(.x, .y) {
      op <- sym(prim_nm)
      expr <- expr((!!op)(!!enexpr(.x), !!enexpr(.y)))
      eval_bare(expr, caller_env())
    },
    `@` = ,
    `$` = function(.x, .i) {
      op <- sym(prim_nm)
      expr <- expr((!!op)(.x, !!quo_squash(enexpr(.i), warn = TRUE)))
      eval_bare(expr)
    },
    `[[<-` = function(.x, .i, .value) {
      expr <- expr((!!enexpr(.x))[[!!enexpr(.i)]] <- !!enexpr(.value))
      eval_bare(expr, caller_env())
    },
    `[<-` = function(.x, ...) {
      args <- exprs(...)
      n <- length(args)
      if (n < 2L) {
        abort("Must supply operands to `[<-`.")
      }
      expr <- expr((!!enexpr(.x))[!!!args[-n]] <- !!args[[n]])
      eval_bare(expr, caller_env())
    },
    `@<-` = function(.x, .i, .value) {
      expr <- expr(`@`(!!enexpr(.x), !!enexpr(.i)) <- !!enexpr(.value))
      eval_bare(expr, caller_env())
    },
    `$<-` = function(.x, .i, .value) {
      expr <- expr(`$`(!!enexpr(.x), !!enexpr(.i)) <- !!enexpr(.value))
      eval_bare(expr, caller_env())
    },
    `(` = function(.x) .x,
    `[` = function(.x, ...) .x[...],
    `[[` = function(.x, ...) .x[[...]],
    `{` = function(...) {
      values <- list(...)
      values[[length(values)]]
    },
    `&`  = new_binary_closure(function(.x, .y) .x & .y),
    `|`  = new_binary_closure(function(.x, .y) .x | .y),
    `&&` = new_binary_closure(function(.x, .y) .x && .y),
    `||` = new_binary_closure(function(.x, .y) .x || .y, shortcircuiting = TRUE),
    `!`  = function(.x) !.x,
    `+`  = new_binary_closure(function(.x, .y) if (missing(.y)) .x else .x + .y, versatile = TRUE),
    `-`  = new_binary_closure(function(.x, .y) if (missing(.y)) -.x else .x - .y, versatile = TRUE),
    `*`  = new_binary_closure(function(.x, .y) .x * .y),
    `/`  = new_binary_closure(function(.x, .y) .x / .y),
    `^`  = new_binary_closure(function(.x, .y) .x ^ .y),
    `%%` = new_binary_closure(function(.x, .y) .x %% .y),
    `<`  = new_binary_closure(function(.x, .y) .x < .y),
    `<=` = new_binary_closure(function(.x, .y) .x <= .y),
    `>`  = new_binary_closure(function(.x, .y) .x > .y),
    `>=` = new_binary_closure(function(.x, .y) .x >= .y),
    `==` = new_binary_closure(function(.x, .y) .x == .y),
    `!=` = new_binary_closure(function(.x, .y) .x != .y),
    `:`  = new_binary_closure(function(.x, .y) .x : .y),
    `~`  = function(.x, .y) {
      if (is_missing(substitute(.y))) {
        new_formula(NULL, substitute(.x), caller_env())
      } else {
        new_formula(substitute(.x), substitute(.y), caller_env())
      }
    },

    `c` = function(...) c(...),
    seq.int = function(from = 1L, to = from, ...) seq.int(from, to, ...),

    # Unsupported primitives
    `break` = ,
    `for` = ,
    `function` = ,
    `if` = ,
    `next` = ,
    `repeat` = ,
    `return` = ,
    `while` = {
      nm <- chr_quoted(prim_nm)
      abort(paste0("Can't coerce the primitive function ", nm, " to a closure."))
    }
  )
}

new_binary_closure <- function(fn,
                               versatile = FALSE,
                               shortcircuiting = FALSE) {
  if (versatile) {
    nodes <- versatile_check_nodes
  } else if (shortcircuiting) {
    nodes <- shortcircuiting_check_nodes
  } else {
    nodes <- binary_check_nodes
  }

  nodes <- duplicate(nodes, shallow = TRUE)
  nodes <- node_append(nodes, fn_body_node(fn))
  body <- new_call(brace_sym, nodes)

  formals(fn) <- binary_fmls
  body(fn) <- body

  fn
}

binary_fmls <- as.pairlist(alist(
  e1 = ,
  e2 = ,
  .x = e1,
  .y = e2
))
binary_check_nodes <- pairlist(
  quote(
    if (missing(.x)) {
      if (missing(e1)) {
        abort("Must supply `e1` or `.x` to binary operator.")
      }
      .x <- e1
    } else if (!missing(e1)) {
      abort("Can't supply both `e1` and `.x` to binary operator.")
    }
  ),
  quote(
    if (missing(.y)) {
      if (missing(e2)) {
        abort("Must supply `e2` or `.y` to binary operator.")
      }
      .y <- e2
    } else if (!missing(e2)) {
      abort("Can't supply both `e2` and `.y` to binary operator.")
    }
  )
)
versatile_check_nodes <- as.pairlist(c(
  binary_check_nodes[[1]],
  quote(
    if (missing(.y) && !missing(e2)) {
      .y <- e2
    } else if (!missing(e2)) {
      abort("Can't supply both `e2` and `.y` to binary operator.")
    }
  )
))
shortcircuiting_check_nodes <- as.pairlist(c(
  binary_check_nodes[[1]],
  quote(if (.x) return(TRUE)),
  binary_check_nodes[[2]]
))

as_predicate <- function(.fn, ...) {
  .fn <- as_function(.fn)

  function(...) {
    out <- .fn(...)

    if (!is_bool(out)) {
      abort(sprintf(
        "Predicate functions must return a single `TRUE` or `FALSE`, not %s",
        as_predicate_friendly_type_of(out)
      ))
    }

    out
  }
}
as_predicate_friendly_type_of <- function(x) {
  if (is_na(x)) {
    "a missing value"
  } else {
    obj_type_friendly(x)
  }
}

Try the rlang package in your browser

Any scripts or data that you put into this service are public.

rlang documentation built on Nov. 4, 2023, 9:06 a.m.