Title: | Lazy (Non-Standard) Evaluation |
---|---|
Description: | An alternative approach to non-standard evaluation using formulas. Provides a full implementation of LISP style 'quasiquotation', making it easier to generate code with other code. |
Authors: | Hadley Wickham [aut, cre], RStudio [cph] |
Maintainer: | Hadley Wickham <[email protected]> |
License: | GPL-3 |
Version: | 0.2.2 |
Built: | 2024-12-06 04:10:30 UTC |
Source: | https://github.com/hadley/lazyeval |
These are a S3 generics with built-in methods for names, calls, formuals, and strings. The distinction between a name and a call is particularly important when coercing from a string. Coercing to a call will parse the string, coercing to a name will create a (potentially) non-syntactic name.
as_name(x) as_call(x)
as_name(x) as_call(x)
x |
An object to coerce |
as_name("x + y") as_call("x + y") as_call(~ f) as_name(~ f())
as_name("x + y") as_call("x + y") as_call(~ f) as_name(~ f())
Convert an object to a lazy expression or lazy dots.
as.lazy(x, env = baseenv()) as.lazy_dots(x, env)
as.lazy(x, env = baseenv()) as.lazy_dots(x, env)
x |
An R object. Current methods for |
env |
Environment to use for objects that don't already have associated environment. |
as.lazy(~ x + 1) as.lazy(quote(x + 1), globalenv()) as.lazy("x + 1", globalenv()) as.lazy_dots(list(~x, y = ~z + 1)) as.lazy_dots(c("a", "b", "c"), globalenv()) as.lazy_dots(~x) as.lazy_dots(quote(x), globalenv()) as.lazy_dots(quote(f()), globalenv()) as.lazy_dots(lazy(x))
as.lazy(~ x + 1) as.lazy(quote(x + 1), globalenv()) as.lazy("x + 1", globalenv()) as.lazy_dots(list(~x, y = ~z + 1)) as.lazy_dots(c("a", "b", "c"), globalenv()) as.lazy_dots(~x) as.lazy_dots(quote(x), globalenv()) as.lazy_dots(quote(f()), globalenv()) as.lazy_dots(lazy(x))
ast_
takes a quoted expression; ast
does the quoting
for you.
ast_(x, width = getOption("width")) ast(x)
ast_(x, width = getOption("width")) ast(x)
x |
Quoted call, list of calls, or expression to display. |
width |
Display width, defaults to current width as reported by
|
ast(f(x, 1, g(), h(i()))) ast(if (TRUE) 3 else 4) ast(function(a = 1, b = 2) {a + b + 10}) ast(f(x)(y)(z)) ast_(quote(f(x, 1, g(), h(i())))) ast_(quote(if (TRUE) 3 else 4)) ast_(expression(1, 2, 3))
ast(f(x, 1, g(), h(i()))) ast(if (TRUE) 3 else 4) ast(function(a = 1, b = 2) {a + b + 10}) ast(f(x)(y)(z)) ast_(quote(f(x, 1, g(), h(i())))) ast_(quote(if (TRUE) 3 else 4)) ast_(expression(1, 2, 3))
Modify the arguments of a call.
call_modify(call, new_args, env = parent.frame()) call_standardise(call, env = parent.frame())
call_modify(call, new_args, env = parent.frame()) call_standardise(call, env = parent.frame())
call |
A call to modify. It is first standardised with
|
new_args |
A named list of expressions (constants, names or calls)
used to modify the call. Use |
env |
Environment in which to look up call value. |
call <- quote(mean(x, na.rm = TRUE)) call_standardise(call) # Modify an existing argument call_modify(call, list(na.rm = FALSE)) call_modify(call, list(x = quote(y))) # Remove an argument call_modify(call, list(na.rm = NULL)) # Add a new argument call_modify(call, list(trim = 0.1)) # Add an explicit missing argument call_modify(call, list(na.rm = quote(expr = )))
call <- quote(mean(x, na.rm = TRUE)) call_standardise(call) # Modify an existing argument call_modify(call, list(na.rm = FALSE)) call_modify(call, list(x = quote(y))) # Remove an argument call_modify(call, list(na.rm = NULL)) # Add a new argument call_modify(call, list(trim = 0.1)) # Add an explicit missing argument call_modify(call, list(na.rm = quote(expr = )))
Create a call by "hand"
call_new(f, ..., .args = list())
call_new(f, ..., .args = list())
f |
Function to call. For |
... , .args
|
Arguments to the call either in or out of a list |
# f can either be a string, a symbol or a call call_new("f", a = 1) call_new(quote(f), a = 1) call_new(quote(f()), a = 1) #' Can supply arguments individually or in a list call_new(quote(f), a = 1, b = 2) call_new(quote(f), .args = list(a = 1, b = 2))
# f can either be a string, a symbol or a call call_new("f", a = 1) call_new(quote(f), a = 1) call_new(quote(f()), a = 1) #' Can supply arguments individually or in a list call_new(quote(f), a = 1, b = 2) call_new(quote(f), .args = list(a = 1, b = 2))
expr_find()
finds the full expression; expr_text()
turns the
expression into a single string; expr_label()
formats it nicely for
use in messages. expr_env()
finds the environment associated with
the expression.
expr_label(x) expr_text(x, width = 60L, nlines = Inf) expr_find(x) expr_env(x, default_env)
expr_label(x) expr_text(x, width = 60L, nlines = Inf) expr_find(x) expr_env(x, default_env)
x |
A promise (function argument) |
width |
Width of each line |
nlines |
Maximum number of lines to extract. |
default_env |
If supplied, |
These functions never force promises, and will work even if a promise has previously been forced.
# Unlike substitute(), expr_find() finds the original expression f <- function(x) g(x) g <- function(y) h(y) h <- function(z) list(substitute(z), expr_find(z)) f(1 + 2 + 3) expr_label(10) # Names a quoted with `` expr_label(x) # Strings are encoded expr_label("a\nb") # Expressions are captured expr_label(a + b + c) # Long expressions are collapsed expr_label(foo({ 1 + 2 print(x) }))
# Unlike substitute(), expr_find() finds the original expression f <- function(x) g(x) g <- function(y) h(y) h <- function(z) list(substitute(z), expr_find(z)) f(1 + 2 + 3) expr_label(10) # Names a quoted with `` expr_label(x) # Strings are encoded expr_label("a\nb") # Expressions are captured expr_label(a + b + c) # Long expressions are collapsed expr_label(foo({ 1 + 2 print(x) }))
This should be used sparingly if you want to implement true non-standard evaluation with 100% magic. I recommend avoiding this unless you have strong reasons otherwise since requiring arguments to be formulas only adds one extra character to the inputs, and otherwise makes life much much simpler.
f_capture(x) dots_capture(..., .ignore_empty = TRUE)
f_capture(x) dots_capture(..., .ignore_empty = TRUE)
x , ...
|
An unevaluated promises |
.ignore_empty |
If |
f_capture
returns a formula; dots_capture
returns a list of formulas.
f_capture(a + b) dots_capture(a + b, c + d, e + f) # These functions will follow a chain of promises back to the # original definition f <- function(x) g(x) g <- function(y) h(y) h <- function(z) f_capture(z) f(a + b + c)
f_capture(a + b) dots_capture(a + b, c + d, e + f) # These functions will follow a chain of promises back to the # original definition f <- function(x) g(x) g <- function(y) h(y) h <- function(z) f_capture(z) f(a + b + c)
f_eval_rhs
evaluates the RHS of a formula and f_eval_lhs
evaluates the LHS. f_eval
is a shortcut for f_eval_rhs
since
that is what you most commonly need.
f_eval_rhs(f, data = NULL) f_eval_lhs(f, data = NULL) f_eval(f, data = NULL) find_data(x)
f_eval_rhs(f, data = NULL) f_eval_lhs(f, data = NULL) f_eval(f, data = NULL) find_data(x)
f |
A formula. Any expressions wrapped in |
data |
A list (or data frame). |
x |
An object for which you want to find associated data. |
If data
is specified, variables will be looked for first in this
object, and if not found in the environment of the formula.
When used with data
, f_eval
provides two pronouns to make it
possible to be explicit about where you want values to come from:
.env
and .data
. These are thin wrappers around .data
and .env
that throw errors if you try to access non-existent values.
f_eval(~ 1 + 2 + 3) # formulas automatically capture their enclosing environment foo <- function(x) { y <- 10 ~ x + y } f <- foo(1) f f_eval(f) # If you supply data, f_eval will look their first: f_eval(~ cyl, mtcars) # To avoid ambiguity, you can use .env and .data pronouns to be # explicit: cyl <- 10 f_eval(~ .data$cyl, mtcars) f_eval(~ .env$cyl, mtcars) # Imagine you are computing the mean of a variable: f_eval(~ mean(cyl), mtcars) # How can you change the variable that's being computed? # The easiest way is "unquote" with uq() # See ?f_interp for more details var <- ~ cyl f_eval(~ mean( uq(var) ), mtcars)
f_eval(~ 1 + 2 + 3) # formulas automatically capture their enclosing environment foo <- function(x) { y <- 10 ~ x + y } f <- foo(1) f f_eval(f) # If you supply data, f_eval will look their first: f_eval(~ cyl, mtcars) # To avoid ambiguity, you can use .env and .data pronouns to be # explicit: cyl <- 10 f_eval(~ .data$cyl, mtcars) f_eval(~ .env$cyl, mtcars) # Imagine you are computing the mean of a variable: f_eval(~ mean(cyl), mtcars) # How can you change the variable that's being computed? # The easiest way is "unquote" with uq() # See ?f_interp for more details var <- ~ cyl f_eval(~ mean( uq(var) ), mtcars)
Interpolation replaces sub-expressions of the form uq(x)
with
the evaluated value of x
, and inlines sub-expressions of
the form uqs(x)
.
f_interp(f, data = NULL) uq(x, data = NULL) uqf(x) uqs(x)
f_interp(f, data = NULL) uq(x, data = NULL) uqf(x) uqs(x)
f |
A one-sided formula. |
data |
When called from inside |
x |
For |
Formally, f_interp
is a quasiquote function, uq()
is the
unquote operator, and uqs()
is the unquote splice operator.
These terms have a rich history in LISP, and live on in modern languages
like http://docs.julialang.org/en/release-0.1/manual/metaprogramming/
and https://docs.racket-lang.org/reference/quasiquote.html.
f_interp(x ~ 1 + uq(1 + 2 + 3) + 10) # Use uqs() if you want to add multiple arguments to a function # It must evaluate to a list args <- list(1:10, na.rm = TRUE) f_interp(~ mean( uqs(args) )) # You can combine the two var <- quote(xyz) extra_args <- list(trim = 0.9) f_interp(~ mean( uq(var) , uqs(extra_args) )) foo <- function(n) { ~ 1 + uq(n) } f <- foo(10) f f_interp(f)
f_interp(x ~ 1 + uq(1 + 2 + 3) + 10) # Use uqs() if you want to add multiple arguments to a function # It must evaluate to a list args <- list(1:10, na.rm = TRUE) f_interp(~ mean( uqs(args) )) # You can combine the two var <- quote(xyz) extra_args <- list(trim = 0.9) f_interp(~ mean( uq(var) , uqs(extra_args) )) foo <- function(n) { ~ 1 + uq(n) } f <- foo(10) f f_interp(f)
f_list
makes a new list; as_f_list
takes an existing list.
Both take the LHS of any two-sided formulas and evaluate it, replacing the
current name with the result.
f_list(...) as_f_list(x)
f_list(...) as_f_list(x)
... |
Named arguments. |
x |
An existing list |
A named list.
f_list("y" ~ x) f_list(a = "y" ~ a, ~ b, c = ~c)
f_list("y" ~ x) f_list(a = "y" ~ a, ~ b, c = ~c)
Create a formula object by "hand".
f_new(rhs, lhs = NULL, env = parent.frame())
f_new(rhs, lhs = NULL, env = parent.frame())
lhs , rhs
|
A call, name, or atomic vector. |
env |
An environment |
A formula object
f_new(quote(a)) f_new(quote(a), quote(b))
f_new(quote(a)) f_new(quote(a), quote(b))
f_rhs
extracts the righthand side, f_lhs
extracts the
lefthand side, and f_env
extracts the environment. All functions
throw an error if f
is not a formula.
f_rhs(f) f_rhs(x) <- value f_lhs(f) f_lhs(x) <- value f_env(f) f_env(x) <- value
f_rhs(f) f_rhs(x) <- value f_lhs(f) f_lhs(x) <- value f_env(f) f_env(x) <- value
f , x
|
A formula |
value |
The value to replace with. |
f_rhs
and f_lhs
return language objects (i.e.
atomic vectors of length 1, a name, or a call). f_env
returns an environment.
f_rhs(~ 1 + 2 + 3) f_rhs(~ x) f_rhs(~ "A") f_rhs(1 ~ 2) f_lhs(~ y) f_lhs(x ~ y) f_env(~ x)
f_rhs(~ 1 + 2 + 3) f_rhs(~ x) f_rhs(~ "A") f_rhs(1 ~ 2) f_lhs(~ y) f_lhs(x ~ y) f_env(~ x)
Equivalent of expr_text()
and expr_label()
for
formulas.
f_text(x, width = 60L, nlines = Inf) f_label(x)
f_text(x, width = 60L, nlines = Inf) f_label(x)
x |
A formula. |
width |
Width of each line |
nlines |
Maximum number of lines to extract. |
f <- ~ a + b + bc f_text(f) f_label(f) # Names a quoted with `` f_label(~ x) # Strings are encoded f_label(~ "a\nb") # Long expressions are collapsed f_label(~ foo({ 1 + 2 print(x) }))
f <- ~ a + b + bc f_text(f) f_label(f) # Names a quoted with `` f_label(~ x) # Strings are encoded f_label(~ "a\nb") # Long expressions are collapsed f_label(~ foo({ 1 + 2 print(x) }))
This interpolates values in the formula that are defined in its environment, replacing the environment with its parent.
f_unwrap(f)
f_unwrap(f)
f |
A formula to unwrap. |
n <- 100 f <- ~ x + n f_unwrap(f)
n <- 100 f <- ~ x + n f_unwrap(f)
This constructs a new function given it's three components: list of arguments, body code and parent environment.
function_new(args, body, env = parent.frame())
function_new(args, body, env = parent.frame())
args |
A named list of default arguments. Note that if you want
arguments that don't have defaults, you'll need to use the special function
|
body |
A language object representing the code inside the function.
Usually this will be most easily generated with |
env |
The parent environment of the function, defaults to the calling
environment of |
f <- function(x) x + 3 g <- function_new(alist(x = ), quote(x + 3)) # The components of the functions are identical identical(formals(f), formals(g)) identical(body(f), body(g)) identical(environment(f), environment(g)) # But the functions are not identical because f has src code reference identical(f, g) attr(f, "srcref") <- NULL # Now they are: stopifnot(identical(f, g))
f <- function(x) x + 3 g <- function_new(alist(x = ), quote(x + 3)) # The components of the functions are identical identical(formals(f), formals(g)) identical(body(f), body(g)) identical(environment(f), environment(g)) # But the functions are not identical because f has src code reference identical(f, g) attr(f, "srcref") <- NULL # Now they are: stopifnot(identical(f, g))
This is useful if you want to build an expression up from a mixture of constants and variables.
interp(`_obj`, ..., .values)
interp(`_obj`, ..., .values)
_obj |
An object to modify: can be a call, name, formula,
|
... , .values
|
Either individual name-value pairs, or a list (or environment) of values. |
# Interp works with formulas, lazy objects, quoted calls and strings interp(~ x + y, x = 10) interp(lazy(x + y), x = 10) interp(quote(x + y), x = 10) interp("x + y", x = 10) # Use as.name if you have a character string that gives a # variable name interp(~ mean(var), var = as.name("mpg")) # or supply the quoted name directly interp(~ mean(var), var = quote(mpg)) # Or a function! interp(~ f(a, b), f = as.name("+")) # Remember every action in R is a function call: # http://adv-r.had.co.nz/Functions.html#all-calls # If you've built up a list of values through some other # mechanism, use .values interp(~ x + y, .values = list(x = 10)) # You can also interpolate variables defined in the current # environment, but this is a little risky. y <- 10 interp(~ x + y, .values = environment())
# Interp works with formulas, lazy objects, quoted calls and strings interp(~ x + y, x = 10) interp(lazy(x + y), x = 10) interp(quote(x + y), x = 10) interp("x + y", x = 10) # Use as.name if you have a character string that gives a # variable name interp(~ mean(var), var = as.name("mpg")) # or supply the quoted name directly interp(~ mean(var), var = quote(mpg)) # Or a function! interp(~ f(a, b), f = as.name("+")) # Remember every action in R is a function call: # http://adv-r.had.co.nz/Functions.html#all-calls # If you've built up a list of values through some other # mechanism, use .values interp(~ x + y, .values = list(x = 10)) # You can also interpolate variables defined in the current # environment, but this is a little risky. y <- 10 interp(~ x + y, .values = environment())
Is object a formula?
is_formula(x)
is_formula(x)
x |
Object to test |
is_formula(~ 10) is_formula(10)
is_formula(~ 10) is_formula(10)
These helpers are consistent wrappers around their base R equivalents. A language object is either an atomic vector (typically a scalar), a name (aka a symbol), a call, or a pairlist (used for function arguments).
is_lang(x) is_name(x) is_call(x) is_pairlist(x) is_atomic(x)
is_lang(x) is_name(x) is_call(x) is_pairlist(x) is_atomic(x)
x |
An object to test. |
as_name()
and as_call()
for coercion
functions.
q1 <- quote(1) is_lang(q1) is_atomic(q1) q2 <- quote(x) is_lang(q2) is_name(q2) q3 <- quote(x + 1) is_lang(q3) is_call(q3)
q1 <- quote(1) is_lang(q1) is_atomic(q1) q2 <- quote(x) is_lang(q2) is_name(q2) q3 <- quote(x + 1) is_lang(q3) is_call(q3)
lazy()
uses non-standard evaluation to turn promises into lazy
objects; lazy_()
does standard evaluation and is suitable for
programming.
lazy_(expr, env) lazy(expr, env = parent.frame(), .follow_symbols = TRUE)
lazy_(expr, env) lazy(expr, env = parent.frame(), .follow_symbols = TRUE)
expr |
Expression to capture. For |
env |
Environment in which to evaluate expr. |
.follow_symbols |
If |
Use lazy()
like you'd use substitute()
to capture an unevaluated promise. Compared to substitute()
it
also captures the environment associated with the promise, so that you
can correctly replay it in the future.
lazy_(quote(a + x), globalenv()) # Lazy is designed to be used inside a function - you should # give it the name of a function argument (a promise) f <- function(x = b - a) { lazy(x) } f() f(a + b / c) # Lazy also works when called from the global environment. This makes # easy to play with interactively. lazy(a + b / c) # By default, lazy will climb all the way back to the initial promise # This is handy if you have if you have nested functions: g <- function(y) f(y) h <- function(z) g(z) f(a + b) g(a + b) h(a + b) # To avoid this behavour, set .follow_symbols = FALSE # See vignette("chained-promises") for details
lazy_(quote(a + x), globalenv()) # Lazy is designed to be used inside a function - you should # give it the name of a function argument (a promise) f <- function(x = b - a) { lazy(x) } f() f(a + b / c) # Lazy also works when called from the global environment. This makes # easy to play with interactively. lazy(a + b / c) # By default, lazy will climb all the way back to the initial promise # This is handy if you have if you have nested functions: g <- function(y) f(y) h <- function(z) g(z) f(a + b) g(a + b) h(a + b) # To avoid this behavour, set .follow_symbols = FALSE # See vignette("chained-promises") for details
Capture ... (dots) for later lazy evaluation.
lazy_dots(..., .follow_symbols = FALSE, .ignore_empty = FALSE)
lazy_dots(..., .follow_symbols = FALSE, .ignore_empty = FALSE)
... |
Dots from another function |
.follow_symbols |
If |
.ignore_empty |
If |
A named list of lazy
expressions.
lazy_dots(x = 1) lazy_dots(a, b, c * 4) f <- function(x = a + b, ...) { lazy_dots(x = x, y = a + b, ...) } f(z = a + b) f(z = a + b, .follow_symbols = TRUE) # .follow_symbols is off by default because it causes problems # with lazy loaded objects lazy_dots(letters) lazy_dots(letters, .follow_symbols = TRUE) # You can also modify a dots like a list. Anything on the RHS will # be coerced to a lazy. l <- lazy_dots(x = 1) l$y <- quote(f) l[c("y", "x")] l["z"] <- list(~g) c(lazy_dots(x = 1), lazy_dots(f))
lazy_dots(x = 1) lazy_dots(a, b, c * 4) f <- function(x = a + b, ...) { lazy_dots(x = x, y = a + b, ...) } f(z = a + b) f(z = a + b, .follow_symbols = TRUE) # .follow_symbols is off by default because it causes problems # with lazy loaded objects lazy_dots(letters) lazy_dots(letters, .follow_symbols = TRUE) # You can also modify a dots like a list. Anything on the RHS will # be coerced to a lazy. l <- lazy_dots(x = 1) l$y <- quote(f) l[c("y", "x")] l["z"] <- list(~g) c(lazy_dots(x = 1), lazy_dots(f))
Evaluate a lazy expression.
lazy_eval(x, data = NULL)
lazy_eval(x, data = NULL)
x |
A lazy object or a formula. |
data |
Option, a data frame or list in which to preferentially look for variables before using the environment associated with the lazy object. |
f <- function(x) { z <- 100 ~ x + z } z <- 10 lazy_eval(f(10)) lazy_eval(f(10), list(x = 100)) lazy_eval(f(10), list(x = 1, z = 1)) lazy_eval(lazy_dots(a = x, b = z), list(x = 10))
f <- function(x) { z <- 100 ~ x + z } z <- 10 lazy_eval(f(10)) lazy_eval(f(10), list(x = 100)) lazy_eval(f(10), list(x = 1, z = 1)) lazy_eval(lazy_dots(a = x, b = z), list(x = 10))
lazy_dots
as arguments.In order to exactly replay the original call, the environment must be the
same for all of the dots. This function circumvents that a little,
falling back to the baseenv()
if all environments aren't
the same.
make_call(fun, args)
make_call(fun, args)
fun |
Function as symbol or quoted call. |
args |
Arguments to function; must be a |
A list:
env |
The common environment for all elements |
expr |
The expression |
make_call(quote(f), lazy_dots(x = 1, 2)) make_call(quote(f), list(x = 1, y = ~x)) make_call(quote(f), ~x) # If no known or no common environment, fails back to baseenv() make_call(quote(f), quote(x))
make_call(quote(f), lazy_dots(x = 1, 2)) make_call(quote(f), list(x = 1, y = ~x)) make_call(quote(f), ~x) # If no known or no common environment, fails back to baseenv() make_call(quote(f), quote(x))
Generate a missing argument.
missing_arg()
missing_arg()
f_interp(~f(x = uq(missing_arg()))) f_interp(~f(x = uq(NULL)))
f_interp(~f(x = uq(missing_arg()))) f_interp(~f(x = uq(NULL)))