| Title: | Functions for Base Types and Core R and 'Tidyverse' Features |
|---|---|
| Description: | A toolbox for working with base types, core R features like the condition system, and core 'Tidyverse' features like tidy evaluation. |
| Authors: | Lionel Henry [aut, cre], Hadley Wickham [aut], mikefc [cph] (Hash implementation based on Mike's xxhashlite), Yann Collet [cph] (Author of the embedded xxHash library), Posit, PBC [cph, fnd] |
| Maintainer: | Lionel Henry <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.1.6.9000 |
| Built: | 2025-11-27 07:09:07 UTC |
| Source: | https://github.com/r-lib/rlang |
These functions are equivalent to base functionsbase::stop(),base::warning(), andbase::message(). They signal a condition(an error, warning, or message respectively) and make it easy tosupply condition metadata:
Supplyclass to create a classed condition that can be caughtor handled selectively, allowing for finer-grained errorhandling.
Supply metadata with named... arguments. This data is storedin the condition object and can be examined by handlers.
Supplycall to inform users about which function the erroroccurred in.
Supply another condition asparent to create achained condition.
Certain components of condition messages are formatted with unicodesymbols and terminal colours by default. These aspects can becustomised, seeCustomising condition messages.
abort( message = NULL, class = NULL, ..., call, body = NULL, footer = NULL, trace = NULL, parent = NULL, use_cli_format = NULL, .inherit = TRUE, .internal = FALSE, .file = NULL, .frame = caller_env(), .trace_bottom = NULL, .subclass = deprecated())warn( message = NULL, class = NULL, ..., body = NULL, footer = NULL, parent = NULL, use_cli_format = NULL, .inherit = NULL, .frequency = c("always", "regularly", "once"), .frequency_id = NULL, .subclass = deprecated())inform( message = NULL, class = NULL, ..., body = NULL, footer = NULL, parent = NULL, use_cli_format = NULL, .inherit = NULL, .file = NULL, .frequency = c("always", "regularly", "once"), .frequency_id = NULL, .subclass = deprecated())signal(message = "", class, ..., .subclass = deprecated())reset_warning_verbosity(id)reset_message_verbosity(id)abort( message=NULL, class=NULL,..., call, body=NULL, footer=NULL, trace=NULL, parent=NULL, use_cli_format=NULL, .inherit=TRUE, .internal=FALSE, .file=NULL, .frame= caller_env(), .trace_bottom=NULL, .subclass= deprecated())warn( message=NULL, class=NULL,..., body=NULL, footer=NULL, parent=NULL, use_cli_format=NULL, .inherit=NULL, .frequency= c("always","regularly","once"), .frequency_id=NULL, .subclass= deprecated())inform( message=NULL, class=NULL,..., body=NULL, footer=NULL, parent=NULL, use_cli_format=NULL, .inherit=NULL, .file=NULL, .frequency= c("always","regularly","once"), .frequency_id=NULL, .subclass= deprecated())signal(message="", class,..., .subclass= deprecated())reset_warning_verbosity(id)reset_message_verbosity(id)
message | The message to display, formatted as abulletedlist. The first element is displayed as analert bulletprefixed with If a message is not supplied, it is expected that the message isgeneratedlazily through If a function, it is stored in the |
class | Subclass of the condition. |
... | Additional data to be stored in the condition object.If you supply condition fields, you should usually provide a |
call | The execution environment of a currently runningfunction, e.g. You only need to supply Can also be For more information about error calls, seeIncluding function calls in error messages. |
body,footer | Additional bullets. |
trace | A |
parent | Supply
For more information about error calls, seeIncluding contextual information with error chains. |
use_cli_format | Whether to format If set to |
.inherit | Whether the condition inherits from |
.internal | If |
.file | A connection or a string specifying where to print themessage. The default depends on the context, see the |
.frame | The throwing context. Used as default for |
.trace_bottom | Used in the display of simplified backtracesas the last relevant call frame to show. This way, the irrelevantparts of backtraces corresponding to condition handling( |
.subclass |
|
.frequency | How frequently should the warning or message bedisplayed? By default ( |
.frequency_id | A unique identifier for the warning ormessage. This is used when |
id | The identifying string of the condition that was suppliedas |
abort() throws subclassed errors, see"rlang_error".
warn() temporarily set thewarning.length global option tothe maximum value (8170), unless that option has been changedfrom the default value. The default limit (1000 characters) isespecially easy to hit when the message contains a lot of ANSIescapes, as created by the crayon or cli packages
As withbase::stop(), errors thrown withabort() are prefixedwith"Error: ". Calls and source references are included in theprefix, e.g."Error in my_function() at myfile.R:1:2:". Thereare a few cosmetic differences:
The call is stripped from its arguments to keep it simple. It isthen formatted using thecli package ifavailable.
A line break between the prefix and the message when the formeris too long. When a source location is included, a line break isalways inserted.
If your throwing code is highly structured, you may have toexplicitly informabort() about the relevant user-facing call toinclude in the prefix. Internal helpers are rarely relevant to endusers. See thecall argument ofabort().
abort() saves a backtrace in thetrace component of the errorcondition. You can print a simplified backtrace of the last errorby callinglast_error() and a full backtrace withsummary(last_error()). Learn how to control what is displayedwhen an error is thrown withrlang_backtrace_on_error.
Signalling a condition withinform() orwarn() displays amessage in the console. These messages can be muffled as usual withbase::suppressMessages() orbase::suppressWarnings().
inform() andwarn() messages can also be silenced with theglobal optionsrlib_message_verbosity andrlib_warning_verbosity. These options take the values:
"default": Verbose unless the.frequency argument is supplied.
"verbose": Always verbose.
"quiet": Always quiet.
When set to quiet, the message is not displayed and the conditionis not signalled.
stdout andstderrBy default,abort() andinform() print to standard output ininteractive sessions. This allows rlang to be in control of theappearance of messages in IDEs like RStudio.
There are two situations where messages are streamed tostderr:
In non-interactive sessions, messages are streamed to standarderror so that R scripts can easily filter them out from normaloutput by redirectingstderr.
If a sink is active (either on output or on messages) messagesare always streamed tostderr.
These exceptions ensure consistency of behaviour in interactive andnon-interactive sessions, and when sinks are active.
# These examples are guarded to avoid throwing errorsif (FALSE) {# Signal an error with a message just like stop():abort("The error message.")# Unhandled errors are saved automatically by `abort()` and can be# retrieved with `last_error()`. The error prints with a simplified# backtrace:f <- function() try(g())g <- function() evalq(h())h <- function() abort("Tilt.")last_error()# Use `summary()` to print the full backtrace and the condition fields:summary(last_error())# Give a class to the error:abort("The error message", "mypkg_bad_error")# This allows callers to handle the error selectivelytryCatch( mypkg_function(), mypkg_bad_error = function(err) { warn(conditionMessage(err)) # Demote the error to a warning NA # Return an alternative value })# You can also specify metadata that will be stored in the condition:abort("The error message.", "mypkg_bad_error", data = 1:10)# This data can then be consulted by user handlers:tryCatch( mypkg_function(), mypkg_bad_error = function(err) { # Compute an alternative return value with the data: recover_error(err$data) })# If you call low-level APIs it may be a good idea to create a# chained error with the low-level error wrapped in a more# user-friendly error. Use `try_fetch()` to fetch errors of a given# class and rethrow them with the `parent` argument of `abort()`:file <- "http://foo.bar/baz"try( try_fetch( download(file), error = function(err) { msg <- sprintf("Can't download `%s`", file) abort(msg, parent = err) }))# You can also hard-code the call when it's not easy to# forward it from the caller f <- function() { abort("my message", call = call("my_function"))}g <- function() { f()}# Shows that the error occurred in `my_function()`try(g())}# These examples are guarded to avoid throwing errorsif(FALSE){# Signal an error with a message just like stop():abort("The error message.")# Unhandled errors are saved automatically by `abort()` and can be# retrieved with `last_error()`. The error prints with a simplified# backtrace:f<-function() try(g())g<-function() evalq(h())h<-function() abort("Tilt.")last_error()# Use `summary()` to print the full backtrace and the condition fields:summary(last_error())# Give a class to the error:abort("The error message","mypkg_bad_error")# This allows callers to handle the error selectivelytryCatch( mypkg_function(), mypkg_bad_error=function(err){ warn(conditionMessage(err))# Demote the error to a warningNA# Return an alternative value})# You can also specify metadata that will be stored in the condition:abort("The error message.","mypkg_bad_error", data=1:10)# This data can then be consulted by user handlers:tryCatch( mypkg_function(), mypkg_bad_error=function(err){# Compute an alternative return value with the data: recover_error(err$data)})# If you call low-level APIs it may be a good idea to create a# chained error with the low-level error wrapped in a more# user-friendly error. Use `try_fetch()` to fetch errors of a given# class and rethrow them with the `parent` argument of `abort()`:file<-"http://foo.bar/baz"try( try_fetch( download(file), error=function(err){ msg<- sprintf("Can't download `%s`", file) abort(msg, parent= err)}))# You can also hard-code the call when it's not easy to# forward it from the caller f<-function(){ abort("my message", call= call("my_function"))}g<-function(){ f()}# Shows that the error occurred in `my_function()`try(g())}
This is equivalent tobase::match.arg() with a few differences:
Partial matches trigger an error.
Error messages are a bit more informative and obey the tidyversestandards.
arg_match() derives the possible values from thecaller function.
arg_match0() is a bare-bones version if performance is at a premium.It requires a string asarg and explicit charactervalues.For convenience,arg may also be a character vector containingevery element ofvalues, possibly permuted.In this case, the first element ofarg is used.
arg_match( arg, values = NULL, ..., multiple = FALSE, error_arg = caller_arg(arg), error_call = caller_env())arg_match0(arg, values, arg_nm = caller_arg(arg), error_call = caller_env())arg_match( arg, values=NULL,..., multiple=FALSE, error_arg= caller_arg(arg), error_call= caller_env())arg_match0(arg, values, arg_nm= caller_arg(arg), error_call= caller_env())
arg | A symbol referring to an argument accepting strings. |
values | A character vector of possible values that |
... | These dots are for future extensions and must be empty. |
multiple | Whether |
error_arg | An argument name as a string. This argumentwill be mentioned in error messages as the input that is at theorigin of a problem. |
error_call | The execution environment of a currentlyrunning function, e.g. |
arg_nm | Same as |
The string supplied toarg.
fn <- function(x = c("foo", "bar")) arg_match(x)fn("bar")# Throws an informative error for mismatches:try(fn("b"))try(fn("baz"))# Use the bare-bones version with explicit values for speed:arg_match0("bar", c("foo", "bar", "baz"))# For convenience:fn1 <- function(x = c("bar", "baz", "foo")) fn3(x)fn2 <- function(x = c("baz", "bar", "foo")) fn3(x)fn3 <- function(x) arg_match0(x, c("foo", "bar", "baz"))fn1()fn2("bar")try(fn3("zoo"))fn<-function(x= c("foo","bar")) arg_match(x)fn("bar")# Throws an informative error for mismatches:try(fn("b"))try(fn("baz"))# Use the bare-bones version with explicit values for speed:arg_match0("bar", c("foo","bar","baz"))# For convenience:fn1<-function(x= c("bar","baz","foo")) fn3(x)fn2<-function(x= c("baz","bar","foo")) fn3(x)fn3<-function(x) arg_match0(x, c("foo","bar","baz"))fn1()fn2("bar")try(fn3("zoo"))
Use@inheritParams rlang::args_error_context in your package todocumentarg andcall arguments (or equivalently their prefixedversionserror_arg anderror_call).
arg parameters should be formatted as argument (e.g. usingcli's.arg specifier) and included in error messages. See alsocaller_arg().
call parameters should be included in error conditions in afield namedcall. An easy way to do this is by passing acallargument toabort(). See alsolocal_error_call().
arg | An argument name as a string. This argumentwill be mentioned in error messages as the input that is at theorigin of a problem. |
error_arg | An argument name as a string. This argumentwill be mentioned in error messages as the input that is at theorigin of a problem. |
call | The execution environment of a currentlyrunning function, e.g. |
error_call | The execution environment of a currentlyrunning function, e.g. |
as_box() boxes its input only if it is not already a box. Theclass is also checked if supplied.
as_box_if() boxes its input only if it not already a box, or ifthe predicate.p returnsTRUE.
as_box(x, class = NULL)as_box_if(.x, .p, .class = NULL, ...)as_box(x, class=NULL)as_box_if(.x, .p, .class=NULL,...)
x,.x | An R object. |
class,.class | A box class. If the input is already a box ofthat class, it is returned as is. If the input needs to be boxed, |
.p | A predicate function. |
... | Arguments passed to |
Adata mask is an environment (or possiblymultiple environments forming an ancestry) containing user-suppliedobjects. Objects in the mask have precedence over objects in theenvironment (i.e. they mask those objects). Many R functionsevaluate quoted expressions in a data mask so these expressions canrefer to objects within the user data.
These functions let you construct a tidy eval data mask manually.They are meant for developers of tidy eval interfaces rather thanfor end users.
as_data_mask(data)as_data_pronoun(data)new_data_mask(bottom, top = bottom)as_data_mask(data)as_data_pronoun(data)new_data_mask(bottom, top= bottom)
data | A data frame or named vector of masking data. |
bottom | The environment containing masking objects if thedata mask is one environment deep. The bottom environment if thedata mask comprises multiple environment. If you haven't supplied |
top | The last environment of the data mask. If the data maskis only one environment deep, Thismust be an environment that you own, i.e. that you havecreated yourself. The parent of |
A data mask that you can supply toeval_tidy().
Most of the time you can just calleval_tidy() with a list or adata frame and the data mask will be constructed automatically.There are three main use cases for manual creation of data masks:
Wheneval_tidy() is called with the same data in a tight loop.Because there is some overhead to creating tidy eval data masks,constructing the mask once and reusing it for subsequentevaluations may improve performance.
When several expressions should be evaluated in the exact sameenvironment because a quoted expression might create new objectsthat can be referred in other quoted expressions evaluated at alater time. One example of this istibble::lst() where newcolumns can refer to previous ones.
When your data mask requires special features. For instance thedata frame columns in dplyr data masks are implemented withactive bindings.
Unlikebase::eval() which takes any kind of environments as datamask,eval_tidy() has specific requirements in order to supportquosures. For this reason you can't supply bareenvironments.
There are two ways of constructing an rlang data mask manually:
as_data_mask() transforms a list or data frame to a data mask.It automatically installs the data pronoun.data.
new_data_mask() is a bare bones data mask constructor forenvironments. You can supply a bottom and a top environment incase your data mask comprises multiple environments (see sectionbelow).
Unlikeas_data_mask() it does not install the.data pronounso you need to provide one yourself. You can provide a pronounconstructed withas_data_pronoun() or your own pronoun class.
as_data_pronoun() will create a pronoun from a list, anenvironment, or an rlang data mask. In the latter case, the wholeancestry is looked up from the bottom to the top of the mask.Functions stored in the mask are bypassed by the pronoun.
Once you have built a data mask, simply pass it toeval_tidy() asthedata argument. You can repeat this as many times asneeded. Note that any objects created there (perhaps because of acall to<-) will persist in subsequent evaluations.
In some cases you'll need several levels in your data mask. Onegood reason is when you include functions in the mask. It's a goodidea to keep data objects one level lower than function objects, sothat the former cannot override the definitions of the latter (seeexamples).
In that case, set up all your environments and keep track of thebottom child and the top parent. You'll need to pass both tonew_data_mask().
Note that the parent of the top environment is completelyundetermined, you shouldn't expect it to remain the same at alltimes. This parent is replaced during evaluation byeval_tidy()to one of the following environments:
The default environment passed as theenv argument ofeval_tidy().
The environment of the current quosure being evaluated, if applicable.
Consequently, all masking data should be contained between thebottom and top environment of the data mask.
# Evaluating in a tidy evaluation environment enables all tidy# features:mask <- as_data_mask(mtcars)eval_tidy(quo(letters), mask)# You can install new pronouns in the mask:mask$.pronoun <- as_data_pronoun(list(foo = "bar", baz = "bam"))eval_tidy(quo(.pronoun$foo), mask)# In some cases the data mask can leak to the user, for example if# a function or formula is created in the data mask environment:cyl <- "user variable from the context"fn <- eval_tidy(quote(function() cyl), mask)fn()# If new objects are created in the mask, they persist in the# subsequent calls:eval_tidy(quote(new <- cyl + am), mask)eval_tidy(quote(new * 2), mask)# In some cases your data mask is a whole chain of environments# rather than a single environment. You'll have to use# `new_data_mask()` and let it know about the bottom of the mask# (the last child of the environment chain) and the topmost parent.# A common situation where you'll want a multiple-environment mask# is when you include functions in your mask. In that case you'll# put functions in the top environment and data in the bottom. This# will prevent the data from overwriting the functions.top <- new_environment(list(`+` = base::paste, c = base::paste))# Let's add a middle environment just for sport:middle <- env(top)# And finally the bottom environment containing data:bottom <- env(middle, a = "a", b = "b", c = "c")# We can now create a mask by supplying the top and bottom# environments:mask <- new_data_mask(bottom, top = top)# This data mask can be passed to eval_tidy() instead of a list or# data frame:eval_tidy(quote(a + b + c), data = mask)# Note how the function `c()` and the object `c` are looked up# properly because of the multi-level structure:eval_tidy(quote(c(a, b, c)), data = mask)# new_data_mask() does not create data pronouns, but# data pronouns can be added manually:mask$.fns <- as_data_pronoun(top)# The `.data` pronoun should generally be created from the# mask. This will ensure data is looked up throughout the whole# ancestry. Only non-function objects are looked up from this# pronoun:mask$.data <- as_data_pronoun(mask)mask$.data$c# Now we can reference values with the pronouns:eval_tidy(quote(c(.data$a, .data$b, .data$c)), data = mask)# Evaluating in a tidy evaluation environment enables all tidy# features:mask<- as_data_mask(mtcars)eval_tidy(quo(letters), mask)# You can install new pronouns in the mask:mask$.pronoun<- as_data_pronoun(list(foo="bar", baz="bam"))eval_tidy(quo(.pronoun$foo), mask)# In some cases the data mask can leak to the user, for example if# a function or formula is created in the data mask environment:cyl<-"user variable from the context"fn<- eval_tidy(quote(function() cyl), mask)fn()# If new objects are created in the mask, they persist in the# subsequent calls:eval_tidy(quote(new<- cyl+ am), mask)eval_tidy(quote(new*2), mask)# In some cases your data mask is a whole chain of environments# rather than a single environment. You'll have to use# `new_data_mask()` and let it know about the bottom of the mask# (the last child of the environment chain) and the topmost parent.# A common situation where you'll want a multiple-environment mask# is when you include functions in your mask. In that case you'll# put functions in the top environment and data in the bottom. This# will prevent the data from overwriting the functions.top<- new_environment(list(`+`= base::paste, c= base::paste))# Let's add a middle environment just for sport:middle<- env(top)# And finally the bottom environment containing data:bottom<- env(middle, a="a", b="b", c="c")# We can now create a mask by supplying the top and bottom# environments:mask<- new_data_mask(bottom, top= top)# This data mask can be passed to eval_tidy() instead of a list or# data frame:eval_tidy(quote(a+ b+ c), data= mask)# Note how the function `c()` and the object `c` are looked up# properly because of the multi-level structure:eval_tidy(quote(c(a, b, c)), data= mask)# new_data_mask() does not create data pronouns, but# data pronouns can be added manually:mask$.fns<- as_data_pronoun(top)# The `.data` pronoun should generally be created from the# mask. This will ensure data is looked up throughout the whole# ancestry. Only non-function objects are looked up from this# pronoun:mask$.data<- as_data_pronoun(mask)mask$.data$c# Now we can reference values with the pronouns:eval_tidy(quote(c(.data$a, .data$b, .data$c)), data= mask)
as_environment() coerces named vectors (including lists) to anenvironment. The names must be unique. If supplied an unnamedstring, it returns the corresponding package environment (seepkg_env()).
as_environment(x, parent = NULL)as_environment(x, parent=NULL)
x | An object to coerce. |
parent | A parent environment, |
Ifx is an environment andparent is notNULL, theenvironment is duplicated before being set a new parent. The returnvalue is therefore a different environment thanx.
# Coerce a named vector to an environment:env <- as_environment(mtcars)# By default it gets the empty environment as parent:identical(env_parent(env), empty_env())# With strings it is a handy shortcut for pkg_env():as_environment("base")as_environment("rlang")# With NULL it returns the empty environment:as_environment(NULL)# Coerce a named vector to an environment:env<- as_environment(mtcars)# By default it gets the empty environment as parent:identical(env_parent(env), empty_env())# With strings it is a handy shortcut for pkg_env():as_environment("base")as_environment("rlang")# With NULL it returns the empty environment:as_environment(NULL)
as_function() transforms a one-sided formula into a function.This powers the lambda syntax in packages like purrr.
as_function( x, env = global_env(), ..., arg = caller_arg(x), call = caller_env())is_lambda(x)as_function( x, env= global_env(),..., arg= caller_arg(x), call= caller_env())is_lambda(x)
x | A function or formula. If afunction, it is used as is. If aformula, e.g. If astring, the function is looked up in |
env | Environment in which to fetch the function in case |
... | These dots are for future extensions and must be empty. |
arg | An argument name as a string. This argumentwill be mentioned in error messages as the input that is at theorigin of a problem. |
call | The execution environment of a currentlyrunning function, e.g. |
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"))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_label() transforms R objects into a short, human-readabledescription. You can use labels to:
Display an object in a concise way, for example to labellise axesin a graphical plot.
Give default names to columns in a data frame. In this case,labelling is the first step before name repair.
See alsoas_name() for transforming symbols back to astring. Unlikeas_label(),as_name() is a well definedoperation that guarantees the roundtrip symbol -> string ->symbol.
In general, if you don't know for sure what kind of object you'redealing with (a call, a symbol, an unquoted constant), useas_label() and make no assumption about the resulting string. Ifyou know you have a symbol and need the name of the object itrefers to, useas_name(). For instance, useas_label() withobjects captured withenquo() andas_name() with symbolscaptured withensym().
as_label(x)as_label(x)
x | An object. |
Quosures aresquashed before being labelled.
Symbols are transformed to string withas_string().
Calls are abbreviated.
Numbers are represented as such.
Other constants are represented by their type, such as<dbl>or<data.frame>.
as_name() for transforming symbols back to a stringdeterministically.
# as_label() is useful with quoted expressions:as_label(expr(foo(bar)))as_label(expr(foobar))# It works with any R object. This is also useful for quoted# arguments because the user might unquote constant objects:as_label(1:3)as_label(base::list)# as_label() is useful with quoted expressions:as_label(expr(foo(bar)))as_label(expr(foobar))# It works with any R object. This is also useful for quoted# arguments because the user might unquote constant objects:as_label(1:3)as_label(base::list)
as_name() convertssymbols to character strings. Theconversion is deterministic. That is, the roundtripsymbol -> name -> symbol always gives the same result.
Useas_name() when you need to transform a symbol to a stringtorefer to an object by its name.
Useas_label() when you need to transform any kind of object toa string torepresent that object with a short description.
as_name(x)as_name(x)
x | A string or symbol, possibly wrapped in aquosure.If a string, the attributes are removed, if any. |
rlang::as_name() is theopposite ofbase::as.name(). Ifyou're writing base R code, we recommend usingbase::as.symbol()which is an alias ofas.name() that follows a more modernterminology (R types instead of S modes).
A character vector of length 1.
as_label() for converting any object to a single stringsuitable as a label.as_string() for a lower-level version thatdoesn't unwrap quosures.
# Let's create some symbols:foo <- quote(foo)bar <- sym("bar")# as_name() converts symbols to strings:fooas_name(foo)typeof(bar)typeof(as_name(bar))# as_name() unwraps quosured symbols automatically:as_name(quo(foo))# Let's create some symbols:foo<- quote(foo)bar<- sym("bar")# as_name() converts symbols to strings:fooas_name(foo)typeof(bar)typeof(as_name(bar))# as_name() unwraps quosured symbols automatically:as_name(quo(foo))
as_string() convertssymbols to character strings.
as_string(x)as_string(x)
x | A string or symbol. If a string, the attributes areremoved, if any. |
A character vector of length 1.
Unlikebase::as.symbol() andbase::as.name(),as_string()automatically transforms unicode tags such as"<U+5E78>" to theproper UTF-8 character. This is important on Windows because:
R on Windows has no UTF-8 support, and uses native encoding instead.
The native encodings do not cover all Unicode characters. Forexample, Western encodings do not support CKJ characters.
When a lossy UTF-8 -> native transformation occurs, uncoveredcharacters are transformed to an ASCII unicode tag like"<U+5E78>".
Symbols are always encoded in native. This means thattransforming the column names of a data frame to symbols might bea lossy operation.
This operation is very common in the tidyverse because of datamasking APIs like dplyr where data frames are transformed toenvironments. While the names of a data frame are stored as acharacter vector, the bindings of environments are stored assymbols.
Because it reencodes the ASCII unicode tags to their UTF-8representation, the string -> symbol -> string roundtrip ismore stable withas_string().
as_name() for a higher-level variant ofas_string()that automatically unwraps quosures.
# Let's create some symbols:foo <- quote(foo)bar <- sym("bar")# as_string() converts symbols to strings:fooas_string(foo)typeof(bar)typeof(as_string(bar))# Let's create some symbols:foo<- quote(foo)bar<- sym("bar")# as_string() converts symbols to strings:fooas_string(foo)typeof(bar)typeof(as_string(bar))
These predicates check for a given type but only returnTRUE forbare R objects. Bare objects have no class attributes. For example,a data frame is a list, but not a bare list.
is_bare_list(x, n = NULL)is_bare_atomic(x, n = NULL)is_bare_vector(x, n = NULL)is_bare_double(x, n = NULL)is_bare_complex(x, n = NULL)is_bare_integer(x, n = NULL)is_bare_numeric(x, n = NULL)is_bare_character(x, n = NULL)is_bare_logical(x, n = NULL)is_bare_raw(x, n = NULL)is_bare_string(x, n = NULL)is_bare_bytes(x, n = NULL)is_bare_list(x, n=NULL)is_bare_atomic(x, n=NULL)is_bare_vector(x, n=NULL)is_bare_double(x, n=NULL)is_bare_complex(x, n=NULL)is_bare_integer(x, n=NULL)is_bare_numeric(x, n=NULL)is_bare_character(x, n=NULL)is_bare_logical(x, n=NULL)is_bare_raw(x, n=NULL)is_bare_string(x, n=NULL)is_bare_bytes(x, n=NULL)
x | Object to be tested. |
n | Expected length of a vector. |
The predicates for vectors include then argument forpattern-matching on the vector length.
Likeis_atomic() and unlike base Ris.atomic() for R < 4.4.0,is_bare_atomic() does not returnTRUE forNULL. Starting inR 4.4.0,is.atomic(NULL) returns FALSE.
Unlike base Ris.numeric(),is_bare_double() only returnsTRUE for floating point numbers.
type-predicates,scalar-type-predicates
new_box() is similar tobase::I() but it protects a value bywrapping it in a scalar list rather than by adding an attribute.unbox() retrieves the boxed value.is_box() tests whether anobject is boxed with optional class.as_box() ensures that avalue is wrapped in a box.as_box_if() does the same but only ifthe value matches a predicate.
new_box(.x, class = NULL, ...)is_box(x, class = NULL)unbox(box)new_box(.x, class=NULL,...)is_box(x, class=NULL)unbox(box)
class | For |
... | Additional attributes passed to |
x,.x | An R object. |
box | A boxed value to unbox. |
boxed <- new_box(letters, "mybox")is_box(boxed)is_box(boxed, "mybox")is_box(boxed, "otherbox")unbox(boxed)# as_box() avoids double-boxing:boxed2 <- as_box(boxed, "mybox")boxed2unbox(boxed2)# Compare to:boxed_boxed <- new_box(boxed, "mybox")boxed_boxedunbox(unbox(boxed_boxed))# Use `as_box_if()` with a predicate if you need to ensure a box# only for a subset of values:as_box_if(NULL, is_null, "null_box")as_box_if("foo", is_null, "null_box")boxed<- new_box(letters,"mybox")is_box(boxed)is_box(boxed,"mybox")is_box(boxed,"otherbox")unbox(boxed)# as_box() avoids double-boxing:boxed2<- as_box(boxed,"mybox")boxed2unbox(boxed2)# Compare to:boxed_boxed<- new_box(boxed,"mybox")boxed_boxedunbox(unbox(boxed_boxed))# Use `as_box_if()` with a predicate if you need to ensure a box# only for a subset of values:as_box_if(NULL, is_null,"null_box")as_box_if("foo", is_null,"null_box")
Construct, manipulate and display vectors of byte sizes. These are numericvectors, so you can compare them numerically, but they can also be comparedto human readable values such as '10MB'.
parse_bytes() takes a character vector of human-readable bytesand returns a structured bytes vector.
as_bytes() is a generic conversion function for objectsrepresenting bytes.
Note: Abytes() constructor will be exported soon.
as_bytes(x)parse_bytes(x)as_bytes(x)parse_bytes(x)
x | A numeric or character vector. Character representations can useshorthand sizes (see examples). |
These memory sizes are always assumed to be base 1000, rather than 1024.
parse_bytes("1")parse_bytes("1K")parse_bytes("1Kb")parse_bytes("1KiB")parse_bytes("1MB")parse_bytes("1KB") < "1MB"sum(parse_bytes(c("1MB", "5MB", "500KB")))parse_bytes("1")parse_bytes("1K")parse_bytes("1Kb")parse_bytes("1KiB")parse_bytes("1MB")parse_bytes("1KB")<"1MB"sum(parse_bytes(c("1MB","5MB","500KB")))
Extract arguments from a call
call_args(call)call_args_names(call)call_args(call)call_args_names(call)
call | A defused call. |
A named list of arguments.
call <- quote(f(a, b))# Subsetting a call returns the arguments converted to a language# object:call[-1]# On the other hand, call_args() returns a regular list that is# often easier to work with:str(call_args(call))# When the arguments are unnamed, a vector of empty strings is# supplied (rather than NULL):call_args_names(call)call<- quote(f(a, b))# Subsetting a call returns the arguments converted to a language# object:call[-1]# On the other hand, call_args() returns a regular list that is# often easier to work with:str(call_args(call))# When the arguments are unnamed, a vector of empty strings is# supplied (rather than NULL):call_args_names(call)
This function is a wrapper aroundbase::match.call(). It returnsits own function call.
call_inspect(...)call_inspect(...)
... | Arguments to display in the returned call. |
# When you call it directly, it simply returns what you typedcall_inspect(foo(bar), "" %>% identity())# Pass `call_inspect` to functionals like `lapply()` or `map()` to# inspect the calls they create around the supplied functionlapply(1:3, call_inspect)# When you call it directly, it simply returns what you typedcall_inspect(foo(bar),""%>% identity())# Pass `call_inspect` to functionals like `lapply()` or `map()` to# inspect the calls they create around the supplied functionlapply(1:3, call_inspect)
call_match() is likematch.call() with these differences:
It supports matching missing argument to their defaults in thefunction definition.
It requires you to be a little more specific in some cases.Either all arguments are inferred from the call stack or none ofthem are (see the Inference section).
call_match( call = NULL, fn = NULL, ..., defaults = FALSE, dots_env = NULL, dots_expand = TRUE)call_match( call=NULL, fn=NULL,..., defaults=FALSE, dots_env=NULL, dots_expand=TRUE)
call | A call. The arguments will be matched to |
fn | A function definition to match arguments to. |
... | These dots must be empty. |
defaults | Whether to match missing arguments to theirdefaults. |
dots_env | An execution environment where to find dots. Ifsupplied and dots exist in this environment, and if |
dots_expand | If Note that the resulting call is not meant to be evaluated since Rdoes not support passing dots through a named argument, even ifnamed |
Whencall is not supplied, it is inferred from the call stackalong withfn anddots_env.
call andfn are inferred from the calling environment:sys.call(sys.parent()) andsys.function(sys.parent()).
dots_env is inferred from the caller of the callingenvironment:caller_env(2).
Ifcall is supplied, then you must supplyfn as well. Alsoconsider supplyingdots_env as it is set to the empty environmentwhen not inferred.
# `call_match()` supports matching missing arguments to their# defaultsfn <- function(x = "default") fncall_match(quote(fn()), fn)call_match(quote(fn()), fn, defaults = TRUE)# `call_match()` supports matching missing arguments to their# defaultsfn<-function(x="default") fncall_match(quote(fn()), fn)call_match(quote(fn()), fn, defaults=TRUE)
If you are working with a user-supplied call, make sure thearguments are standardised withcall_match() beforemodifying the call.
call_modify( .call, ..., .homonyms = c("keep", "first", "last", "error"), .standardise = NULL, .env = caller_env())call_modify( .call,..., .homonyms= c("keep","first","last","error"), .standardise=NULL, .env= caller_env())
.call | Can be a call, a formula quoting a call in theright-hand side, or a frame object from which to extract the callexpression. |
... | <dynamic> Named or unnamed expressions(constants, names or calls) used to modify the call. Use |
.homonyms | How to treat arguments with the same name. Thedefault, |
.standardise,.env | Deprecated as of rlang 0.3.0. Pleasecall |
A quosure if.call is a quosure, a call otherwise.
call <- quote(mean(x, na.rm = TRUE))# Modify an existing argumentcall_modify(call, na.rm = FALSE)call_modify(call, x = quote(y))# Remove an argumentcall_modify(call, na.rm = zap())# Add a new argumentcall_modify(call, trim = 0.1)# Add an explicit missing argument:call_modify(call, na.rm = )# Supply a list of new arguments with `!!!`newargs <- list(na.rm = zap(), trim = 0.1)call <- call_modify(call, !!!newargs)call# Remove multiple arguments by splicing zaps:newargs <- rep_named(c("na.rm", "trim"), list(zap()))call <- call_modify(call, !!!newargs)call# Modify the `...` arguments as if it were a named argument:call <- call_modify(call, ... = )callcall <- call_modify(call, ... = zap())call# When you're working with a user-supplied call, standardise it# beforehand in case it includes unmatched arguments:user_call <- quote(matrix(x, nc = 3))call_modify(user_call, ncol = 1)# `call_match()` applies R's argument matching rules. Matching# ensures you're modifying the intended argument.user_call <- call_match(user_call, matrix)user_callcall_modify(user_call, ncol = 1)# By default, arguments with the same name are kept. This has# subtle implications, for instance you can move an argument to# last position by removing it and remapping it:call <- quote(foo(bar = , baz))call_modify(call, bar = zap(), bar = missing_arg())# You can also choose to keep only the first or last homonym# arguments:args <- list(bar = zap(), bar = missing_arg())call_modify(call, !!!args, .homonyms = "first")call_modify(call, !!!args, .homonyms = "last")call<- quote(mean(x, na.rm=TRUE))# Modify an existing argumentcall_modify(call, na.rm=FALSE)call_modify(call, x= quote(y))# Remove an argumentcall_modify(call, na.rm= zap())# Add a new argumentcall_modify(call, trim=0.1)# Add an explicit missing argument:call_modify(call, na.rm=)# Supply a list of new arguments with `!!!`newargs<- list(na.rm= zap(), trim=0.1)call<- call_modify(call,!!!newargs)call# Remove multiple arguments by splicing zaps:newargs<- rep_named(c("na.rm","trim"), list(zap()))call<- call_modify(call,!!!newargs)call# Modify the `...` arguments as if it were a named argument:call<- call_modify(call,...=)callcall<- call_modify(call,...= zap())call# When you're working with a user-supplied call, standardise it# beforehand in case it includes unmatched arguments:user_call<- quote(matrix(x, nc=3))call_modify(user_call, ncol=1)# `call_match()` applies R's argument matching rules. Matching# ensures you're modifying the intended argument.user_call<- call_match(user_call, matrix)user_callcall_modify(user_call, ncol=1)# By default, arguments with the same name are kept. This has# subtle implications, for instance you can move an argument to# last position by removing it and remapping it:call<- quote(foo(bar=, baz))call_modify(call, bar= zap(), bar= missing_arg())# You can also choose to keep only the first or last homonym# arguments:args<- list(bar= zap(), bar= missing_arg())call_modify(call,!!!args, .homonyms="first")call_modify(call,!!!args, .homonyms="last")
call_name() andcall_ns() extract the function name ornamespace ofsimple calls as a string. They returnNULL forcomplex calls.
Simple calls:foo(),bar::foo().
Complex calls:foo()(),bar::foo,foo$bar(),(function() NULL)().
Theis_call_simple() predicate helps you determine whether a callis simple. There are two invariants you can count on:
Ifis_call_simple(x) returnsTRUE,call_name(x) returns astring. Otherwise it returnsNULL.
Ifis_call_simple(x, ns = TRUE) returnsTRUE,call_ns()returns a string. Otherwise it returnsNULL.
call_name(call)call_ns(call)is_call_simple(x, ns = NULL)call_name(call)call_ns(call)is_call_simple(x, ns=NULL)
call | A defused call. |
x | An object to test. |
ns | Whether call is namespaced. If |
The function name or namespace as a string, orNULL ifthe call is not named or namespaced.
# Is the function named?is_call_simple(quote(foo()))is_call_simple(quote(foo[[1]]()))# Is the function namespaced?is_call_simple(quote(list()), ns = TRUE)is_call_simple(quote(base::list()), ns = TRUE)# Extract the function name from quoted calls:call_name(quote(foo(bar)))call_name(quo(foo(bar)))# Namespaced calls are correctly handled:call_name(quote(base::matrix(baz)))# Anonymous and subsetted functions return NULL:call_name(quote(foo$bar()))call_name(quote(foo[[bar]]()))call_name(quote(foo()()))# Extract namespace of a call with call_ns():call_ns(quote(base::bar()))# If not namespaced, call_ns() returns NULL:call_ns(quote(bar()))# Is the function named?is_call_simple(quote(foo()))is_call_simple(quote(foo[[1]]()))# Is the function namespaced?is_call_simple(quote(list()), ns=TRUE)is_call_simple(quote(base::list()), ns=TRUE)# Extract the function name from quoted calls:call_name(quote(foo(bar)))call_name(quo(foo(bar)))# Namespaced calls are correctly handled:call_name(quote(base::matrix(baz)))# Anonymous and subsetted functions return NULL:call_name(quote(foo$bar()))call_name(quote(foo[[bar]]()))call_name(quote(foo()()))# Extract namespace of a call with call_ns():call_ns(quote(base::bar()))# If not namespaced, call_ns() returns NULL:call_ns(quote(bar()))
Quoted function calls are one of the two types ofsymbolic objects in R. They represent the action ofcalling a function, possibly with arguments. There are two ways ofcreating a quoted call:
Byquoting it. Quoting prevents functions from beingcalled. Instead, you get the description of the function call asan R object. That is, a quoted function call.
By constructing it withbase::call(),base::as.call(), orcall2(). In this case, you pass the call elements (the functionto call and the arguments to call it with) separately.
See section below for the difference betweencall2() and the baseconstructors.
call2(.fn, ..., .ns = NULL)call2(.fn,..., .ns=NULL)
.fn | Function to call. Must be a callable object: a string,symbol, call, or a function. |
... | <dynamic> Arguments for the functioncall. Empty arguments are preserved. |
.ns | Namespace with which to prefix |
call2() is more flexible thanbase::call():
The function to call can be a string or acallableobject: a symbol, another call (e.g. a$ or[[ call), or afunction to inline.base::call() only supports strings and youneed to usebase::as.call() to construct a call with a callableobject.
call2(list, 1, 2)as.call(list(list, 1, 2))
The.ns argument is convenient for creating namespaced calls.
call2("list", 1, 2, .ns = "base")# Equivalent tons_call <- call("::", as.symbol("list"), as.symbol("base"))as.call(list(ns_call, 1, 2))call2() hasdynamic dots support. You can splice listsof arguments with!!! or unquote an argument name with gluesyntax.
args <- list(na.rm = TRUE, trim = 0)call2("mean", 1:10, !!!args)# Equivalent toas.call(c(list(as.symbol("mean"), 1:10), args))call2() makes it possible to inline objects in calls, both infunction and argument positions. Inlining an object or a functionhas the advantage that the correct object is used in allenvironments. If all components of the code are inlined, you caneven evaluate in theempty environment.
However inlining also has drawbacks. It can cause issues with NSEfunctions that expect symbolic arguments. The objects may also leakin representations of the call stack, such astraceback().
# fn can either be a string, a symbol or a callcall2("f", a = 1)call2(quote(f), a = 1)call2(quote(f()), a = 1)#' Can supply arguments individually or in a listcall2(quote(f), a = 1, b = 2)call2(quote(f), !!!list(a = 1, b = 2))# Creating namespaced calls is easy:call2("fun", arg = quote(baz), .ns = "mypkg")# Empty arguments are preserved:call2("[", quote(x), , drop = )# fn can either be a string, a symbol or a callcall2("f", a=1)call2(quote(f), a=1)call2(quote(f()), a=1)#' Can supply arguments individually or in a listcall2(quote(f), a=1, b=2)call2(quote(f),!!!list(a=1, b=2))# Creating namespaced calls is easy:call2("fun", arg= quote(baz), .ns="mypkg")# Empty arguments are preserved:call2("[", quote(x),, drop=)
caller_arg() is a variant ofsubstitute() orensym() forarguments that reference other arguments. Unlikesubstitute()which returns an expression,caller_arg() formats the expressionas a single line string which can be included in error messages.
When included in an error message, the resulting label shouldgenerally be formatted as argument, for instance using the.argin the cli package.
Use@inheritParams rlang::args_error_context to document anarg orerror_arg argument that takeserror_arg() as default.
arg | An argument name in the current function. |
arg_checker <- function(x, arg = caller_arg(x), call = caller_env()) { cli::cli_abort("{.arg {arg}} must be a thingy.", arg = arg, call = call)}my_function <- function(my_arg) { arg_checker(my_arg)}try(my_function(NULL))arg_checker<-function(x, arg= caller_arg(x), call= caller_env()){ cli::cli_abort("{.arg {arg}} must be a thingy.", arg= arg, call= call)}my_function<-function(my_arg){ arg_checker(my_arg)}try(my_function(NULL))
This is a small wrapper aroundtryCatch() that captures anycondition signalled while evaluating its argument. It is useful forsituations where you expect a specific condition to be signalled,for debugging, and for unit testing.
catch_cnd(expr, classes = "condition")catch_cnd(expr, classes="condition")
expr | Expression to be evaluated with a catching conditionhandler. |
classes | A character vector of condition classes to catch. Bydefault, catches all conditions. |
A condition if any was signalled,NULL otherwise.
catch_cnd(10)catch_cnd(abort("an error"))catch_cnd(signal("my_condition", message = "a condition"))catch_cnd(10)catch_cnd(abort("an error"))catch_cnd(signal("my_condition", message="a condition"))
... can be inserted in a function signature to force users tofully name the details arguments. In this case, supplying data in... is almost always a programming error. This function checksthat... is empty and fails otherwise.
check_dots_empty( env = caller_env(), error = NULL, call = caller_env(), action = abort)check_dots_empty( env= caller_env(), error=NULL, call= caller_env(), action= abort)
env | Environment in which to look for |
error | An optional error handler passed to |
call | The execution environment of a currentlyrunning function, e.g. |
action |
In packages, document... with this standard tag:
@inheritParams rlang::args_dots_empty
Other dots checking functions:check_dots_unnamed(),check_dots_used()
f <- function(x, ..., foofy = 8) { check_dots_empty() x + foofy}# This fails because `foofy` can't be matched positionallytry(f(1, 4))# This fails because `foofy` can't be matched partially by nametry(f(1, foof = 4))# Thanks to `...`, it must be matched exactlyf(1, foofy = 4)f<-function(x,..., foofy=8){ check_dots_empty() x+ foofy}# This fails because `foofy` can't be matched positionallytry(f(1,4))# This fails because `foofy` can't be matched partially by nametry(f(1, foof=4))# Thanks to `...`, it must be matched exactlyf(1, foofy=4)
In functions likepaste(), named arguments in... are often asign of misspelled argument names. Callcheck_dots_unnamed() tofail with an error when named arguments are detected.
check_dots_unnamed( env = caller_env(), error = NULL, call = caller_env(), action = abort)check_dots_unnamed( env= caller_env(), error=NULL, call= caller_env(), action= abort)
env | Environment in which to look for |
error | An optional error handler passed to |
call | The execution environment of a currentlyrunning function, e.g. |
action |
Other dots checking functions:check_dots_empty(),check_dots_used()
f <- function(..., foofy = 8) { check_dots_unnamed() c(...)}f(1, 2, 3, foofy = 4)try(f(1, 2, 3, foof = 4))f<-function(..., foofy=8){ check_dots_unnamed() c(...)}f(1,2,3, foofy=4)try(f(1,2,3, foof=4))
When... arguments are passed to a method, the method should matchand use these arguments. If this isn't the case, this often indicatesa programming error. Callcheck_dots_used() to fail with an error whenunused arguments are detected.
check_dots_used( env = caller_env(), call = caller_env(), error = NULL, action = deprecated())check_dots_used( env= caller_env(), call= caller_env(), error=NULL, action= deprecated())
env | Environment in which to look for |
call | The execution environment of a currentlyrunning function, e.g. |
error | An optional error handler passed to |
action |
In packages, document... with this standard tag:
@inheritParams rlang::args_dots_used
check_dots_used() implicitly callson.exit() to check that allelements of... have been used when the function exits. If youuseon.exit() elsewhere in your function, make sure to useadd = TRUE so that you don't override the handler set up bycheck_dots_used().
Other dots checking functions:check_dots_empty(),check_dots_unnamed()
f <- function(...) { check_dots_used() g(...)}g <- function(x, y, ...) { x + y}f(x = 1, y = 2)try(f(x = 1, y = 2, z = 3))try(f(x = 1, y = 2, 3, 4, 5))# Use an `error` handler to handle the error differently.# For instance to demote the error to a warning:fn <- function(...) { check_dots_empty( error = function(cnd) { warning(cnd) } ) "out"}fn()f<-function(...){ check_dots_used() g(...)}g<-function(x, y,...){ x+ y}f(x=1, y=2)try(f(x=1, y=2, z=3))try(f(x=1, y=2,3,4,5))# Use an `error` handler to handle the error differently.# For instance to demote the error to a warning:fn<-function(...){ check_dots_empty( error=function(cnd){ warning(cnd)})"out"}fn()
check_exclusive() checks that only one argument is supplied out ofa set of mutually exclusive arguments. An informative error isthrown if multiple arguments are supplied.
check_exclusive(..., .require = TRUE, .frame = caller_env(), .call = .frame)check_exclusive(..., .require=TRUE, .frame= caller_env(), .call= .frame)
... | Function arguments. |
.require | Whether at least one argument must be supplied. |
.frame | Environment where the arguments in |
.call | The execution environment of a currentlyrunning function, e.g. |
The supplied argument name as a string. If.require isFALSE and no argument is supplied, the empty string"" isreturned.
f <- function(x, y) { switch( check_exclusive(x, y), x = message("`x` was supplied."), y = message("`y` was supplied.") )}# Supplying zero or multiple arguments is forbiddentry(f())try(f(NULL, NULL))# The user must supply one of the mutually exclusive argumentsf(NULL)f(y = NULL)# With `.require` you can allow zero argumentsf <- function(x, y) { switch( check_exclusive(x, y, .require = FALSE), x = message("`x` was supplied."), y = message("`y` was supplied."), message("No arguments were supplied") )}f()f<-function(x, y){ switch( check_exclusive(x, y), x= message("`x` was supplied."), y= message("`y` was supplied."))}# Supplying zero or multiple arguments is forbiddentry(f())try(f(NULL,NULL))# The user must supply one of the mutually exclusive argumentsf(NULL)f(y=NULL)# With `.require` you can allow zero argumentsf<-function(x, y){ switch( check_exclusive(x, y, .require=FALSE), x= message("`x` was supplied."), y= message("`y` was supplied."), message("No arguments were supplied"))}f()
Throws an error ifx is missing.
check_required(x, arg = caller_arg(x), call = caller_env())check_required(x, arg= caller_arg(x), call= caller_env())
x | A function argument. Must be a symbol. |
arg | An argument name as a string. This argumentwill be mentioned in error messages as the input that is at theorigin of a problem. |
call | The execution environment of a currentlyrunning function, e.g. |
f <- function(x) { check_required(x)}# Fails because `x` is not suppliedtry(f())# Succeedsf(NULL)f<-function(x){ check_required(x)}# Fails because `x` is not suppliedtry(f())# Succeedsf(NULL)
Like any R objects, errors captured with catchers liketryCatch()have aclass() which you can test withinherits(). However,with chained errors, the class of a captured error might bedifferent than the error that was originally signalled. Usecnd_inherits() to detect whether an error or any of itsparentinherits from a class.
Whereasinherits() tells you whether an object is a particularkind of error,cnd_inherits() answers the question whether anobject is a particular kind of error or has been caused by such anerror.
Some chained conditions carry parents that are not inherited. Seethe.inherit argument ofabort(),warn(), andinform().
cnd_inherits(cnd, class)cnd_inherits(cnd, class)
cnd | A condition to test. |
class | A class passed to |
cnd_inherits()Error catchers liketryCatch() andtry_fetch() can only matchthe class of a condition, not the class of its parents. To match aclass across the ancestry of an error, you'll need a bit ofcraftiness.
Ancestry matching can't be done withtryCatch() at all so you'llneed to switch towithCallingHandlers(). Alternatively, you canuse the experimental rlang functiontry_fetch() which is able toperform the roles of bothtryCatch() andwithCallingHandlers().
withCallingHandlers()UnliketryCatch(),withCallingHandlers() does not capture anerror. If you don't explicitly jump with anerror or avaluethrow, nothing happens.
Since we don't want to throw an error, we'll throw a value usingcallCC():
f <- function() { parent <- error_cnd("bar", message = "Bar") abort("Foo", parent = parent)}cnd <- callCC(function(throw) { withCallingHandlers( f(), error = function(x) if (cnd_inherits(x, "bar")) throw(x) )})class(cnd)#> [1] "rlang_error" "error" "condition"class(cnd$parent)#> [1] "bar" "rlang_error" "error" "condition"try_fetch()This pattern is easier withtry_fetch(). LikewithCallingHandlers(), it doesn't capture a matching error rightaway. Instead, it captures it only if the handler doesn't return azap() value.
cnd <- try_fetch( f(), error = function(x) if (cnd_inherits(x, "bar")) x else zap())class(cnd)#> [1] "rlang_error" "error" "condition"class(cnd$parent)#> [1] "bar" "rlang_error" "error" "condition"
Note thattry_fetch() usescnd_inherits() internally. Thismakes it very easy to match a parent condition:
cnd <- try_fetch( f(), bar = function(x) x)# This is the parentclass(cnd)#> [1] "bar" "rlang_error" "error" "condition"
cnd_message() assembles an error message from three generics:
cnd_header()
cnd_body()
cnd_footer()
Methods for these generics must return a character vector. Theelements are combined into a single string with a newlineseparator. Bullets syntax is supported, either through rlang (seeformat_error_bullets()), or through cli if the condition hasuse_cli_format set toTRUE.
The default method for the error header returns themessage fieldof the condition object. The default methods for the body andfooter return the thebody andfooter fields if any, or emptycharacter vectors otherwise.
cnd_message() is automatically called by theconditionMessage()for rlang errors, warnings, and messages. Error classes createdwithabort() only need to implement header, body or footermethods. This provides a lot of flexibility for hierarchies oferror classes, for instance you could inherit the body of an errormessage from a parent class while overriding the header and footer.
cnd_message(cnd, ..., inherit = TRUE, prefix = FALSE)cnd_header(cnd, ...)cnd_body(cnd, ...)cnd_footer(cnd, ...)cnd_message(cnd,..., inherit=TRUE, prefix=FALSE)cnd_header(cnd,...)cnd_body(cnd,...)cnd_footer(cnd,...)
cnd | A condition object. |
... | Arguments passed to methods. |
inherit | Wether to include parent messages. Parent messagesare printed with a "Caused by error:" prefix, even if |
prefix | Whether to print the full message, including thecondition prefix ( |
Sometimes the contents of an error message depends on the state ofyour checking routine. In that case, it can be tricky to lazilygenerate error messages withcnd_header(),cnd_body(), andcnd_footer(): you have the choice between overspecifying yourerror class hierarchies with one class per state, or replicatingthe type-checking control flow within thecnd_body() method. Noneof these options are ideal.
A better option is to defineheader,body, orfooter fieldsin your condition object. These can be a static string, alambda-formula, or a function with the samesignature ascnd_header(),cnd_body(), orcnd_footer(). Thesefields override the message generics and make it easy to generatean error message tailored to the state in which the error wasconstructed.
cnd_signal() takes a condition as argument and emits thecorresponding signal. The type of signal depends on the class ofthe condition:
A message is signalled if the condition inherits from"message". This is equivalent to signalling withinform() orbase::message().
A warning is signalled if the condition inherits from"warning". This is equivalent to signalling withwarn() orbase::warning().
An error is signalled if the condition inherits from"error". This is equivalent to signalling withabort() orbase::stop().
An interrupt is signalled if the condition inherits from"interrupt". This is equivalent to signalling withinterrupt().
cnd_signal(cnd, ...)cnd_signal(cnd,...)
cnd | A condition object (see |
... | These dots are for future extensions and must be empty. |
cnd_type() to determine the type of a condition.
abort(),warn() andinform() for creating and signallingstructured R conditions in one go.
try_fetch() for establishing condition handlers forparticular condition classes.
# The type of signal depends on the class. If the condition# inherits from "warning", a warning is issued:cnd <- warning_cnd("my_warning_class", message = "This is a warning")cnd_signal(cnd)# If it inherits from "error", an error is raised:cnd <- error_cnd("my_error_class", message = "This is an error")try(cnd_signal(cnd))# The type of signal depends on the class. If the condition# inherits from "warning", a warning is issued:cnd<- warning_cnd("my_warning_class", message="This is a warning")cnd_signal(cnd)# If it inherits from "error", an error is raised:cnd<- error_cnd("my_error_class", message="This is an error")try(cnd_signal(cnd))
A value boxed withdone() signals to its caller that itshould stop iterating. Use it to shortcircuit a loop.
done(x)is_done_box(x, empty = NULL)done(x)is_done_box(x, empty=NULL)
x | For |
empty | Whether the box is empty. If |
Aboxed value.
done(3)x <- done(3)is_done_box(x)done(3)x<- done(3)is_done_box(x)
.data and.env pronounsThe.data and.env pronouns make it explicit where to findobjects when programming withdata-maskedfunctions.
m <- 10mtcars %>% mutate(disp = .data$disp * .env$m)
.data retrieves data-variables from the data frame.
.env retrieves env-variables from the environment.
Because the lookup is explicit, there is no ambiguity between bothkinds of variables. Compare:
disp <- 10mtcars %>% mutate(disp = .data$disp * .env$disp)mtcars %>% mutate(disp = disp * disp)
Note that.data is only a pronoun, it is not a real dataframe. This means that you can't take its names or map a functionover the contents of.data. Similarly,.env is not an actual Renvironment. For instance, it doesn't have a parent and thesubsetting operators behave differently.
.data versus the magrittr pronoun.In amagrittr pipeline,.datais not necessarily interchangeable with the magrittr pronoun..With grouped data frames in particular,.data represents thecurrent group slice whereas the pronoun. represents the wholedata frame. Always prefer using.data in data-masked context.
.data live?The.data pronoun is automatically created for you bydata-masking functions using thetidy eval framework.You don't need to importrlang::.data or uselibrary(rlang) towork with this pronoun.
However, the.data object exported from rlang is useful to importin your package namespace to avoid aR CMD check note whenreferring to objects from the data mask. R does not have any way ofknowing about the presence or absence of.data in a particularscope so you need to import it explicitly or equivalently declareit withutils::globalVariables(".data").
Note thatrlang::.data is a "fake" pronoun. Do not refer torlang::.data with therlang:: qualifier in data maskingcode. Use the unqualified.data symbol that is automatically putin scope by data-masking functions.
The base... syntax supports:
Forwarding arguments from function to function, matching themalong the way to arguments.
Collecting arguments inside data structures, e.g. withc() orlist().
Dynamic dots offer a few additional features,injection in particular:
You cansplice arguments saved in a list with the spliceoperator!!!.
You caninject names withglue syntax onthe left-hand side of:=.
Trailing commas are ignored, making it easier to copy and pastelines of arguments.
If your function takes dots, adding support for dynamic features isas easy as collecting the dots withlist2() instead oflist().See alsodots_list(), which offers more control over the collection.
In general, passing... to a function that supports dynamic dotscauses your function to inherit the dynamic behaviour.
In packages, document dynamic dots with this standard tag:
@param ... <[`dynamic-dots`][rlang::dyn-dots]> What these dots do.
f <- function(...) { out <- list2(...) rev(out)}# Trailing commas are ignoredf(this = "that", )# Splice lists of arguments with `!!!`x <- list(alpha = "first", omega = "last")f(!!!x)# Inject a name using glue syntaxif (is_installed("glue")) { nm <- "key" f("{nm}" := "value") f("prefix_{nm}" := "value")}f<-function(...){ out<- list2(...) rev(out)}# Trailing commas are ignoredf(this="that",)# Splice lists of arguments with `!!!`x<- list(alpha="first", omega="last")f(!!!x)# Inject a name using glue syntaxif(is_installed("glue")){ nm<-"key" f("{nm}":="value") f("prefix_{nm}":="value")}
{{The embrace operator{{ is used to create functions that callotherdata-masking functions. It transports adata-masked argument (an argument that can refer to columns of adata frame) from one function to another.
my_mean <- function(data, var) { dplyr::summarise(data, mean = mean({{ var }}))}{{ combinesenquo() and!! in onestep. The snippet above is equivalent to:
my_mean <- function(data, var) { var <- enquo(var) dplyr::summarise(data, mean = mean(!!var))}The empty environment is the only one that does not have a parent.It is always used as the tail of an environment chain such as thesearch path (seesearch_envs()).
empty_env()empty_env()
# Create environments with nothing in scope:child_env(empty_env())# Create environments with nothing in scope:child_env(empty_env())
englue() creates a string with theglue operators{ and{{. These operators arenormally used to inject names withindynamic dots.englue() makes them available anywhere within a function.
englue() must be used inside a function.englue("{{ var }}")defuses the argumentvar and transforms it to astring using the default name operation.
englue(x, env = caller_env(), error_call = current_env(), error_arg = "x")englue(x, env= caller_env(), error_call= current_env(), error_arg="x")
x | A string to interpolate with glue operators. |
env | User environment where the interpolation data lives incase you're wrapping |
error_call | The execution environment of a currentlyrunning function, e.g. |
error_arg | An argument name as a string. This argumentwill be mentioned in error messages as the input that is at theorigin of a problem. |
englue("{{ var }}") is equivalent toas_label(enquo(var)). Itdefusesarg and transforms the expression to astring withas_label().
In dynamic dots, using only{ is allowed. Inenglue() you mustuse{{ at least once. Useglue::glue() for simpleinterpolation.
Before usingenglue() in a package, first ensure that glue isinstalled by adding it to yourImports: section.
usethis::use_package("glue", "Imports")englue()You can provide englue semantics to a user provided string by supplyingenv.In this example we create a variant ofenglue() that supports aspecial.qux pronoun by:
Creating an environmentmasked_env that inherits from the userenv, the one where their data lives.
Overriding theerror_arg anderror_call arguments to point toour own argument name and call environment. This pattern isslightly different from usual error context passing becauseenglue() is a backend function that uses its own error contextby default (and not a checking function that usesyour errorcontext by default).
my_englue <- function(text) { masked_env <- env(caller_env(), .qux = "QUX") englue( text, env = masked_env, error_arg = "text", error_call = current_env() )}# Users can then use your wrapper as they would use `englue()`:fn <- function(x) { foo <- "FOO" my_englue("{{ x }}_{.qux}_{foo}")}fn(bar)#> [1] "bar_QUX_FOO"If you are creating a low level package on top of englue(), youshould also consider exposingenv,error_arg anderror_callin yourenglue() wrapper so users can wrap your wrapper.
g <- function(var) englue("{{ var }}")g(cyl)g(1 + 1)g(!!letters)# These are equivalent toas_label(quote(cyl))as_label(quote(1 + 1))as_label(letters)g<-function(var) englue("{{ var }}")g(cyl)g(1+1)g(!!letters)# These are equivalent toas_label(quote(cyl))as_label(quote(1+1))as_label(letters)
enquo() andenquos()defuse function arguments.A defused expression can be examined, modified, and injected intoother expressions.
Defusing function arguments is useful for:
Creating data-masking functions.
Interfacing with anotherdata-masking functionusing thedefuse-and-inject pattern.
These are advanced tools. Make sure to first learn about the embraceoperator{{ inData mask programming patterns.{{ is easier to work with less theory, and it is sufficientin most applications.
enquo(arg)enquos( ..., .named = FALSE, .ignore_empty = c("trailing", "none", "all"), .ignore_null = c("none", "all"), .unquote_names = TRUE, .homonyms = c("keep", "first", "last", "error"), .check_assign = FALSE)enquo(arg)enquos(..., .named=FALSE, .ignore_empty= c("trailing","none","all"), .ignore_null= c("none","all"), .unquote_names=TRUE, .homonyms= c("keep","first","last","error"), .check_assign=FALSE)
arg | An unquoted argument name. The expressionsupplied to that argument is defused and returned. |
... | Names of arguments to defuse. |
.named | If |
.ignore_empty | Whether to ignore empty arguments. Can be oneof |
.ignore_null | Whether to ignore unnamed null arguments. Can be |
.unquote_names | Whether to treat |
.homonyms | How to treat arguments with the same name. Thedefault, |
.check_assign | Whether to check for |
enquo() returns aquosure andenquos()returns a list of quosures.
Arguments defused withenquo() andenquos() automatically gaininjection support.
my_mean <- function(data, var) { var <- enquo(var) dplyr::summarise(data, mean(!!var))}# Can now use `!!` and `{{`my_mean(mtcars, !!sym("cyl"))Seeenquo0() andenquos0() for variants that don't enableinjection.
Defusing R expressions for an overview.
expr() to defuse your own local expressions.
base::eval() andeval_bare() for resuming evaluationof a defused expression.
# `enquo()` defuses the expression supplied by your userf <- function(arg) { enquo(arg)}f(1 + 1)# `enquos()` works with arguments and dots. It returns a list of# expressionsf <- function(...) { enquos(...)}f(1 + 1, 2 * 10)# `enquo()` and `enquos()` enable _injection_ and _embracing_ for# your usersg <- function(arg) { f({{ arg }} * 2)}g(100)column <- sym("cyl")g(!!column)# `enquo()` defuses the expression supplied by your userf<-function(arg){ enquo(arg)}f(1+1)# `enquos()` works with arguments and dots. It returns a list of# expressionsf<-function(...){ enquos(...)}f(1+1,2*10)# `enquo()` and `enquos()` enable _injection_ and _embracing_ for# your usersg<-function(arg){ f({{ arg}}*2)}g(100)column<- sym("cyl")g(!!column)
These functions create new environments.
env() creates a child of the current environment by defaultand takes a variable number of named objects to populate it.
new_environment() creates a child of the empty environment bydefault and takes a named list of objects to populate it.
env(...)new_environment(data = list(), parent = empty_env())env(...)new_environment(data= list(), parent= empty_env())
...,data | <dynamic> Named values. You cansupply one unnamed to specify a custom parent, otherwise itdefaults to the current environment. |
parent | A parent environment. |
Environments are containers of uniquely named objects. Their mostcommon use is to provide a scope for the evaluation of Rexpressions. Not all languages have first class environments,i.e. can manipulate scope as regular objects. Reification of scopeis one of the most powerful features of R as it allows you to changewhat objects a function or expression sees when it is evaluated.
Environments also constitute a data structure in their ownright. They are a collection of uniquely named objects, subsettableby name and modifiable by reference. This latter property (seesection on reference semantics) is especially useful for creatingmutable OO systems (cf theR6 packageand theggproto systemfor extending ggplot2).
All R environments (except theempty environment) aredefined with a parent environment. An environment and itsgrandparents thus form a linear hierarchy that is the basis forlexical scoping inR. When R evaluates an expression, it looks up symbols in a givenenvironment. If it cannot find these symbols there, it keepslooking them up in parent environments. This way, objects definedin child environments have precedence over objects defined inparent environments.
The ability of overriding specific definitions is used in thetidyeval framework to create powerful domain-specific grammars. Acommon use of masking is to put data frame columns in scope. Seefor exampleas_data_mask().
Unlike regular objects such as vectors, environments are anuncopyable object type. This means that if youhave multiple references to a given environment (by assigning theenvironment to another symbol with<- or passing the environmentas argument to a function), modifying the bindings of one of thosereferences changes all other references as well.
# env() creates a new environment that inherits from the current# environment by defaultenv <- env(a = 1, b = "foo")env$bidentical(env_parent(env), current_env())# Supply one unnamed argument to inherit from another environment:env <- env(base_env(), a = 1, b = "foo")identical(env_parent(env), base_env())# Both env() and child_env() support tidy dots features:objs <- list(b = "foo", c = "bar")env <- env(a = 1, !!! objs)env$c# You can also unquote names with the definition operator `:=`var <- "a"env <- env(!!var := "A")env$a# Use new_environment() to create containers with the empty# environment as parent:env <- new_environment()env_parent(env)# Like other new_ constructors, it takes an object rather than dots:new_environment(list(a = "foo", b = "bar"))# env() creates a new environment that inherits from the current# environment by defaultenv<- env(a=1, b="foo")env$bidentical(env_parent(env), current_env())# Supply one unnamed argument to inherit from another environment:env<- env(base_env(), a=1, b="foo")identical(env_parent(env), base_env())# Both env() and child_env() support tidy dots features:objs<- list(b="foo", c="bar")env<- env(a=1,!!! objs)env$c# You can also unquote names with the definition operator `:=`var<-"a"env<- env(!!var:="A")env$a# Use new_environment() to create containers with the empty# environment as parent:env<- new_environment()env_parent(env)# Like other new_ constructors, it takes an object rather than dots:new_environment(list(a="foo", b="bar"))
These functions create bindings in an environment. The bindings aresupplied through... as pairs of names and values or expressions.env_bind() is equivalent to evaluating a<- expression withinthe given environment. This function should take care of themajority of use cases but the other variants can be useful forspecific problems.
env_bind() takes namedvalues which are bound in.env.env_bind() is equivalent tobase::assign().
env_bind_active() takes namedfunctions and creates activebindings in.env. This is equivalent tobase::makeActiveBinding(). An active binding executes afunction each time it is evaluated. The arguments are passed toas_function() so you can supply formulas instead of functions.
Remember that functions are scoped in their own environment.These functions can thus refer to symbols from this enclosurethat are not actually in scope in the dynamic environment wherethe active bindings are invoked. This allows creative solutionsto difficult problems (see the implementations ofdplyr::do()methods for an example).
env_bind_lazy() takes namedexpressions. This is equivalenttobase::delayedAssign(). The arguments are captured withexprs() (and thus support call-splicing and unquoting) andassigned to symbols in.env. These expressions are notevaluated immediately but lazily. Once a symbol is evaluated, thecorresponding expression is evaluated in turn and its value isbound to the symbol (the expressions are thus evaluated onlyonce, if at all).
%<~% is a shortcut forenv_bind_lazy(). It works like<-but the RHS is evaluated lazily.
env_bind(.env, ...)env_bind_lazy(.env, ..., .eval_env = caller_env())env_bind_active(.env, ...)lhs %<~% rhsenv_bind(.env,...)env_bind_lazy(.env,..., .eval_env= caller_env())env_bind_active(.env,...)lhs%<~% rhs
.env | An environment. |
... | <dynamic> Named objects ( |
.eval_env | The environment where the expressions will beevaluated when the symbols are forced. |
lhs | The variable name to which |
rhs | An expression lazily evaluated and assigned to |
The input object.env, with its associated environmentmodified in place, invisibly.
Since environments have reference semantics (see relevant sectioninenv() documentation), modifying the bindings of an environmentproduces effects in all other references to that environment. Inother words,env_bind() and its variants have side effects.
Like other side-effecty functions likepar() andoptions(),env_bind() and variants return the old values invisibly.
env_poke() for binding a single element.
# env_bind() is a programmatic way of assigning values to symbols# with `<-`. We can add bindings in the current environment:env_bind(current_env(), foo = "bar")foo# Or modify those bindings:bar <- "bar"env_bind(current_env(), bar = "BAR")bar# You can remove bindings by supplying zap sentinels:env_bind(current_env(), foo = zap())try(foo)# Unquote-splice a named list of zapszaps <- rep_named(c("foo", "bar"), list(zap()))env_bind(current_env(), !!!zaps)try(bar)# It is most useful to change other environments:my_env <- env()env_bind(my_env, foo = "foo")my_env$foo# A useful feature is to splice lists of named values:vals <- list(a = 10, b = 20)env_bind(my_env, !!!vals, c = 30)my_env$bmy_env$c# You can also unquote a variable referring to a symbol or a string# as binding name:var <- "baz"env_bind(my_env, !!var := "BAZ")my_env$baz# The old values of the bindings are returned invisibly:old <- env_bind(my_env, a = 1, b = 2, baz = "baz")old# You can restore the original environment state by supplying the# old values back:env_bind(my_env, !!!old)# env_bind_lazy() assigns expressions lazily:env <- env()env_bind_lazy(env, name = { cat("forced!\n"); "value" })# Referring to the binding will cause evaluation:env$name# But only once, subsequent references yield the final value:env$name# You can unquote expressions:expr <- quote(message("forced!"))env_bind_lazy(env, name = !!expr)env$name# By default the expressions are evaluated in the current# environment. For instance we can create a local binding and refer# to it, even though the variable is bound in a different# environment:who <- "mickey"env_bind_lazy(env, name = paste(who, "mouse"))env$name# You can specify another evaluation environment with `.eval_env`:eval_env <- env(who = "minnie")env_bind_lazy(env, name = paste(who, "mouse"), .eval_env = eval_env)env$name# Or by unquoting a quosure:quo <- local({ who <- "fievel" quo(paste(who, "mouse"))})env_bind_lazy(env, name = !!quo)env$name# You can create active bindings with env_bind_active(). Active# bindings execute a function each time they are evaluated:fn <- function() { cat("I have been called\n") rnorm(1)}env <- env()env_bind_active(env, symbol = fn)# `fn` is executed each time `symbol` is evaluated or retrieved:env$symbolenv$symboleval_bare(quote(symbol), env)eval_bare(quote(symbol), env)# All arguments are passed to as_function() so you can use the# formula shortcut:env_bind_active(env, foo = ~ runif(1))env$fooenv$foo# env_bind() is a programmatic way of assigning values to symbols# with `<-`. We can add bindings in the current environment:env_bind(current_env(), foo="bar")foo# Or modify those bindings:bar<-"bar"env_bind(current_env(), bar="BAR")bar# You can remove bindings by supplying zap sentinels:env_bind(current_env(), foo= zap())try(foo)# Unquote-splice a named list of zapszaps<- rep_named(c("foo","bar"), list(zap()))env_bind(current_env(),!!!zaps)try(bar)# It is most useful to change other environments:my_env<- env()env_bind(my_env, foo="foo")my_env$foo# A useful feature is to splice lists of named values:vals<- list(a=10, b=20)env_bind(my_env,!!!vals, c=30)my_env$bmy_env$c# You can also unquote a variable referring to a symbol or a string# as binding name:var<-"baz"env_bind(my_env,!!var:="BAZ")my_env$baz# The old values of the bindings are returned invisibly:old<- env_bind(my_env, a=1, b=2, baz="baz")old# You can restore the original environment state by supplying the# old values back:env_bind(my_env,!!!old)# env_bind_lazy() assigns expressions lazily:env<- env()env_bind_lazy(env, name={ cat("forced!\n");"value"})# Referring to the binding will cause evaluation:env$name# But only once, subsequent references yield the final value:env$name# You can unquote expressions:expr<- quote(message("forced!"))env_bind_lazy(env, name=!!expr)env$name# By default the expressions are evaluated in the current# environment. For instance we can create a local binding and refer# to it, even though the variable is bound in a different# environment:who<-"mickey"env_bind_lazy(env, name= paste(who,"mouse"))env$name# You can specify another evaluation environment with `.eval_env`:eval_env<- env(who="minnie")env_bind_lazy(env, name= paste(who,"mouse"), .eval_env= eval_env)env$name# Or by unquoting a quosure:quo<- local({ who<-"fievel" quo(paste(who,"mouse"))})env_bind_lazy(env, name=!!quo)env$name# You can create active bindings with env_bind_active(). Active# bindings execute a function each time they are evaluated:fn<-function(){ cat("I have been called\n") rnorm(1)}env<- env()env_bind_active(env, symbol= fn)# `fn` is executed each time `symbol` is evaluated or retrieved:env$symbolenv$symboleval_bare(quote(symbol), env)eval_bare(quote(symbol), env)# All arguments are passed to as_function() so you can use the# formula shortcut:env_bind_active(env, foo=~ runif(1))env$fooenv$foo
env_browse(env) is equivalent to evaluatingbrowser() inenv. It persistently sets the environment for step-debugging.Supplyvalue = FALSE to disable browsing.
env_is_browsed() is a predicate that inspects whether anenvironment is being browsed.
env_browse(env, value = TRUE)env_is_browsed(env)env_browse(env, value=TRUE)env_is_browsed(env)
env | An environment. |
value | Whether to browse |
env_browse() returns the previous value ofenv_is_browsed() (a logical), invisibly.
env_cache() is a wrapper aroundenv_get() andenv_poke()designed to retrieve a cached value fromenv.
If thenm binding exists, it returns its value.
Otherwise, it stores the default value inenv and returns that.
env_cache(env, nm, default)env_cache(env, nm, default)
env | An environment. |
nm | Name of binding, a string. |
default | The default value to store in |
Either the value ofnm ordefault if it did not existyet.
e <- env(a = "foo")# Returns existing bindingenv_cache(e, "a", "default")# Creates a `b` binding and returns its default valueenv_cache(e, "b", "default")# Now `b` is definede$be<- env(a="foo")# Returns existing bindingenv_cache(e,"a","default")# Creates a `b` binding and returns its default valueenv_cache(e,"b","default")# Now `b` is definede$b
env_clone() creates a new environment containing exactly thesame bindings as the input, optionally with a new parent.
env_coalesce() copies binding from the RHS environment into theLHS. If the RHS already contains bindings with the same name asin the LHS, those are kept as is.
Both these functions preserve active bindings and promises.
env_clone(env, parent = env_parent(env))env_coalesce(env, from)env_clone(env, parent= env_parent(env))env_coalesce(env, from)
env | An environment. |
parent | The parent of the cloned environment. |
from | Environment to copy bindings from. |
# A clone initially contains the same bindings as the original# environmentenv <- env(a = 1, b = 2)clone <- env_clone(env)env_print(clone)env_print(env)# But it can acquire new bindings or change existing ones without# impacting the original environmentenv_bind(clone, a = "foo", c = 3)env_print(clone)env_print(env)# `env_coalesce()` copies bindings from one environment to anotherlhs <- env(a = 1)rhs <- env(a = "a", b = "b", c = "c")env_coalesce(lhs, rhs)env_print(lhs)# To copy all the bindings from `rhs` into `lhs`, first delete the# conflicting bindings from `rhs`env_unbind(lhs, env_names(rhs))env_coalesce(lhs, rhs)env_print(lhs)# A clone initially contains the same bindings as the original# environmentenv<- env(a=1, b=2)clone<- env_clone(env)env_print(clone)env_print(env)# But it can acquire new bindings or change existing ones without# impacting the original environmentenv_bind(clone, a="foo", c=3)env_print(clone)env_print(env)# `env_coalesce()` copies bindings from one environment to anotherlhs<- env(a=1)rhs<- env(a="a", b="b", c="c")env_coalesce(lhs, rhs)env_print(lhs)# To copy all the bindings from `rhs` into `lhs`, first delete the# conflicting bindings from `rhs`env_unbind(lhs, env_names(rhs))env_coalesce(lhs, rhs)env_print(lhs)
This function returns the number of environments betweenenv andtheempty environment, includingenv. The depth ofenv is also the number of parents ofenv (since the emptyenvironment counts as a parent).
env_depth(env)env_depth(env)
env | An environment. |
An integer.
The section on inheritance inenv() documentation.
env_depth(empty_env())env_depth(pkg_env("rlang"))env_depth(empty_env())env_depth(pkg_env("rlang"))
env_get() extracts an object from an enviromentenv. Bydefault, it does not look in the parent environments.env_get_list() extracts multiple objects from an environment intoa named list.
env_get(env = caller_env(), nm, default, inherit = FALSE, last = empty_env())env_get_list( env = caller_env(), nms, default, inherit = FALSE, last = empty_env())env_get(env= caller_env(), nm, default, inherit=FALSE, last= empty_env())env_get_list( env= caller_env(), nms, default, inherit=FALSE, last= empty_env())
env | An environment. |
nm | Name of binding, a string. |
default | A default value in case there is no binding for |
inherit | Whether to look for bindings in the parentenvironments. |
last | Last environment inspected when |
nms | Names of bindings, a character vector. |
An object if it exists. Otherwise, throws an error.
env_cache() for a variant ofenv_get() designed tocache a value in an environment.
parent <- child_env(NULL, foo = "foo")env <- child_env(parent, bar = "bar")# This throws an error because `foo` is not directly defined in env:# env_get(env, "foo")# However `foo` can be fetched in the parent environment:env_get(env, "foo", inherit = TRUE)# You can also avoid an error by supplying a default value:env_get(env, "foo", default = "FOO")parent<- child_env(NULL, foo="foo")env<- child_env(parent, bar="bar")# This throws an error because `foo` is not directly defined in env:# env_get(env, "foo")# However `foo` can be fetched in the parent environment:env_get(env,"foo", inherit=TRUE)# You can also avoid an error by supplying a default value:env_get(env,"foo", default="FOO")
env_has() is a vectorised predicate that queries whether anenvironment owns bindings personally (withinherit set toFALSE, the default), or sees them in its own environment or inany of its parents (withinherit = TRUE).
env_has(env = caller_env(), nms, inherit = FALSE)env_has(env= caller_env(), nms, inherit=FALSE)
env | An environment. |
nms | A character vector of binding names for which to checkexistence. |
inherit | Whether to look for bindings in the parentenvironments. |
A named logical vector as long asnms.
parent <- child_env(NULL, foo = "foo")env <- child_env(parent, bar = "bar")# env does not own `foo` but sees it in its parent environment:env_has(env, "foo")env_has(env, "foo", inherit = TRUE)parent<- child_env(NULL, foo="foo")env<- child_env(parent, bar="bar")# env does not own `foo` but sees it in its parent environment:env_has(env,"foo")env_has(env,"foo", inherit=TRUE)
This returnsTRUE ifx hasancestor among its parents.
env_inherits(env, ancestor)env_inherits(env, ancestor)
env | An environment. |
ancestor | Another environment from which |
Detects ifenv is user-facing, that is, whether it's an environmentthat inherits from:
The global environment, as would happen when called interactively
A package that is currently being tested
If either is true, we considerenv to belong to an evaluationframe that was calleddirectly by the end user. This is bycontrast toindirect calls by third party functions which are notuser facing.
For instance thelifecycle packageusesenv_is_user_facing() to figure out whether a deprecated functionwas called directly or indirectly, and select an appropriateverbosity level as a function of that.
env_is_user_facing(env)env_is_user_facing(env)
env | An environment. |
You can override the return value ofenv_is_user_facing() bysetting the global option"rlang_user_facing" to:
TRUE orFALSE.
A package name as a string. Thenenv_is_user_facing(x) returnsTRUE ifx inherits from the namespace corresponding to thatpackage name.
fn <- function() { env_is_user_facing(caller_env())}# Direct call of `fn()` from the global envwith(global_env(), fn())# Indirect call of `fn()` from a packagewith(ns_env("utils"), fn())fn<-function(){ env_is_user_facing(caller_env())}# Direct call of `fn()` from the global envwith(global_env(), fn())# Indirect call of `fn()` from a packagewith(ns_env("utils"), fn())
Special environments like the global environment have their ownnames.env_name() returns:
"global" for the global environment.
"empty" for the empty environment.
"base" for the base package environment (the last environment onthe search path).
"namespace:pkg" ifenv is the namespace of the package "pkg".
Thename attribute ofenv if it exists. This is how thepackage environments and theimports environments store their names. The name of packageenvironments is typically "package:pkg".
The empty string"" otherwise.
env_label() is exactly likeenv_name() but returns the memoryaddress of anonymous environments as fallback.
env_name(env)env_label(env)env_name(env)env_label(env)
env | An environment. |
# Some environments have specific names:env_name(global_env())env_name(ns_env("rlang"))# Anonymous environments don't have names but are labelled by their# address in memory:env_name(env())env_label(env())# Some environments have specific names:env_name(global_env())env_name(ns_env("rlang"))# Anonymous environments don't have names but are labelled by their# address in memory:env_name(env())env_label(env())
env_names() returns object names from an enviromentenv as acharacter vector. All names are returned, even those starting witha dot.env_length() returns the number of bindings.
env_names(env)env_length(env)env_names(env)env_length(env)
env | An environment. |
A character vector of object names.
Technically, objects are bound to symbols rather than strings,since the R interpreter evaluates symbols (seeis_expression() for adiscussion of symbolic objects versus literal objects). However itis often more convenient to work with strings. In rlangterminology, the string corresponding to a symbol is called thename of the symbol (or by extension the name of an object boundto a symbol).
There are deep encoding issues when you convert a string to symboland vice versa. Symbols arealways in the native encoding. Ifthat encoding (let's say latin1) cannot support some characters,these characters are serialised to ASCII. That's why you sometimessee strings looking like<U+1234>, especially if you're runningWindows (as R doesn't support UTF-8 as native encoding on thatplatform).
To alleviate some of the encoding pain,env_names() alwaysreturns a UTF-8 character vector (which is fine even on Windows)with ASCII unicode points translated back to UTF-8.
env <- env(a = 1, b = 2)env_names(env)env<- env(a=1, b=2)env_names(env)
env_parent() returns the parent environment ofenv if calledwithn = 1, the grandparent withn = 2, etc.
env_tail() searches through the parents and returns the onewhich hasempty_env() as parent.
env_parents() returns the list of all parents, including theempty environment. This list is named usingenv_name().
See the section oninheritance inenv()'s documentation.
env_parent(env = caller_env(), n = 1)env_tail(env = caller_env(), last = global_env())env_parents(env = caller_env(), last = global_env())env_parent(env= caller_env(), n=1)env_tail(env= caller_env(), last= global_env())env_parents(env= caller_env(), last= global_env())
env | An environment. |
n | The number of generations to go up. |
last | The environment at which to stop. Defaults to theglobal environment. The empty environment is always a stoppingcondition so it is safe to leave the default even when taking thetail or the parents of an environment on the search path.
|
An environment forenv_parent() andenv_tail(), a listof environments forenv_parents().
# Get the parent environment with env_parent():env_parent(global_env())# Or the tail environment with env_tail():env_tail(global_env())# By default, env_parent() returns the parent environment of the# current evaluation frame. If called at top-level (the global# frame), the following two expressions are equivalent:env_parent()env_parent(base_env())# This default is more handy when called within a function. In this# case, the enclosure environment of the function is returned# (since it is the parent of the evaluation frame):enclos_env <- env()fn <- set_env(function() env_parent(), enclos_env)identical(enclos_env, fn())# Get the parent environment with env_parent():env_parent(global_env())# Or the tail environment with env_tail():env_tail(global_env())# By default, env_parent() returns the parent environment of the# current evaluation frame. If called at top-level (the global# frame), the following two expressions are equivalent:env_parent()env_parent(base_env())# This default is more handy when called within a function. In this# case, the enclosure environment of the function is returned# (since it is the parent of the evaluation frame):enclos_env<- env()fn<- set_env(function() env_parent(), enclos_env)identical(enclos_env, fn())
env_poke() will assign or reassign a binding inenv ifcreateisTRUE. Ifcreate isFALSE and a binding does not alreadyexists, an error is issued.
env_poke(env = caller_env(), nm, value, inherit = FALSE, create = !inherit)env_poke(env= caller_env(), nm, value, inherit=FALSE, create=!inherit)
env | An environment. |
nm | Name of binding, a string. |
value | The value for a new binding. |
inherit | Whether to look for bindings in the parentenvironments. |
create | Whether to create a binding if it does not alreadyexist in the environment. |
Ifinherit isTRUE, the parents environments are checked foran existing binding to reassign. If not found andcreate isTRUE, a new binding is created inenv. The default value forcreate is a function ofinherit:FALSE when inheriting,TRUE otherwise.
This default makes sense because the inheriting case is mostlyfor overriding an existing binding. If not found, somethingprobably went wrong and it is safer to issue an error. Note thatthis is different to the base R operator<<- which will createa binding in the global environment instead of the currentenvironment when no existing binding is found in the parents.
The old value ofnm or azap sentinel if thebinding did not exist yet.
env_bind() for binding multiple elements.env_cache()for a variant ofenv_poke() designed to cache values.
This prints:
Thelabel and the parent label.
Whether the environment islocked.
The bindings in the environment (up to 20 bindings). They areprinted succinctly usingpillar::type_sum() (if available,otherwise uses an internal version of that generic). In additionfancy bindings (actives and promises) areindicated as such.
Locked bindings get a[L] tag
Note that printing a package namespace (seens_env()) withenv_print() will typically tag function bindings as<lazy>until they are evaluated the first time. This is because packagefunctions are lazily-loaded from disk to improve performance whenloading a package.
env_print(env = caller_env())env_print(env= caller_env())
env | An environment, or object that can be converted to anenvironment by |
env_unbind() is the complement ofenv_bind(). Likeenv_has(),it ignores the parent environments ofenv by default. Setinherit toTRUE to track down bindings in parent environments.
env_unbind(env = caller_env(), nms, inherit = FALSE)env_unbind(env= caller_env(), nms, inherit=FALSE)
env | An environment. |
nms | A character vector of binding names to remove. |
inherit | Whether to look for bindings in the parentenvironments. |
The input objectenv with its associated environmentmodified in place, invisibly.
env <- env(foo = 1, bar = 2)env_has(env, c("foo", "bar"))# Remove bindings with `env_unbind()`env_unbind(env, c("foo", "bar"))env_has(env, c("foo", "bar"))# With inherit = TRUE, it removes bindings in parent environments# as well:parent <- env(empty_env(), foo = 1, bar = 2)env <- env(parent, foo = "b")env_unbind(env, "foo", inherit = TRUE)env_has(env, c("foo", "bar"))env_has(env, c("foo", "bar"), inherit = TRUE)env<- env(foo=1, bar=2)env_has(env, c("foo","bar"))# Remove bindings with `env_unbind()`env_unbind(env, c("foo","bar"))env_has(env, c("foo","bar"))# With inherit = TRUE, it removes bindings in parent environments# as well:parent<- env(empty_env(), foo=1, bar=2)env<- env(parent, foo="b")env_unbind(env,"foo", inherit=TRUE)env_has(env, c("foo","bar"))env_has(env, c("foo","bar"), inherit=TRUE)
eval_bare() is a lower-level version of functionbase::eval().Technically, it is a simple wrapper around the C functionRf_eval(). You generally don't need to useeval_bare() insteadofeval(). Its main advantage is that it handles stack-sensitivecalls (such asreturn(),on.exit() orparent.frame()) moreconsistently when you pass an enviroment of a frame on the callstack.
eval_bare(expr, env = parent.frame())eval_bare(expr, env= parent.frame())
expr | An expression to evaluate. |
env | The environment in which to evaluate the expression. |
These semantics are possible becauseeval_bare() creates only oneframe on the call stack whereaseval() creates two frames, thesecond of which has the user-supplied environment as frameenvironment. When you supply an existing frame environment tobase::eval() there will be two frames on the stack with the sameframe environment. Stack-sensitive functions only detect thetopmost of these frames. We call these evaluation semantics"stack inconsistent".
Evaluating expressions in the actual frame environment has usefulpractical implications foreval_bare():
return() calls are evaluated in frame environments that mightbe buried deep in the call stack. This causes a long return thatunwinds multiple frames (triggering theon.exit() event foreach frame). By contrasteval() only returns from theeval()call, one level up.
on.exit(),parent.frame(),sys.call(), and generally allthe stack inspection functionssys.xxx() are evaluated in thecorrect frame environment. This is similar to how this type ofcalls can be evaluated deep in the call stack because of lazyevaluation, when you force an argument that has been passedaround several times.
The flip side of the semantics ofeval_bare() is that it can'tevaluatebreak ornext expressions even if called within aloop.
eval_tidy() for evaluation with data mask and quosuresupport.
# eval_bare() works just like base::eval() but you have to create# the evaluation environment yourself:eval_bare(quote(foo), env(foo = "bar"))# eval() has different evaluation semantics than eval_bare(). It# can return from the supplied environment even if its an# environment that is not on the call stack (i.e. because you've# created it yourself). The following would trigger an error with# eval_bare():ret <- quote(return("foo"))eval(ret, env())# eval_bare(ret, env()) # "no function to return from" error# Another feature of eval() is that you can control surround loops:bail <- quote(break)while (TRUE) { eval(bail) # eval_bare(bail) # "no loop for break/next" error}# To explore the consequences of stack inconsistent semantics, let's# create a function that evaluates `parent.frame()` deep in the call# stack, in an environment corresponding to a frame in the middle of# the stack. For consistency with R's lazy evaluation semantics, we'd# expect to get the caller of that frame as result:fn <- function(eval_fn) { list( returned_env = middle(eval_fn), actual_env = current_env() )}middle <- function(eval_fn) { deep(eval_fn, current_env())}deep <- function(eval_fn, eval_env) { expr <- quote(parent.frame()) eval_fn(expr, eval_env)}# With eval_bare(), we do get the expected environment:fn(rlang::eval_bare)# But that's not the case with base::eval():fn(base::eval)# eval_bare() works just like base::eval() but you have to create# the evaluation environment yourself:eval_bare(quote(foo), env(foo="bar"))# eval() has different evaluation semantics than eval_bare(). It# can return from the supplied environment even if its an# environment that is not on the call stack (i.e. because you've# created it yourself). The following would trigger an error with# eval_bare():ret<- quote(return("foo"))eval(ret, env())# eval_bare(ret, env()) # "no function to return from" error# Another feature of eval() is that you can control surround loops:bail<- quote(break)while(TRUE){ eval(bail)# eval_bare(bail) # "no loop for break/next" error}# To explore the consequences of stack inconsistent semantics, let's# create a function that evaluates `parent.frame()` deep in the call# stack, in an environment corresponding to a frame in the middle of# the stack. For consistency with R's lazy evaluation semantics, we'd# expect to get the caller of that frame as result:fn<-function(eval_fn){ list( returned_env= middle(eval_fn), actual_env= current_env())}middle<-function(eval_fn){ deep(eval_fn, current_env())}deep<-function(eval_fn, eval_env){ expr<- quote(parent.frame()) eval_fn(expr, eval_env)}# With eval_bare(), we do get the expected environment:fn(rlang::eval_bare)# But that's not the case with base::eval():fn(base::eval)
eval_tidy() is a variant ofbase::eval() that powers the tidyevaluation framework. Likeeval() it accepts user data asargument. Whereaseval() simply transforms the data to anenvironment,eval_tidy() transforms it to adata mask withas_data_mask(). Evaluating in a datamask enables the following features:
Quosures. Quosures are expressions bundled withan environment. Ifdata is supplied, objects in the data maskalways have precedence over the quosure environment, i.e. thedata masks the environment.
Pronouns. Ifdata is supplied, the.env and.datapronouns are installed in the data mask..env is a reference tothe calling environment and.data refers to thedataargument. These pronouns are an escape hatch for thedata mask ambiguity problem.
eval_tidy(expr, data = NULL, env = caller_env())eval_tidy(expr, data=NULL, env= caller_env())
expr | Anexpression orquosure to evaluate. |
data | A data frame, or named list or vector. Alternatively, adata mask created with |
env | The environment in which to evaluate |
base::eval() is sufficient for simple evaluation. Useeval_tidy() when you'd like to support expressions referring tothe.data pronoun, or when you need to support quosures.
If you're evaluating an expression captured withinjection support, it is recommended to useeval_tidy() because users may inject quosures.
Note that unwrapping a quosure withquo_get_expr() does notguarantee that there is no quosures inside the expression. Quosuresmight be unquoted anywhere in the expression tree. For instance,the following does not work reliably in the presence of nestedquosures:
my_quoting_fn <- function(x) { x <- enquo(x) expr <- quo_get_expr(x) env <- quo_get_env(x) eval(expr, env)}# Works:my_quoting_fn(toupper(letters))# Fails because of a nested quosure:my_quoting_fn(toupper(!!quo(letters)))eval_tidy()eval_tidy() always evaluates in a data mask, even whendata isNULL. Because of this, it has different stack semantics thanbase::eval():
Lexical side effects, such as assignment with<-, occur in themask rather thanenv.
Functions that require the evaluation environment to correspondto a frame on the call stack do not work. This is whyreturn()called from a quosure does not work.
The mask environment creates a new branch in the treerepresentation of backtraces (which you can visualise in abrowser() session withlobstr::cst()).
See alsoeval_bare() for more information about these differences.
new_data_mask() andas_data_mask() for manually creating data masks.
# With simple defused expressions eval_tidy() works the same way as# eval():fruit <- "apple"vegetable <- "potato"expr <- quote(paste(fruit, vegetable, sep = " or "))expreval(expr)eval_tidy(expr)# Both accept a data mask as argument:data <- list(fruit = "banana", vegetable = "carrot")eval(expr, data)eval_tidy(expr, data)# The main difference is that eval_tidy() supports quosures:with_data <- function(data, expr) { quo <- enquo(expr) eval_tidy(quo, data)}with_data(NULL, fruit)with_data(data, fruit)# eval_tidy() installs the `.data` and `.env` pronouns to allow# users to be explicit about variable references:with_data(data, .data$fruit)with_data(data, .env$fruit)# With simple defused expressions eval_tidy() works the same way as# eval():fruit<-"apple"vegetable<-"potato"expr<- quote(paste(fruit, vegetable, sep=" or "))expreval(expr)eval_tidy(expr)# Both accept a data mask as argument:data<- list(fruit="banana", vegetable="carrot")eval(expr, data)eval_tidy(expr, data)# The main difference is that eval_tidy() supports quosures:with_data<-function(data, expr){ quo<- enquo(expr) eval_tidy(quo, data)}with_data(NULL, fruit)with_data(data, fruit)# eval_tidy() installs the `.data` and `.env` pronouns to allow# users to be explicit about variable references:with_data(data, .data$fruit)with_data(data, .env$fruit)
This function constructs and evaluates a call to.fn.It has two primary uses:
To call a function with arguments stored in a list (if thefunction doesn't supportdynamic dots). Splice thelist of arguments with!!!.
To call every function stored in a list (in conjunction withmap()/lapply())
exec(.fn, ..., .env = caller_env())exec(.fn,..., .env= caller_env())
.fn | A function, or function name as a string. |
... | <dynamic> Arguments for |
.env | Environment in which to evaluate the call. This will bemost useful if |
args <- list(x = c(1:10, 100, NA), na.rm = TRUE)exec("mean", !!!args)exec("mean", !!!args, trim = 0.2)fs <- list(a = function() "a", b = function() "b")lapply(fs, exec)# Compare to do.call it will not automatically inline expressions# into the evaluated call.x <- 10args <- exprs(x1 = x + 1, x2 = x * 2)exec(list, !!!args)do.call(list, args)# exec() is not designed to generate pretty function calls. This is# most easily seen if you call a function that captures the call:f <- disp ~ cylexec("lm", f, data = mtcars)# If you need finer control over the generated call, you'll need to# construct it yourself. This may require creating a new environment# with carefully constructed bindingsdata_env <- env(data = mtcars)eval(expr(lm(!!f, data)), data_env)args<- list(x= c(1:10,100,NA), na.rm=TRUE)exec("mean",!!!args)exec("mean",!!!args, trim=0.2)fs<- list(a=function()"a", b=function()"b")lapply(fs, exec)# Compare to do.call it will not automatically inline expressions# into the evaluated call.x<-10args<- exprs(x1= x+1, x2= x*2)exec(list,!!!args)do.call(list, args)# exec() is not designed to generate pretty function calls. This is# most easily seen if you call a function that captures the call:f<- disp~ cylexec("lm", f, data= mtcars)# If you need finer control over the generated call, you'll need to# construct it yourself. This may require creating a new environment# with carefully constructed bindingsdata_env<- env(data= mtcars)eval(expr(lm(!!f, data)), data_env)
expr()defuses an R expression withinjection support.
It is equivalent tobase::bquote().
expr | An expression to defuse. |
Defusing R expressions for an overview.
enquo() to defuse non-local expressions from functionarguments.
sym() andcall2() for building expressions (symbols and callsrespectively) programmatically.
base::eval() andeval_bare() for resuming evaluationof a defused expression.
# R normally returns the result of an expression1 + 1# `expr()` defuses the expression that you have supplied and# returns it instead of its valueexpr(1 + 1)expr(toupper(letters))# It supports _injection_ with `!!` and `!!!`. This is a convenient# way of modifying part of an expression by injecting other# objects.var <- "cyl"expr(with(mtcars, mean(!!sym(var))))vars <- c("cyl", "am")expr(with(mtcars, c(!!!syms(vars))))# Compare to the normal way of building expressionscall("with", call("mean", sym(var)))call("with", call2("c", !!!syms(vars)))# R normally returns the result of an expression1+1# `expr()` defuses the expression that you have supplied and# returns it instead of its valueexpr(1+1)expr(toupper(letters))# It supports _injection_ with `!!` and `!!!`. This is a convenient# way of modifying part of an expression by injecting other# objects.var<-"cyl"expr(with(mtcars, mean(!!sym(var))))vars<- c("cyl","am")expr(with(mtcars, c(!!!syms(vars))))# Compare to the normal way of building expressionscall("with", call("mean", sym(var)))call("with", call2("c",!!!syms(vars)))
expr_print(), powered byexpr_deparse(), is an alternativeprinter for R expressions with a few improvements over the base Rprinter.
It colourisesquosures according to their environment.Quosures from the global environment are printed normally whilequosures from local environments are printed in unique colour (orin italic when all colours are taken).
It wraps inlined objects in angular brackets. For instance, aninteger vector unquoted in a function call (e.g.expr(foo(!!(1:3)))) is printed like this:foo(<int: 1L, 2L, 3L>) while by default R prints the code to create that vector:foo(1:3) which is ambiguous.
It respects the width boundary (from the global optionwidth)in more cases.
expr_print(x, ...)expr_deparse(x, ..., width = peek_option("width"))expr_print(x,...)expr_deparse(x,..., width= peek_option("width"))
x | An object or expression to print. |
... | Arguments passed to |
width | The width of the deparsed or printed expression.Defaults to the global option |
expr_deparse() returns a character vector of lines.expr_print() returns its input invisibly.
# It supports any object. Non-symbolic objects are always printed# within angular brackets:expr_print(1:3)expr_print(function() NULL)# Contrast this to how the code to create these objects is printed:expr_print(quote(1:3))expr_print(quote(function() NULL))# The main cause of non-symbolic objects in expressions is# quasiquotation:expr_print(expr(foo(!!(1:3))))# Quosures from the global environment are printed normally:expr_print(quo(foo))expr_print(quo(foo(!!quo(bar))))# Quosures from local environments are colourised according to# their environments (if you have crayon installed):local_quo <- local(quo(foo))expr_print(local_quo)wrapper_quo <- local(quo(bar(!!local_quo, baz)))expr_print(wrapper_quo)# It supports any object. Non-symbolic objects are always printed# within angular brackets:expr_print(1:3)expr_print(function()NULL)# Contrast this to how the code to create these objects is printed:expr_print(quote(1:3))expr_print(quote(function()NULL))# The main cause of non-symbolic objects in expressions is# quasiquotation:expr_print(expr(foo(!!(1:3))))# Quosures from the global environment are printed normally:expr_print(quo(foo))expr_print(quo(foo(!!quo(bar))))# Quosures from local environments are colourised according to# their environments (if you have crayon installed):local_quo<- local(quo(foo))expr_print(local_quo)wrapper_quo<- local(quo(bar(!!local_quo, baz)))expr_print(wrapper_quo)
This gives default names to unnamed elements of a list ofexpressions (or expression wrappers such as formulas orquosures), deparsed withas_label().
exprs_auto_name( exprs, ..., repair_auto = c("minimal", "unique"), repair_quiet = FALSE)quos_auto_name(quos)exprs_auto_name( exprs,..., repair_auto= c("minimal","unique"), repair_quiet=FALSE)quos_auto_name(quos)
exprs | A list of expressions. |
... | These dots are for future extensions and must be empty. |
repair_auto | Whether to repair the automatic names. Bydefault, minimal names are returned. See |
repair_quiet | Whether to inform user about repaired names. |
quos | A list of quosures. |
f_rhs extracts the right-hand side,f_lhs extracts the left-handside, andf_env extracts the environment in which the formula was defined.All functions throw an error iff is not a formula.
f_rhs(f)f_rhs(x) <- valuef_lhs(f)f_lhs(x) <- valuef_env(f)f_env(x) <- valuef_rhs(f)f_rhs(x)<- valuef_lhs(f)f_lhs(x)<- valuef_env(f)f_env(x)<- value
f,x | A formula |
value | The value to replace with. |
f_rhs andf_lhs return language objects (i.e. atomicvectors of length 1, a name, or a call).f_env returns anenvironment.
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 <- as.formula("y ~ x", env = new.env())f_env(f)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<- as.formula("y ~ x", env= new.env())f_env(f)
Equivalent ofexpr_text() andexpr_label() for formulas.
f_text(x, width = 60L, nlines = Inf)f_name(x)f_label(x)f_text(x, width=60L, nlines=Inf)f_name(x)f_label(x)
x | A formula. |
width | Width of each line. |
nlines | Maximum number of lines to extract. |
f <- ~ a + b + bcf_text(f)f_label(f)# Names a quoted with ``f_label(~ x)# Strings are encodedf_label(~ "a\nb")# Long expressions are collapsedf_label(~ foo({ 1 + 2 print(x)}))f<-~ a+ b+ bcf_text(f)f_label(f)# Names a quoted with ``f_label(~ x)# Strings are encodedf_label(~"a\nb")# Long expressions are collapsedf_label(~ foo({1+2 print(x)}))
rlang has several options which may be set globally to controlbehavior. A brief description of each is given here. If any functionsare referenced, refer to their documentation for additional details.
rlang_interactive: A logical value used byis_interactive(). Thiscan be set toTRUE to test interactive behavior in unit tests,for example.
rlang_backtrace_on_error: A character string which controls whetherbacktraces are displayed with error messages, and the level ofdetail they print. Seerlang_backtrace_on_error for the possible option values.
rlang_trace_format_srcrefs: A logical value used to control whethersrcrefs are printed as part of the backtrace.
rlang_trace_top_env: An environment which will be treated as thetop-level environment when printing traces. Seetrace_back()for examples.
fn_body() is a simple wrapper aroundbase::body(). It alwaysreturns a\{ expression and throws an error when the input is aprimitive function (whereasbody() returnsNULL). The setterversion preserves attributes, unlikebody<-.
fn_body(fn = caller_fn())fn_body(fn) <- valuefn_body(fn= caller_fn())fn_body(fn)<- value
fn | A function. It is looked up in the calling frame if notsupplied. |
value | New formals or formals names for |
# 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() 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))
Closure environments define the scope of functions (seeenv()).When a function call is evaluated, R creates an evaluation framethat inherits from the closure environment. This makes all objectsdefined in the closure environment and all its parents available tocode executed within the function.
fn_env(fn)fn_env(x) <- valuefn_env(fn)fn_env(x)<- value
fn,x | A function. |
value | A new closure environment for the function. |
fn_env() returns the closure environment offn. There is alsoan assignment method to set a new closure environment.
env <- child_env("base")fn <- with_env(env, function() NULL)identical(fn_env(fn), env)other_env <- child_env("base")fn_env(fn) <- other_envidentical(fn_env(fn), other_env)env<- child_env("base")fn<- with_env(env,function()NULL)identical(fn_env(fn), env)other_env<- child_env("base")fn_env(fn)<- other_envidentical(fn_env(fn), other_env)
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. Thisis especially useful for forwarding arguments inconstructed calls.
fn_fmls(fn = caller_fn())fn_fmls_names(fn = caller_fn())fn_fmls_syms(fn = caller_fn())fn_fmls(fn) <- valuefn_fmls_names(fn) <- valuefn_fmls(fn= caller_fn())fn_fmls_names(fn= caller_fn())fn_fmls_syms(fn= caller_fn())fn_fmls(fn)<- valuefn_fmls_names(fn)<- value
fn | A function. It is looked up in the calling frame if notsupplied. |
value | New formals or formals names for |
Unlikeformals(), these helpers throw an error with primitivefunctions instead of returningNULL.
call_args() andcall_args_names()
# 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()# 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()
format_error_bullets() takes a character vector and returns a singlestring (or an empty vector if the input is empty). The elements ofthe input vector are assembled as a list of bullets, depending ontheir names:
Unnamed elements are unindented. They act as titles or subtitles.
Elements named"*" are bulleted with a cyan "bullet" symbol.
Elements named"i" are bulleted with a blue "info" symbol.
Elements named"x" are bulleted with a red "cross" symbol.
Elements named"v" are bulleted with a green "tick" symbol.
Elements named"!" are bulleted with a yellow "warning" symbol.
Elements named">" are bulleted with an "arrow" symbol.
Elements named" " start with an indented line break.
For convenience, if the vector is fully unnamed, the elements areformatted as "*" bullets.
The bullet formatting for errors follows the idea that sentences inerror messages are best kept short and simple. The best way topresent the information is in thecnd_body() method of an errorcondition as a bullet list of simple sentences containing a singleclause. The info and cross symbols of the bullets provide hints onhow to interpret the bullet relative to the general error issue,which should be supplied ascnd_header().
format_error_bullets(x)format_error_bullets(x)
x | A named character vector of messages. Named elements areprefixed with the corresponding bullet. Elements named with asingle space |
# All bulletswriteLines(format_error_bullets(c("foo", "bar")))# This is equivalent towriteLines(format_error_bullets(set_names(c("foo", "bar"), "*")))# Supply named elements to format info, cross, and tick bulletswriteLines(format_error_bullets(c(i = "foo", x = "bar", v = "baz", "*" = "quux")))# An unnamed element breaks the linewriteLines(format_error_bullets(c(i = "foo\nbar")))# A " " element breaks the line within a bullet (with indentation)writeLines(format_error_bullets(c(i = "foo", " " = "bar")))# All bulletswriteLines(format_error_bullets(c("foo","bar")))# This is equivalent towriteLines(format_error_bullets(set_names(c("foo","bar"),"*")))# Supply named elements to format info, cross, and tick bulletswriteLines(format_error_bullets(c(i="foo", x="bar", v="baz","*"="quux")))# An unnamed element breaks the linewriteLines(format_error_bullets(c(i="foo\nbar")))# A " " element breaks the line within a bullet (with indentation)writeLines(format_error_bullets(c(i="foo"," "="bar")))
These functions dispatch internally with methods for functions,formulas and frames. If called with a missing argument, theenvironment of the current evaluation frame is returned. If youcallget_env() with an environment, it acts as the identityfunction and the environment is simply returned (this helpssimplifying code when writing generic functions for environments).
get_env(env, default = NULL)set_env(env, new_env = caller_env())env_poke_parent(env, new_env)get_env(env, default=NULL)set_env(env, new_env= caller_env())env_poke_parent(env, new_env)
env | An environment. |
default | The default environment in case |
new_env | An environment to replace |
Whileset_env() returns a modified copy and does not have sideeffects,env_poke_parent() operates changes the environment byside effect. This is because environments areuncopyable. Be careful not to change environmentsthat you don't own, e.g. a parent environment of a function from apackage.
quo_get_env() andquo_set_env() for versions ofget_env() andset_env() that only work on quosures.
# Environment of closure functions:fn <- function() "foo"get_env(fn)# Or of quosures or formulas:get_env(~foo)get_env(quo(foo))# Provide a default in case the object doesn't bundle an environment.# Let's create an unevaluated formula:f <- quote(~foo)# The following line would fail if run because unevaluated formulas# don't bundle an environment (they didn't have the chance to# record one yet):# get_env(f)# It is often useful to provide a default when you're writing# functions accepting formulas as input:default <- env()identical(get_env(f, default), default)# set_env() can be used to set the enclosure of functions and# formulas. Let's create a function with a particular environment:env <- child_env("base")fn <- set_env(function() NULL, env)# That function now has `env` as enclosure:identical(get_env(fn), env)identical(get_env(fn), current_env())# set_env() does not work by side effect. Setting a new environment# for fn has no effect on the original function:other_env <- child_env(NULL)set_env(fn, other_env)identical(get_env(fn), other_env)# Since set_env() returns a new function with a different# environment, you'll need to reassign the result:fn <- set_env(fn, other_env)identical(get_env(fn), other_env)# Environment of closure functions:fn<-function()"foo"get_env(fn)# Or of quosures or formulas:get_env(~foo)get_env(quo(foo))# Provide a default in case the object doesn't bundle an environment.# Let's create an unevaluated formula:f<- quote(~foo)# The following line would fail if run because unevaluated formulas# don't bundle an environment (they didn't have the chance to# record one yet):# get_env(f)# It is often useful to provide a default when you're writing# functions accepting formulas as input:default<- env()identical(get_env(f, default), default)# set_env() can be used to set the enclosure of functions and# formulas. Let's create a function with a particular environment:env<- child_env("base")fn<- set_env(function()NULL, env)# That function now has `env` as enclosure:identical(get_env(fn), env)identical(get_env(fn), current_env())# set_env() does not work by side effect. Setting a new environment# for fn has no effect on the original function:other_env<- child_env(NULL)set_env(fn, other_env)identical(get_env(fn), other_env)# Since set_env() returns a new function with a different# environment, you'll need to reassign the result:fn<- set_env(fn, other_env)identical(get_env(fn), other_env)
global_entrace() enriches base errors, warnings, and messageswith rlang features.
They are assigned a backtrace. You can configure whether todisplay a backtrace on error with therlang_backtrace_on_errorglobal option.
They are recorded inlast_error(),last_warnings(), orlast_messages(). You can inspect backtraces at any time bycalling these functions.
Set global entracing in your RProfile with:
rlang::global_entrace()
global_entrace(enable = TRUE, class = c("error", "warning", "message"))global_entrace(enable=TRUE, class= c("error","warning","message"))
enable | Whether to enable or disable global handling. |
class | A character vector of one or several classes ofconditions to be entraced. |
Callglobal_entrace() inside an RMarkdown document to causeerrors and warnings to be promoted to rlang conditions that includea backtrace. This needs to be done in a separate setup chunk beforethe first error or warning.
This is useful in conjunction withrlang_backtrace_on_error_report andrlang_backtrace_on_warning_report. To get full entracing in anRmd document, include this in a setup chunk before the first erroror warning is signalled.
```{r setup}rlang::global_entrace()options(rlang_backtrace_on_warning_report = "full")options(rlang_backtrace_on_error_report = "full")```On R 4.0 and newer,global_entrace() installs a global handlerwithglobalCallingHandlers(). On older R versions,entrace() isset as anoption(error = ) handler. The latter method has thedisadvantage that only one handler can be set at a time. This meansthat you need to manually switch betweenentrace() and otherhandlers likerecover(). Also this causes a conflict with IDEhandlers (e.g. in RStudio).
global_handle() sets up a default configuration for error,warning, and message handling. It calls:
global_entrace() to enable rlang errors and warnings globally.
global_prompt_install() to recover frompackageNotFoundErrorswith a user prompt to install the missing package. Note that atthe time of writing (R 4.1), there are only very limitedsituations where this handler works.
global_handle(entrace = TRUE, prompt_install = TRUE)global_handle(entrace=TRUE, prompt_install=TRUE)
entrace | Passed as |
prompt_install | Passed as |
When enabled,packageNotFoundError thrown byloadNamespace()cause a user prompt to install the missing package and continuewithout interrupting the current program.
This is similar to howcheck_installed() prompts users to installrequired packages. It uses the same install strategy, using pak ifavailable andinstall.packages() otherwise.
global_prompt_install(enable = TRUE)global_prompt_install(enable=TRUE)
enable | Whether to enable or disable global handling. |
"{" and"{{"Dynamic dots (anddata-masked dots which are dynamic by default) have built-in support for names interpolation with theglue package.
tibble::tibble(foo = 1)#> # A tibble: 1 x 1#> foo#> <dbl>#> 1 1foo <- "name"tibble::tibble("{foo}" := 1)#> # A tibble: 1 x 1#> name#> <dbl>#> 1 1Inside functions, embracing an argument with{{ inserts the expression supplied as argument in the string. This gives an indication on the variable or computation supplied as argument:
tib <- function(x) { tibble::tibble("var: {{ x }}" := x)}tib(1 + 1)#> # A tibble: 1 x 1#> `var: 1 + 1`#> <dbl>#> 1 2See alsoenglue() to string-embrace outside of dynamic dots.
g <- function(x) { englue("var: {{ x }}")}g(1 + 1)#> [1] "var: 1 + 1"Technically,"{{"defuses a function argument, callsas_label() on the expression supplied as argument, and inserts the result in the string.
"{" and"{{"Whileglue::glue() only supports"{", dynamic dots support both"{" and"{{". The double brace variant is similar to the embrace operator{{ available indata-masked arguments.
In the following example, the embrace operator is used in a glue string to name the result with a default name that represents the expression supplied as argument:
my_mean <- function(data, var) { data %>% dplyr::summarise("{{ var }}" := mean({{ var }}))}mtcars %>% my_mean(cyl)#> # A tibble: 1 x 1#> cyl#> <dbl>#> 1 6.19mtcars %>% my_mean(cyl * am)#> # A tibble: 1 x 1#> `cyl * am`#> <dbl>#> 1 2.06"{{" is only meant for inserting an expression supplied as argument to a function. The result of the expression is not inspected or used. To interpolate a string stored in a variable, use the regular glue operator"{" instead:
my_mean <- function(data, var, name = "mean") { data %>% dplyr::summarise("{name}" := mean({{ var }}))}mtcars %>% my_mean(cyl)#> # A tibble: 1 x 1#> mean#> <dbl>#> 1 6.19mtcars %>% my_mean(cyl, name = "cyl")#> # A tibble: 1 x 1#> cyl#> <dbl>#> 1 6.19Using the wrong operator causes unexpected results:
x <- "name"list2("{{ x }}" := 1)#> $`"name"`#> [1] 1list2("{x}" := 1)#> $name#> [1] 1Ideally, using{{ on regular objects would be an error. However for technical reasons it is not possible to make a distinction between function arguments and ordinary variables. SeeDoes {{ work on regular objects? for more information about this limitation.
The implementation ofmy_mean() in the previous section forces a default name onto the result. But what if the caller wants to give it a different name? In functions that take dots, it is possible to just supply a named expression to override the default. In a function likemy_mean() that takes a named argument we need a different approach.
This is whereenglue() becomes useful. We can pull out the default name creation in another user-facing argument like this:
my_mean <- function(data, var, name = englue("{{ var }}")) { data %>% dplyr::summarise("{name}" := mean({{ var }}))}Now the user may supply their own name if needed:
mtcars %>% my_mean(cyl * am)#> # A tibble: 1 x 1#> `cyl * am`#> <dbl>#> 1 2.06mtcars %>% my_mean(cyl * am, name = "mean_cyl_am")#> # A tibble: 1 x 1#> mean_cyl_am#> <dbl>#> 1 2.06
:=?Name injection in dynamic dots was originally implemented with:= instead of= to allow complex expressions on the LHS:
x <- "name"list2(!!x := 1)#> $name#> [1] 1
Name-injection with glue operations was an extension of this existing feature and so inherited the same interface. However, there is no technical barrier to using glue strings on the LHS of=.
Since rlang does not depend directly on glue, you will have to ensure that glue is installed by adding it to yourImports: section.
usethis::use_package("glue", "Imports")This function returns a logical value that indicates if a dataframe or another named object contains an element with a specificname. Note thathas_name() only works with vectors. For instance,environments need the specialised functionenv_has().
has_name(x, name)has_name(x, name)
x | A data frame or another named object |
name | Element name(s) to check |
Unnamed objects are treated as if all names are empty strings.NAinput givesFALSE as output.
A logical vector of the same length asname
has_name(iris, "Species")has_name(mtcars, "gears")has_name(iris,"Species")has_name(mtcars,"gears")
hash() hashes an arbitrary R object.
hash_file() hashes the data contained in a file.
The generated hash is guaranteed to be reproducible across platforms thathave the same endianness and are using the same R version.
hash(x)hash_file(path)hash(x)hash_file(path)
x | An object. |
path | A character vector of paths to the files to be hashed. |
These hashers use the XXH128 hash algorithm of the xxHash library, whichgenerates a 128-bit hash. Both are implemented as streaming hashes, whichgenerate the hash with minimal extra memory usage.
Forhash(), objects are converted to binary using R's native serializationtools. Serialization version 3 is used. Seeserialize() for moreinformation about the serialization version.
Forhash(), a single character string containing the hash.
Forhash_file(), a character vector containing one hash per file.
hash(c(1, 2, 3))hash(mtcars)authors <- file.path(R.home("doc"), "AUTHORS")copying <- file.path(R.home("doc"), "COPYING")hashes <- hash_file(c(authors, copying))hashes# If you need a single hash for multiple files,# hash the result of `hash_file()`hash(hashes)hash(c(1,2,3))hash(mtcars)authors<- file.path(R.home("doc"),"AUTHORS")copying<- file.path(R.home("doc"),"COPYING")hashes<- hash_file(c(authors, copying))hashes# If you need a single hash for multiple files,# hash the result of `hash_file()`hash(hashes)
inherits_any() is likebase::inherits() but is more explicitabout its behaviour with multiple classes. Ifclasses containsseveral elements and the object inherits from at least one ofthem,inherits_any() returnsTRUE.
inherits_all() tests that an object inherits from all of theclasses in the supplied order. This is usually the best way totest for inheritance of multiple classes.
inherits_only() tests that the class vectors are identical. Itis a shortcut foridentical(class(x), class).
inherits_any(x, class)inherits_all(x, class)inherits_only(x, class)inherits_any(x, class)inherits_all(x, class)inherits_only(x, class)
x | An object to test for inheritance. |
class | A character vector of classes. |
obj <- structure(list(), class = c("foo", "bar", "baz"))# With the _any variant only one class must match:inherits_any(obj, c("foobar", "bazbaz"))inherits_any(obj, c("foo", "bazbaz"))# With the _all variant all classes must match:inherits_all(obj, c("foo", "bazbaz"))inherits_all(obj, c("foo", "baz"))# The order of classes must match as well:inherits_all(obj, c("baz", "foo"))# inherits_only() checks that the class vectors are identical:inherits_only(obj, c("foo", "baz"))inherits_only(obj, c("foo", "bar", "baz"))obj<- structure(list(), class= c("foo","bar","baz"))# With the _any variant only one class must match:inherits_any(obj, c("foobar","bazbaz"))inherits_any(obj, c("foo","bazbaz"))# With the _all variant all classes must match:inherits_all(obj, c("foo","bazbaz"))inherits_all(obj, c("foo","baz"))# The order of classes must match as well:inherits_all(obj, c("baz","foo"))# inherits_only() checks that the class vectors are identical:inherits_only(obj, c("foo","baz"))inherits_only(obj, c("foo","bar","baz"))
inject() evaluates an expression withinjectionsupport. There are three main usages:
Splicing lists of arguments in a function call.
Inline objects or other expressions in an expression with!!and!!!. For instance to create functions or formulasprogrammatically.
Pass arguments to NSE functions thatdefuse theirarguments without injection support (see for instanceenquo0()). You can use{{ arg }} with functions documentedto support quosures. Otherwise, use!!enexpr(arg).
inject(expr, env = caller_env())inject(expr, env= caller_env())
expr | An argument to evaluate. This argument is immediatelyevaluated in |
env | The environment in which to evaluate |
# inject() simply evaluates its argument with injection# support. These expressions are equivalent:2 * 3inject(2 * 3)inject(!!2 * !!3)# Injection with `!!` can be useful to insert objects or# expressions within other expressions, like formulas:lhs <- sym("foo")rhs <- sym("bar")inject(!!lhs ~ !!rhs + 10)# Injection with `!!!` splices lists of arguments in function# calls:args <- list(na.rm = TRUE, finite = 0.2)inject(mean(1:10, !!!args))# inject() simply evaluates its argument with injection# support. These expressions are equivalent:2*3inject(2*3)inject(!!2*!!3)# Injection with `!!` can be useful to insert objects or# expressions within other expressions, like formulas:lhs<- sym("foo")rhs<- sym("bar")inject(!!lhs~!!rhs+10)# Injection with `!!!` splices lists of arguments in function# calls:args<- list(na.rm=TRUE, finite=0.2)inject(mean(1:10,!!!args))
!!Theinjection operator!! injects a value orexpression inside another expression. In other words, it modifies apiece of code before R evaluates it.
There are two main cases for injection. You can inject constantvalues to work around issues ofscoping ambiguity, and you can injectdefused expressions likesymbolised column names.
!! work?!! does not work everywhere, you can only use it within certainspecial functions:
Functions takingdefused anddata-masked arguments.
Technically, this means function arguments defused with{{ oren-prefixed operators likeenquo(),enexpr(), etc.
Insideinject().
All data-masking verbs in the tidyverse support injection operatorsout of the box. With base functions, you need to useinject() toenable!!. Using!! out of context may lead to incorrectresults, seeWhat happens if I use injection operators out of context?.
The examples below are built around the base functionwith().Since it's not a tidyverse function we will useinject() to enable!! usage.
Data-masking functions likewith() are handy because you canrefer to column names in your computations. This comes at the priceof data mask ambiguity: if you have defined an env-variable of thesame name as a data-variable, you get a name collisions. Thiscollision is always resolved by giving precedence to thedata-variable (it masks the env-variable):
cyl <- c(100, 110)with(mtcars, mean(cyl))#> [1] 6.1875
The injection operator offers one way of solving this. Use it toinject the env-variable inside the data-masked expression:
inject( with(mtcars, mean(!!cyl)))#> [1] 105
Note that the.env pronoun is a simpler way of solving theambiguity. SeeThe data mask ambiguity for more aboutthis.
Injection is also useful for modifying parts of adefused expression. In the following example we use thesymbolise-and-inject pattern toinject a column name inside a data-masked expression.
var <- sym("cyl")inject( with(mtcars, mean(!!var)))#> [1] 6.1875Sincewith() is a base function, you can't injectquosures, only naked symbols and calls. Thisisn't a problem here because we're injecting the name of a dataframe column. If the environment is important, try injecting apre-computed value instead.
!!?With tidyverse APIs, injecting expressions with!! is no longer acommon pattern. First, the.env pronoun solves theambiguity problem in a more intuitive way:
cyl <- 100mtcars %>% dplyr::mutate(cyl = cyl * .env$cyl)
Second, the embrace operator{{ makes thedefuse-and-inject pattern easier tolearn and use.
my_mean <- function(data, var) { data %>% dplyr::summarise(mean({{ var }}))}# Equivalent tomy_mean <- function(data, var) { data %>% dplyr::summarise(mean(!!enquo(var)))}!! is a good tool to learn for advanced applications but ourhope is that it isn't needed for common data analysis cases.
This function tests ifx is acall. This is apattern-matching predicate that returnsFALSE ifname andnare supplied and the call does not match these properties.
is_call(x, name = NULL, n = NULL, ns = NULL)is_call(x, name=NULL, n=NULL, ns=NULL)
x | An object to test. Formulas and quosures are treatedliterally. |
name | An optional name that the call should match. It ispassed to |
n | An optional number of arguments that the call shouldmatch. |
ns | The namespace of the call. If Can be a character vector of namespaces, in which case the callhas to match at least one of them, otherwise |
is_call(quote(foo(bar)))# You can pattern-match the call with additional arguments:is_call(quote(foo(bar)), "foo")is_call(quote(foo(bar)), "bar")is_call(quote(foo(bar)), quote(foo))# Match the number of arguments with is_call():is_call(quote(foo(bar)), "foo", 1)is_call(quote(foo(bar)), "foo", 2)# By default, namespaced calls are tested unqualified:ns_expr <- quote(base::list())is_call(ns_expr, "list")# You can also specify whether the call shouldn't be namespaced by# supplying an empty string:is_call(ns_expr, "list", ns = "")# Or if it should have a namespace:is_call(ns_expr, "list", ns = "utils")is_call(ns_expr, "list", ns = "base")# You can supply multiple namespaces:is_call(ns_expr, "list", ns = c("utils", "base"))is_call(ns_expr, "list", ns = c("utils", "stats"))# If one of them is "", unnamespaced calls will match as well:is_call(quote(list()), "list", ns = "base")is_call(quote(list()), "list", ns = c("base", ""))is_call(quote(base::list()), "list", ns = c("base", ""))# The name argument is vectorised so you can supply a list of names# to match with:is_call(quote(foo(bar)), c("bar", "baz"))is_call(quote(foo(bar)), c("bar", "foo"))is_call(quote(base::list), c("::", ":::", "$", "@"))is_call(quote(foo(bar)))# You can pattern-match the call with additional arguments:is_call(quote(foo(bar)),"foo")is_call(quote(foo(bar)),"bar")is_call(quote(foo(bar)), quote(foo))# Match the number of arguments with is_call():is_call(quote(foo(bar)),"foo",1)is_call(quote(foo(bar)),"foo",2)# By default, namespaced calls are tested unqualified:ns_expr<- quote(base::list())is_call(ns_expr,"list")# You can also specify whether the call shouldn't be namespaced by# supplying an empty string:is_call(ns_expr,"list", ns="")# Or if it should have a namespace:is_call(ns_expr,"list", ns="utils")is_call(ns_expr,"list", ns="base")# You can supply multiple namespaces:is_call(ns_expr,"list", ns= c("utils","base"))is_call(ns_expr,"list", ns= c("utils","stats"))# If one of them is "", unnamespaced calls will match as well:is_call(quote(list()),"list", ns="base")is_call(quote(list()),"list", ns= c("base",""))is_call(quote(base::list()),"list", ns= c("base",""))# The name argument is vectorised so you can supply a list of names# to match with:is_call(quote(foo(bar)), c("bar","baz"))is_call(quote(foo(bar)), c("bar","foo"))is_call(quote(base::list), c("::",":::","$","@"))
Is object an empty vector or NULL?
is_empty(x)is_empty(x)
x | object to test |
is_empty(NULL)is_empty(list())is_empty(list(NULL))is_empty(NULL)is_empty(list())is_empty(list(NULL))
is_bare_environment() tests whetherx is an environment without a s3 ors4 class.
is_environment(x)is_bare_environment(x)is_environment(x)is_bare_environment(x)
x | object to test |
In rlang, anexpression is the return type ofparse_expr(), theset of objects that can be obtained from parsing R code. Under thisdefinition expressions include numbers, strings,NULL, symbols,and function calls. These objects can be classified as:
Symbolic objects, i.e. symbols and function calls (for whichis_symbolic() returnsTRUE)
Syntactic literals, i.e. scalar atomic objects andNULL(testable withis_syntactic_literal())
is_expression() returnsTRUE if the input is either a symbolicobject or a syntactic literal. If a call, the elements of the callmust all be expressions as well. Unparsable calls are notconsidered expressions in this narrow definition.
Note that in base R, there existsexpression() vectors, a datatype similar to a list that supports special attributes created bythe parser called source references. This data type is notsupported in rlang.
is_expression(x)is_syntactic_literal(x)is_symbolic(x)is_expression(x)is_syntactic_literal(x)is_symbolic(x)
x | An object to test. |
is_symbolic() returnsTRUE for symbols and calls (objects withtypelanguage). Symbolic objects are replaced by their valueduring evaluation. Literals are the complement of symbolicobjects. They are their own value and return themselves duringevaluation.
is_syntactic_literal() is a predicate that returnsTRUE for thesubset of literals that are created by R when parsing text (seeparse_expr()): numbers, strings andNULL. Along with symbols,these literals are the terminating nodes in an AST.
Note that in the most general sense, a literal is any R object thatevaluates to itself and that can be evaluated in the emptyenvironment. For instance,quote(c(1, 2)) is not a literal, it isa call. However, the result of evaluating it inbase_env() is aliteral(in this case an atomic vector).
As the data structure for function arguments, pairlists are also akind of language objects. However, since they are mostly aninternal data structure and can't be returned as is by the parser,is_expression() returnsFALSE for pairlists.
is_call() for a call predicate.
q1 <- quote(1)is_expression(q1)is_syntactic_literal(q1)q2 <- quote(x)is_expression(q2)is_symbol(q2)q3 <- quote(x + 1)is_expression(q3)is_call(q3)# Atomic expressions are the terminating nodes of a call tree:# NULL or a scalar atomic vector:is_syntactic_literal("string")is_syntactic_literal(NULL)is_syntactic_literal(letters)is_syntactic_literal(quote(call()))# Parsable literals have the property of being self-quoting:identical("foo", quote("foo"))identical(1L, quote(1L))identical(NULL, quote(NULL))# Like any literals, they can be evaluated within the empty# environment:eval_bare(quote(1L), empty_env())# Whereas it would fail for symbolic expressions:# eval_bare(quote(c(1L, 2L)), empty_env())# Pairlists are also language objects representing argument lists.# You will usually encounter them with extracted formals:fmls <- formals(is_expression)typeof(fmls)# Since they are mostly an internal data structure, is_expression()# returns FALSE for pairlists, so you will have to check explicitly# for them:is_expression(fmls)is_pairlist(fmls)q1<- quote(1)is_expression(q1)is_syntactic_literal(q1)q2<- quote(x)is_expression(q2)is_symbol(q2)q3<- quote(x+1)is_expression(q3)is_call(q3)# Atomic expressions are the terminating nodes of a call tree:# NULL or a scalar atomic vector:is_syntactic_literal("string")is_syntactic_literal(NULL)is_syntactic_literal(letters)is_syntactic_literal(quote(call()))# Parsable literals have the property of being self-quoting:identical("foo", quote("foo"))identical(1L, quote(1L))identical(NULL, quote(NULL))# Like any literals, they can be evaluated within the empty# environment:eval_bare(quote(1L), empty_env())# Whereas it would fail for symbolic expressions:# eval_bare(quote(c(1L, 2L)), empty_env())# Pairlists are also language objects representing argument lists.# You will usually encounter them with extracted formals:fmls<- formals(is_expression)typeof(fmls)# Since they are mostly an internal data structure, is_expression()# returns FALSE for pairlists, so you will have to check explicitly# for them:is_expression(fmls)is_pairlist(fmls)
is_formula() tests whetherx is a call to~.is_bare_formula()tests in addition thatx does not inherit from anything else than"formula".
Note: When we first implementedis_formula(), we thought itbest to treat unevaluated formulas as formulas by default (seesection below). Now we think this default introduces too many edgecases in normal code. We recommend always supplyingscoped = TRUE. Unevaluated formulas can be handled via ais_call(x, "~")branch.
is_formula(x, scoped = NULL, lhs = NULL)is_bare_formula(x, scoped = TRUE, lhs = NULL)is_formula(x, scoped=NULL, lhs=NULL)is_bare_formula(x, scoped=TRUE, lhs=NULL)
x | An object to test. |
scoped | A boolean indicating whether the quosure is scoped,that is, has a valid environment attribute and inherits from |
lhs | A boolean indicating whether the formula has a left-handside. If |
At parse time, a formula is a simple call to~ and it does nothave a class or an environment. Once evaluated, the~ callbecomes a properly structured formula. Unevaluated formulas ariseby quotation, e.g.~~foo,quote(~foo), orsubstitute(arg)witharg being supplied a formula. Use thescoped argument tocheck whether the formula carries an environment.
is_formula(~10)is_formula(10)# If you don't supply `lhs`, both one-sided and two-sided formulas# will return `TRUE`is_formula(disp ~ am)is_formula(~am)# You can also specify whether you expect a LHS:is_formula(disp ~ am, lhs = TRUE)is_formula(disp ~ am, lhs = FALSE)is_formula(~am, lhs = TRUE)is_formula(~am, lhs = FALSE)# Handling of unevaluated formulas is a bit tricky. These formulas# are special because they don't inherit from `"formula"` and they# don't carry an environment (they are not scoped):f <- quote(~foo)f_env(f)# By default unevaluated formulas are treated as formulasis_formula(f)# Supply `scoped = TRUE` to ensure you have an evaluated formulais_formula(f, scoped = TRUE)# By default unevaluated formulas not treated as bare formulasis_bare_formula(f)# If you supply `scoped = TRUE`, they will be considered bare# formulas even though they don't inherit from `"formula"`is_bare_formula(f, scoped = TRUE)is_formula(~10)is_formula(10)# If you don't supply `lhs`, both one-sided and two-sided formulas# will return `TRUE`is_formula(disp~ am)is_formula(~am)# You can also specify whether you expect a LHS:is_formula(disp~ am, lhs=TRUE)is_formula(disp~ am, lhs=FALSE)is_formula(~am, lhs=TRUE)is_formula(~am, lhs=FALSE)# Handling of unevaluated formulas is a bit tricky. These formulas# are special because they don't inherit from `"formula"` and they# don't carry an environment (they are not scoped):f<- quote(~foo)f_env(f)# By default unevaluated formulas are treated as formulasis_formula(f)# Supply `scoped = TRUE` to ensure you have an evaluated formulais_formula(f, scoped=TRUE)# By default unevaluated formulas not treated as bare formulasis_bare_formula(f)# If you supply `scoped = TRUE`, they will be considered bare# formulas even though they don't inherit from `"formula"`is_bare_formula(f, scoped=TRUE)
The R language defines two different types of functions: primitivefunctions, which are low-level, and closures, which are the regularkind of functions.
is_function(x)is_closure(x)is_primitive(x)is_primitive_eager(x)is_primitive_lazy(x)is_function(x)is_closure(x)is_primitive(x)is_primitive_eager(x)is_primitive_lazy(x)
x | Object to be tested. |
Closures are functions written in R, named after the way theirarguments are scoped within nested environments (seehttps://en.wikipedia.org/wiki/Closure_(computer_programming)). Theroot environment of the closure is called the closureenvironment. When closures are evaluated, a new environment calledthe evaluation frame is created with the closure environment asparent. This is where the body of the closure is evaluated. Theseclosure frames appear on the evaluation stack, as opposed toprimitive functions which do not necessarily have their ownevaluation frame and never appear on the stack.
Primitive functions are more efficient than closures for tworeasons. First, they are written entirely in fast low-levelcode. Second, the mechanism by which they are passed arguments ismore efficient because they often do not need the full procedure ofargument matching (dealing with positional versus named arguments,partial matching, etc). One practical consequence of the specialway in which primitives are passed arguments is that theytechnically do not have formal arguments, andformals() willreturnNULL if called on a primitive function. Finally, primitivefunctions 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()andis_primitive_lazy() allow you to check whether a primitivefunction evaluates arguments eagerly or lazily.
You will also encounter the distinction between primitive andinternal functions in technical documentation. Like primitivefunctions, internal functions are defined at a low level andwritten in C. However, internal functions have no representation inthe R language. Instead, they are called via a call tobase::.Internal() within a regular closure. This ensures thatthey appear as normal R function objects: they obey all the usualrules of argument passing, and they appear on the evaluation stackas any other closures. As a result,fn_fmls() does not need tolook in the.ArgsEnv environment to obtain a representation oftheir arguments, and there is no way of querying from R whetherthey are lazy ('special' in R terminology) or eager ('builtin').
You can call primitive functions with.Primitive() and internalfunctions with.Internal(). However, calling internal functionsin a package is forbidden by CRAN's policy because they areconsidered part of the private API. They often assume that theyhave been called with correctly formed arguments, and may cause Rto crash if you call them with unexpected objects.
# 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)# Many primitive functions evaluate arguments eagerly:is_primitive_eager(base::c)is_primitive_eager(base::list)is_primitive_eager(base::`+`)# However, primitives that operate on expressions, like quote() or# substitute(), are lazy:is_primitive_lazy(base::quote)is_primitive_lazy(base::substitute)# 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)# Many primitive functions evaluate arguments eagerly:is_primitive_eager(base::c)is_primitive_eager(base::list)is_primitive_eager(base::`+`)# However, primitives that operate on expressions, like quote() or# substitute(), are lazy:is_primitive_lazy(base::quote)is_primitive_lazy(base::substitute)
These functions check that packages are installed with minimal sideeffects. If installed, the packages will be loaded but notattached.
is_installed() doesn't interact with the user. It simplyreturnsTRUE orFALSE depending on whether the packages areinstalled.
In interactive sessions,check_installed() asks the userwhether to install missing packages. If the user accepts, thepackages are installed withpak::pkg_install() if available, orutils::install.packages() otherwise. If the session is noninteractive or if the user chooses not to install the packages,the current evaluation is aborted.
You can disable the prompt by setting therlib_restart_package_not_found global option toFALSE. In thatcase, missing packages always cause an error.
is_installed(pkg, ..., version = NULL, compare = NULL)check_installed( pkg, reason = NULL, ..., version = NULL, compare = NULL, action = NULL, call = caller_env())is_installed(pkg,..., version=NULL, compare=NULL)check_installed( pkg, reason=NULL,..., version=NULL, compare=NULL, action=NULL, call= caller_env())
pkg | The package names. Can include version requirements,e.g. |
... | These dots must be empty. |
version | Minimum versions for |
compare | A character vector of comparison operators to usefor |
reason | Optional string indicating why is |
action | An optional function taking |
call | The execution environment of a currentlyrunning function, e.g. |
is_installed() returnsTRUE ifall package namesprovided inpkg are installed,FALSEotherwise.check_installed() either doesn't return or returnsNULL.
check_installed() signals error conditions of classrlib_error_package_not_found. The error includespkg andversion fields. They are vectorised and may include severalpackages.
The error is signalled with arlib_restart_package_not_foundrestart on the stack to allow handlers to install the requiredpackages. To do so, add acalling handlerforrlib_error_package_not_found, install the required packages,and invoke the restart without arguments. This restarts the checkfrom scratch.
The condition is not signalled in non-interactive sessions, in therestarting case, or if therlib_restart_package_not_found useroption is set toFALSE.
is_installed("utils")is_installed(c("base", "ggplot5"))is_installed(c("base", "ggplot5"), version = c(NA, "5.1.0"))is_installed("utils")is_installed(c("base","ggplot5"))is_installed(c("base","ggplot5"), version= c(NA,"5.1.0"))
These predicates check whether R considers a number vector to beinteger-like, according to its own tolerance check (which is infact delegated to the C library). This function is not adapted todata analysis, see the help forbase::is.integer() for examplesof how to check for whole numbers.
Things to consider when checking for integer-like doubles:
This check can be expensive because the whole double vector hasto be traversed and checked.
Large double values may be integerish but may still not becoercible to integer. This is because integers in R only supportvalues up to2^31 - 1 while numbers stored as double can bemuch larger.
is_integerish(x, n = NULL, finite = NULL)is_bare_integerish(x, n = NULL, finite = NULL)is_scalar_integerish(x, finite = NULL)is_integerish(x, n=NULL, finite=NULL)is_bare_integerish(x, n=NULL, finite=NULL)is_scalar_integerish(x, finite=NULL)
x | Object to be tested. |
n | Expected length of a vector. |
finite | Whether all values of the vector are finite. Thenon-finite values are |
is_bare_numeric() for testing whether an object is abase numeric type (a bare double or integer vector).
is_integerish(10L)is_integerish(10.0)is_integerish(10.0, n = 2)is_integerish(10.000001)is_integerish(TRUE)is_integerish(10L)is_integerish(10.0)is_integerish(10.0, n=2)is_integerish(10.000001)is_integerish(TRUE)
Likebase::interactive(),is_interactive() returnsTRUE whenthe function runs interactively andFALSE when it runs in batchmode. It also checks, in this order:
Therlang_interactive global option. If set to a singleTRUEorFALSE,is_interactive() returns that value immediately. Thisescape hatch is useful in unit tests or to manually turn oninteractive features in RMarkdown outputs.
Whether knitr or testthat is in progress, in which caseis_interactive() returnsFALSE.
with_interactive() andlocal_interactive() set the globaloption conveniently.
is_interactive()local_interactive(value = TRUE, frame = caller_env())with_interactive(expr, value = TRUE)is_interactive()local_interactive(value=TRUE, frame= caller_env())with_interactive(expr, value=TRUE)
value | A single |
frame | The environment of a running function which definesthe scope of the temporary options. When the function returns,the options are reset to their original values. |
expr | An expression to evaluate with interactivity set to |
is_named() is a scalar predicate that checks thatx has anames attribute and that none of the names are missing or empty(NA or"").
is_named2() is likeis_named() but always returnsTRUE forempty vectors, even those that don't have anames attribute.In other words, it tests for the property that each element of avector is named.is_named2() composes well withnames2()whereasis_named() composes withnames().
have_name() is a vectorised variant.
is_named(x)is_named2(x)have_name(x)is_named(x)is_named2(x)have_name(x)
x | A vector to test. |
is_named() always returnsTRUE for empty vectors because
is_named() andis_named2() are scalar predicates thatreturnTRUE orFALSE.have_name() is vectorised and returnsa logical vector as long as the input.
# is_named() is a scalar predicate about the whole vector of names:is_named(c(a = 1, b = 2))is_named(c(a = 1, 2))# Unlike is_named2(), is_named() returns `FALSE` for empty vectors# that don't have a `names` attribute.is_named(list())is_named2(list())# have_name() is a vectorised predicatehave_name(c(a = 1, b = 2))have_name(c(a = 1, 2))# Empty and missing names are treated as invalid:invalid <- set_names(letters[1:5])names(invalid)[1] <- ""names(invalid)[3] <- NAis_named(invalid)have_name(invalid)# A data frame normally has valid, unique namesis_named(mtcars)have_name(mtcars)# A matrix usually doesn't because the names are stored in a# different attributemat <- matrix(1:4, 2)colnames(mat) <- c("a", "b")is_named(mat)names(mat)# is_named() is a scalar predicate about the whole vector of names:is_named(c(a=1, b=2))is_named(c(a=1,2))# Unlike is_named2(), is_named() returns `FALSE` for empty vectors# that don't have a `names` attribute.is_named(list())is_named2(list())# have_name() is a vectorised predicatehave_name(c(a=1, b=2))have_name(c(a=1,2))# Empty and missing names are treated as invalid:invalid<- set_names(letters[1:5])names(invalid)[1]<-""names(invalid)[3]<-NAis_named(invalid)have_name(invalid)# A data frame normally has valid, unique namesis_named(mtcars)have_name(mtcars)# A matrix usually doesn't because the names are stored in a# different attributemat<- matrix(1:4,2)colnames(mat)<- c("a","b")is_named(mat)names(mat)
Is an object a namespace environment?
is_namespace(x)is_namespace(x)
x | An object to test. |
Is object a symbol?
is_symbol(x, name = NULL)is_symbol(x, name=NULL)
x | An object to test. |
name | An optional name or vector of names that the symbolshould match. |
These functions bypass R's automatic conversion rules and checkthatx is literallyTRUE orFALSE.
is_true(x)is_false(x)is_true(x)is_false(x)
x | object to test |
is_true(TRUE)is_true(1)is_false(FALSE)is_false(0)is_true(TRUE)is_true(1)is_false(FALSE)is_false(0)
Is object a weak reference?
is_weakref(x)is_weakref(x)
x | An object to test. |
abort() errorlast_error() returns the last error entraced byabort() orglobal_entrace(). The error is printed with a backtrace insimplified form.
last_trace() is a shortcut to return the backtrace stored inthe last error. This backtrace is printed in full form.
last_error()last_trace(drop = NULL)last_error()last_trace(drop=NULL)
drop | Whether to drop technical calls. These are hidden fromusers by default, set |
rlang_backtrace_on_error to control what is displayed when anerror is thrown.
global_entrace() to enablelast_error() logging for all errors.
last_warnings() andlast_messages() return a list of allwarnings and messages that occurred during the last R command.
global_entrace() must be active in order to log the messages andwarnings.
By default the warnings and messages are printed with a simplifiedbacktrace, likelast_error(). Usesummary() to print theconditions with a full backtrace.
last_warnings(n = NULL)last_messages(n = NULL)last_warnings(n=NULL)last_messages(n=NULL)
n | How many warnings or messages to display. Defaults to all. |
Enable backtrace capture withglobal_entrace():
global_entrace()
Signal some warnings in nested functions. The warnings inform aboutwhich function emitted a warning but they don't provide informationabout the call stack:
f <- function() { warning("foo"); g() }g <- function() { warning("bar", immediate. = TRUE); h() }h <- function() warning("baz")f()#> Warning in g() : bar#> Warning messages:#> 1: In f() : foo#> 2: In h() : bazCalllast_warnings() to see backtraces for each of these warnings:
last_warnings()#> [[1]]#> <warning/rlang_warning>#> Warning in `f()`:#> foo#> Backtrace:#> x#> 1. \-global f()#>#> [[2]]#> <warning/rlang_warning>#> Warning in `g()`:#> bar#> Backtrace:#> x#> 1. \-global f()#> 2. \-global g()#>#> [[3]]#> <warning/rlang_warning>#> Warning in `h()`:#> baz#> Backtrace:#> x#> 1. \-global f()#> 2. \-global g()#> 3. \-global h()
This works similarly with messages:
f <- function() { inform("Hey!"); g() }g <- function() { inform("Hi!"); h() }h <- function() inform("Hello!")f()#> Hey!#> Hi!#> Hello!rlang::last_messages()#> [[1]]#> <message/rlang_message>#> Message:#> Hey!#> ---#> Backtrace:#> x#> 1. \-global f()#>#> [[2]]#> <message/rlang_message>#> Message:#> Hi!#> ---#> Backtrace:#> x#> 1. \-global f()#> 2. \-global g()#>#> [[3]]#> <message/rlang_message>#> Message:#> Hello!#> ---#> Backtrace:#> x#> 1. \-global f()#> 2. \-global g()#> 3. \-global h()list2(...) is equivalent tolist(...) with a few additionalfeatures, collectively calleddynamic dots. Whilelist2() hard-code these features,dots_list() is a lower-levelversion that offers more control.
list2(...)dots_list( ..., .named = FALSE, .ignore_empty = c("trailing", "none", "all"), .preserve_empty = FALSE, .homonyms = c("keep", "first", "last", "error"), .check_assign = FALSE)list2(...)dots_list(..., .named=FALSE, .ignore_empty= c("trailing","none","all"), .preserve_empty=FALSE, .homonyms= c("keep","first","last","error"), .check_assign=FALSE)
... | Arguments to collect in a list. These dots aredynamic. |
.named | If |
.ignore_empty | Whether to ignore empty arguments. Can be oneof |
.preserve_empty | Whether to preserve the empty arguments thatwere not ignored. If |
.homonyms | How to treat arguments with the same name. Thedefault, |
.check_assign | Whether to check for |
For historical reasons,dots_list() creates a named list bydefault. By comparisonlist2() implements the preferred behaviourof only creating a names vector when a name is supplied.
A list containing the... inputs.
# Let's create a function that takes a variable number of arguments:numeric <- function(...) { dots <- list2(...) num <- as.numeric(dots) set_names(num, names(dots))}numeric(1, 2, 3)# The main difference with list(...) is that list2(...) enables# the `!!!` syntax to splice lists:x <- list(2, 3)numeric(1, !!! x, 4)# As well as unquoting of names:nm <- "yup!"numeric(!!nm := 1)# One useful application of splicing is to work around exact and# partial matching of arguments. Let's create a function taking# named arguments and dots:fn <- function(data, ...) { list2(...)}# You normally cannot pass an argument named `data` through the dots# as it will match `fn`'s `data` argument. The splicing syntax# provides a workaround:fn("wrong!", data = letters) # exact matching of `data`fn("wrong!", dat = letters) # partial matching of `data`fn(some_data, !!!list(data = letters)) # no matching# Empty trailing arguments are allowed:list2(1, )# But non-trailing empty arguments cause an error:try(list2(1, , ))# Use the more configurable `dots_list()` function to preserve all# empty arguments:list3 <- function(...) dots_list(..., .preserve_empty = TRUE)# Note how the last empty argument is still ignored because# `.ignore_empty` defaults to "trailing":list3(1, , )# The list with preserved empty arguments is equivalent to:list(1, missing_arg())# Arguments with duplicated names are kept by default:list2(a = 1, a = 2, b = 3, b = 4, 5, 6)# Use the `.homonyms` argument to keep only the first of these:dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "first")# Or the last:dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "last")# Or raise an informative error:try(dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "error"))# dots_list() can be configured to warn when a `<-` call is# detected:my_list <- function(...) dots_list(..., .check_assign = TRUE)my_list(a <- 1)# There is no warning if the assignment is wrapped in braces.# This requires users to be explicit about their intent:my_list({ a <- 1 })# Let's create a function that takes a variable number of arguments:numeric<-function(...){ dots<- list2(...) num<- as.numeric(dots) set_names(num, names(dots))}numeric(1,2,3)# The main difference with list(...) is that list2(...) enables# the `!!!` syntax to splice lists:x<- list(2,3)numeric(1,!!! x,4)# As well as unquoting of names:nm<-"yup!"numeric(!!nm:=1)# One useful application of splicing is to work around exact and# partial matching of arguments. Let's create a function taking# named arguments and dots:fn<-function(data,...){ list2(...)}# You normally cannot pass an argument named `data` through the dots# as it will match `fn`'s `data` argument. The splicing syntax# provides a workaround:fn("wrong!", data= letters)# exact matching of `data`fn("wrong!", dat= letters)# partial matching of `data`fn(some_data,!!!list(data= letters))# no matching# Empty trailing arguments are allowed:list2(1,)# But non-trailing empty arguments cause an error:try(list2(1,,))# Use the more configurable `dots_list()` function to preserve all# empty arguments:list3<-function(...) dots_list(..., .preserve_empty=TRUE)# Note how the last empty argument is still ignored because# `.ignore_empty` defaults to "trailing":list3(1,,)# The list with preserved empty arguments is equivalent to:list(1, missing_arg())# Arguments with duplicated names are kept by default:list2(a=1, a=2, b=3, b=4,5,6)# Use the `.homonyms` argument to keep only the first of these:dots_list(a=1, a=2, b=3, b=4,5,6, .homonyms="first")# Or the last:dots_list(a=1, a=2, b=3, b=4,5,6, .homonyms="last")# Or raise an informative error:try(dots_list(a=1, a=2, b=3, b=4,5,6, .homonyms="error"))# dots_list() can be configured to warn when a `<-` call is# detected:my_list<-function(...) dots_list(..., .check_assign=TRUE)my_list(a<-1)# There is no warning if the assignment is wrapped in braces.# This requires users to be explicit about their intent:my_list({ a<-1})
local_bindings() temporarily changes bindings in.env (whichis by default the caller environment). The bindings are reset totheir original values when the current frame (or an arbitrary oneif you specify.frame) goes out of scope.
with_bindings() evaluatesexpr with temporary bindings. Whenwith_bindings() returns, bindings are reset to their originalvalues. It is a simple wrapper aroundlocal_bindings().
local_bindings(..., .env = .frame, .frame = caller_env())with_bindings(.expr, ..., .env = caller_env())local_bindings(..., .env= .frame, .frame= caller_env())with_bindings(.expr,..., .env= caller_env())
... | Pairs of names and values. These dots support splicing(with value semantics) and name unquoting. |
.env | An environment. |
.frame | The frame environment that determines the scope ofthe temporary bindings. When that frame is popped from the callstack, bindings are switched back to their original values. |
.expr | An expression to evaluate with temporary bindings. |
local_bindings() returns the values of old bindingsinvisibly;with_bindings() returns the value ofexpr.
foo <- "foo"bar <- "bar"# `foo` will be temporarily rebinded while executing `expr`with_bindings(paste(foo, bar), foo = "rebinded")paste(foo, bar)foo<-"foo"bar<-"bar"# `foo` will be temporarily rebinded while executing `expr`with_bindings(paste(foo, bar), foo="rebinded")paste(foo, bar)
local_error_call() is an alternative to explicitly passing acall argument toabort(). It sets the call (or a value thatindicates where to find the call, see below) in a local bindingthat is automatically picked up byabort().
local_error_call(call, frame = caller_env())local_error_call(call, frame= caller_env())
call | This can be:
|
frame | The execution environment in which to set the localerror call. |
By defaultabort() uses the function call of its caller ascontext in error messages:
foo <- function() abort("Uh oh.")foo()#> Error in `foo()`: Uh oh.This is not always appropriate. For example a function that checksan input on the behalf of another function should reference thelatter, not the former:
arg_check <- function(arg, error_arg = as_string(substitute(arg))) { abort(cli::format_error("{.arg {error_arg}} is failing."))}foo <- function(x) arg_check(x)foo()#> Error in `arg_check()`: `x` is failing.The mismatch is clear in the example above.arg_check() does nothave anyx argument and so it is confusing to presentarg_check() as being the relevant context for the failure of thex argument.
One way around this is to take acall orerror_call argumentand pass it toabort(). Here we name this argumenterror_callfor consistency witherror_arg which is prefixed because there isan existingarg argument. In other situations, takingarg andcall arguments might be appropriate.
arg_check <- function(arg, error_arg = as_string(substitute(arg)), error_call = caller_env()) { abort( cli::format_error("{.arg {error_arg}} is failing."), call = error_call )}foo <- function(x) arg_check(x)foo()#> Error in `foo()`: `x` is failing.This is the generally recommended pattern for argument checkingfunctions. If you mention an argument in an error message, provideyour callers a way to supply a different argument name and adifferent error call.abort() stores the error call in thecallcondition field which is then used to generate the "in" part oferror messages.
In more complex cases it's often burdensome to pass the relevantcall around, for instance if your checking and throwing code isstructured into many different functions. In this case, uselocal_error_call() to set the call locally or instructabort()to climb the call stack one level to find the relevant call. In thefollowing example, the complexity is not so important that sparingthe argument passing makes a big difference. However thisillustrates the pattern:
arg_check <- function(arg, error_arg = caller_arg(arg), error_call = caller_env()) { # Set the local error call local_error_call(error_call) my_classed_stop( cli::format_error("{.arg {error_arg}} is failing.") )}my_classed_stop <- function(message) { # Forward the local error call to the caller's local_error_call(caller_env()) abort(message, class = "my_class")}foo <- function(x) arg_check(x)foo()#> Error in `foo()`: `x` is failing.Thecall argument can also be the string"caller". This isequivalent tocaller_env() orparent.frame() but has a loweroverhead because call stack introspection is only performed when anerror is triggered. Note that eagerly callingcaller_env() isfast enough in almost all cases.
If your function needs to be really fast, assign the error callflag directly instead of callinglocal_error_call():
.__error_call__. <- "caller"
# Set a context for error messagesfunction() { local_error_call(quote(foo())) local_error_call(sys.call())}# Disable the contextfunction() { local_error_call(NULL)}# Use the caller's contextfunction() { local_error_call(caller_env())}# Set a context for error messagesfunction(){ local_error_call(quote(foo())) local_error_call(sys.call())}# Disable the contextfunction(){ local_error_call(NULL)}# Use the caller's contextfunction(){ local_error_call(caller_env())}
local_options() changes options for the duration of a stackframe (by default the current one). Options are set back to theirold values when the frame returns.
with_options() changes options while an expression isevaluated. Options are restored when the expression returns.
push_options() adds or changes options permanently.
peek_option() andpeek_options() return option values. Theformer returns the option directly while the latter returns alist.
local_options(..., .frame = caller_env())with_options(.expr, ...)push_options(...)peek_options(...)peek_option(name)local_options(..., .frame= caller_env())with_options(.expr,...)push_options(...)peek_options(...)peek_option(name)
... | For |
.frame | The environment of a stack frame which defines thescope of the temporary options. When the frame returns, theoptions are set back to their original values. |
.expr | An expression to evaluate with temporary options. |
name | An option name as string. |
Forlocal_options() andpush_options(), the old optionvalues.peek_option() returns the current value of an optionwhile the pluralpeek_options() returns a list of currentoption values.
These functions are experimental.
# Store and retrieve a global option:push_options(my_option = 10)peek_option("my_option")# Change the option temporarily:with_options(my_option = 100, peek_option("my_option"))peek_option("my_option")# The scoped variant is useful within functions:fn <- function() { local_options(my_option = 100) peek_option("my_option")}fn()peek_option("my_option")# The plural peek returns a named list:peek_options("my_option")peek_options("my_option", "digits")# Store and retrieve a global option:push_options(my_option=10)peek_option("my_option")# Change the option temporarily:with_options(my_option=100, peek_option("my_option"))peek_option("my_option")# The scoped variant is useful within functions:fn<-function(){ local_options(my_option=100) peek_option("my_option")}fn()peek_option("my_option")# The plural peek returns a named list:peek_options("my_option")peek_options("my_option","digits")
These functions help using the missing argument as a regular Robject.
missing_arg() generates a missing argument.
is_missing() is likebase::missing() but also supportstesting for missing arguments contained in other objects likelists. It is also more consistent with default arguments whichare never treated as missing (see section below).
maybe_missing() is useful to pass down an input that might bemissing to another function, potentially substituting by adefault value. It avoids triggering an "argument is missing" error.
missing_arg()is_missing(x)maybe_missing(x, default = missing_arg())missing_arg()is_missing(x)maybe_missing(x, default= missing_arg())
x | An object that might be the missing argument. |
default | The object to return if the input is missing,defaults to |
base::quote(expr = ) is the canonical way to create a missingargument object.
expr() called without argument creates a missing argument.
quo() called without argument creates an empty quosure, i.e. aquosure containing the missing argument object.
is_missing() and default argumentsThe base functionmissing() makes a distinction between defaultvalues supplied explicitly and default values generated through amissing argument:
fn <- function(x = 1) base::missing(x)fn()#> [1] TRUEfn(1)#> [1] FALSE
This only happens within a function. If the default value has beengenerated in a calling function, it is never treated as missing:
caller <- function(x = 1) fn(x)caller()#> [1] FALSE
rlang::is_missing() simplifies these rules by never treatingdefault arguments as missing, even in internal contexts:
fn <- function(x = 1) rlang::is_missing(x)fn()#> [1] FALSEfn(1)#> [1] FALSE
This is a little less flexible because you can't specialisebehaviour based on implicitly supplied default values. However,this makes the behaviour ofis_missing() and functions using itsimpler to understand.
The missing argument is an object that triggers an error if andonly if it is the result of evaluating a symbol. No error isproduced when a function call evaluates to the missing argumentobject. For instance, it is possible to bind the missing argumentto a variable with an expression likex[[1]] <- missing_arg().Likewise,x[[1]] is safe to use as argument, e.g.list(x[[1]])even when the result is the missing object.
However, as soon as the missing argument is passed down betweenfunctions through a bare variable, it is likely to cause a missingargument error:
x <- missing_arg()list(x)#> Error:#> ! argument "x" is missing, with no default
To work around this,is_missing() andmaybe_missing(x) use abit of magic to determine if the input is the missing argumentwithout triggering a missing error.
x <- missing_arg()list(maybe_missing(x))#> [[1]]#>
maybe_missing() is particularly useful for prototypingmeta-programming algorithms in R. The missing argument is a likelyinput when computing on the language because it is a standardobject in formals lists. While C functions are always allowed toreturn the missing argument and pass it to other C functions, thisis not the case on the R side. If you're implementing yourmeta-programming algorithm in R, usemaybe_missing() when aninput might be the missing argument object.
# The missing argument usually arises inside a function when the# user omits an argument that does not have a default:fn <- function(x) is_missing(x)fn()# Creating a missing argument can also be useful to generate callsargs <- list(1, missing_arg(), 3, missing_arg())quo(fn(!!! args))# Other ways to create that object include:quote(expr = )expr()# It is perfectly valid to generate and assign the missing# argument in a list.x <- missing_arg()l <- list(missing_arg())# Just don't evaluate a symbol that contains the empty argument.# Evaluating the object `x` that we created above would trigger an# error.# x # Not run# On the other hand accessing a missing argument contained in a# list does not trigger an error because subsetting is a function# call:l[[1]]is.null(l[[1]])# In case you really need to access a symbol that might contain the# empty argument object, use maybe_missing():maybe_missing(x)is.null(maybe_missing(x))is_missing(maybe_missing(x))# Note that base::missing() only works on symbols and does not# support complex expressions. For this reason the following lines# would throw an error:#> missing(missing_arg())#> missing(l[[1]])# while is_missing() will work as expected:is_missing(missing_arg())is_missing(l[[1]])# The missing argument usually arises inside a function when the# user omits an argument that does not have a default:fn<-function(x) is_missing(x)fn()# Creating a missing argument can also be useful to generate callsargs<- list(1, missing_arg(),3, missing_arg())quo(fn(!!! args))# Other ways to create that object include:quote(expr=)expr()# It is perfectly valid to generate and assign the missing# argument in a list.x<- missing_arg()l<- list(missing_arg())# Just don't evaluate a symbol that contains the empty argument.# Evaluating the object `x` that we created above would trigger an# error.# x # Not run# On the other hand accessing a missing argument contained in a# list does not trigger an error because subsetting is a function# call:l[[1]]is.null(l[[1]])# In case you really need to access a symbol that might contain the# empty argument object, use maybe_missing():maybe_missing(x)is.null(maybe_missing(x))is_missing(maybe_missing(x))# Note that base::missing() only works on symbols and does not# support complex expressions. For this reason the following lines# would throw an error:#> missing(missing_arg())#> missing(l[[1]])# while is_missing() will work as expected:is_missing(missing_arg())is_missing(l[[1]])
names2() always returns a character vector, even when anobject does not have anames attribute. In this case, it returnsa vector of empty names"". It also standardises missing names to"".
The replacement variantnames2<- never addsNA names andinstead fills unnamed vectors with"".
names2(x)names2(x) <- valuenames2(x)names2(x)<- value
x | A vector. |
value | New names. |
names2(letters)# It also takes care of standardising missing names:x <- set_names(1:3, c("a", NA, "b"))names2(x)# Replacing names with the base `names<-` function may introduce# `NA` values when the vector is unnamed:x <- 1:3names(x)[1:2] <- "foo"names(x)# Use the `names2<-` variant to avoid thisx <- 1:3names2(x)[1:2] <- "foo"names(x)names2(letters)# It also takes care of standardising missing names:x<- set_names(1:3, c("a",NA,"b"))names2(x)# Replacing names with the base `names<-` function may introduce# `NA` values when the vector is unnamed:x<-1:3names(x)[1:2]<-"foo"names(x)# Use the `names2<-` variant to avoid thisx<-1:3names2(x)[1:2]<-"foo"names(x)
Create a formula
new_formula(lhs, rhs, env = caller_env())new_formula(lhs, rhs, env= caller_env())
lhs,rhs | A call, name, or atomic vector. |
env | An environment. |
A formula object.
new_formula(quote(a), quote(b))new_formula(NULL, quote(b))new_formula(quote(a), quote(b))new_formula(NULL, quote(b))
This constructs a new function given its three components:list of arguments, body code and parent environment.
new_function(args, body, env = caller_env())new_function(args, body, env= caller_env())
args | A named list or pairlist of default arguments. Notethat if you want arguments that don't have defaults, you'll needto use the special function |
body | A language object representing the code inside thefunction. Usually this will be most easily generated with |
env | The parent environment of the function, defaults to thecalling environment of |
f <- function() lettersg <- 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))f<-function() lettersg<- 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_quosure() wraps any R object (including expressions,formulas, or other quosures) into aquosure.
as_quosure() is similar but it does not rewrap formulas andquosures.
new_quosure(expr, env = caller_env())as_quosure(x, env = NULL)is_quosure(x)new_quosure(expr, env= caller_env())as_quosure(x, env=NULL)is_quosure(x)
expr | An expression to wrap in a quosure. |
env | The environment in which the expression should beevaluated. Only used for symbols and calls. This should normallybe the environment in which the expression was created. |
x | An object to test. |
enquo() andquo() for creating a quosure byargument defusal.
# `new_quosure()` creates a quosure from its components. These are# equivalent:new_quosure(quote(foo), current_env())quo(foo)# `new_quosure()` always rewraps its input into a new quosure, even# if the input is itself a quosure:new_quosure(quo(foo))# This is unlike `as_quosure()` which preserves its input if it's# already a quosure:as_quosure(quo(foo))# `as_quosure()` uses the supplied environment with naked expressions:env <- env(var = "thing")as_quosure(quote(var), env)# If the expression already carries an environment, this# environment is preserved. This is the case for formulas and# quosures:as_quosure(~foo, env)as_quosure(~foo)# An environment must be supplied when the input is a naked# expression:try( as_quosure(quote(var)))# `new_quosure()` creates a quosure from its components. These are# equivalent:new_quosure(quote(foo), current_env())quo(foo)# `new_quosure()` always rewraps its input into a new quosure, even# if the input is itself a quosure:new_quosure(quo(foo))# This is unlike `as_quosure()` which preserves its input if it's# already a quosure:as_quosure(quo(foo))# `as_quosure()` uses the supplied environment with naked expressions:env<- env(var="thing")as_quosure(quote(var), env)# If the expression already carries an environment, this# environment is preserved. This is the case for formulas and# quosures:as_quosure(~foo, env)as_quosure(~foo)# An environment must be supplied when the input is a naked# expression:try( as_quosure(quote(var)))
This small S3 class provides methods for[ andc() and ensuresthe following invariants:
The list only contains quosures.
It is always named, possibly with a vector of empty strings.
new_quosures() takes a list of quosures and adds thequosuresclass and a vector of empty names if needed.as_quosures() callsas_quosure() on all elements before creating thequosuresobject.
new_quosures(x)as_quosures(x, env, named = FALSE)is_quosures(x)new_quosures(x)as_quosures(x, env, named=FALSE)is_quosures(x)
x | A list of quosures or objects to coerce to quosures. |
env | The default environment for the new quosures. |
named | Whether to name the list with |
A weak reference is a special R object which makes it possible to keep areference to an object without preventing garbage collection of that object.It can also be used to keep data about an object without preventing GC of theobject, similar to WeakMaps in JavaScript.
Objects in R are consideredreachable if they can be accessed by followinga chain of references, starting from aroot node; root nodes arespecially-designated R objects, and include the global environment and baseenvironment. As long as the key is reachable, the value will not be garbagecollected. This is true even if the weak reference object becomesunreachable. The key effectively prevents the weak reference and its valuefrom being collected, according to the following chain of ownership:weakref <- key -> value.
When the key becomes unreachable, the key and value in the weak referenceobject are replaced byNULL, and the finalizer is scheduled to execute.
new_weakref(key, value = NULL, finalizer = NULL, on_quit = FALSE)new_weakref(key, value=NULL, finalizer=NULL, on_quit=FALSE)
key | The key for the weak reference. Must be a reference object – thatis, an environment or external pointer. |
value | The value for the weak reference. This can be |
finalizer | A function that is run after the key becomes unreachable. |
on_quit | Should the finalizer be run when R exits? |
is_weakref(),wref_key() andwref_value().
e <- env()# Create a weak reference to ew <- new_weakref(e, finalizer = function(e) message("finalized"))# Get the key object from the weak referenceidentical(wref_key(w), e)# When the regular reference (the `e` binding) is removed and a GC occurs,# the weak reference will not keep the object alive.rm(e)gc()identical(wref_key(w), NULL)# A weak reference with a key and value. The value contains data about the# key.k <- env()v <- list(1, 2, 3)w <- new_weakref(k, v)identical(wref_key(w), k)identical(wref_value(w), v)# When v is removed, the weak ref keeps it alive because k is still reachable.rm(v)gc()identical(wref_value(w), list(1, 2, 3))# When k is removed, the weak ref does not keep k or v alive.rm(k)gc()identical(wref_key(w), NULL)identical(wref_value(w), NULL)e<- env()# Create a weak reference to ew<- new_weakref(e, finalizer=function(e) message("finalized"))# Get the key object from the weak referenceidentical(wref_key(w), e)# When the regular reference (the `e` binding) is removed and a GC occurs,# the weak reference will not keep the object alive.rm(e)gc()identical(wref_key(w),NULL)# A weak reference with a key and value. The value contains data about the# key.k<- env()v<- list(1,2,3)w<- new_weakref(k, v)identical(wref_key(w), k)identical(wref_value(w), v)# When v is removed, the weak ref keeps it alive because k is still reachable.rm(v)gc()identical(wref_value(w), list(1,2,3))# When k is removed, the weak ref does not keep k or v alive.rm(k)gc()identical(wref_key(w),NULL)identical(wref_value(w),NULL)
on_load() registers expressions to be run on the user's machineeach time the package is loaded in memory. This is by contrast tonormal R package code which is run once at build time on thepackager's machine (e.g. CRAN).
on_load() expressions requirerun_on_load() to be calledinside.onLoad().
on_package_load() registers expressions to be run each timeanother package is loaded.
on_load() is for your own package and runs expressions when thenamespace is notsealed yet. This means you can modify existingbinding or create new ones. This is not the case withon_package_load() which runs expressions after a foreign packagehas finished loading, at which point its namespace is sealed.
on_load(expr, env = parent.frame(), ns = topenv(env))run_on_load(ns = topenv(parent.frame()))on_package_load(pkg, expr, env = parent.frame())on_load(expr, env= parent.frame(), ns= topenv(env))run_on_load(ns= topenv(parent.frame()))on_package_load(pkg, expr, env= parent.frame())
expr | An expression to run on load. |
env | The environment in which to evaluate |
ns | The namespace in which to hook |
pkg | Package to hook expression into. |
There are two main use cases for running expressions on load:
When a side effect, such as registering a method withs3_register(), must occur in the user session rather than thepackage builder session.
To avoid hard-coding objects from other packages in yournamespace. If you assignfoo::bar or the result offoo::baz() in your package, they become constants. Anyupstream changes in thefoo package will not be reflected inthe objects you've assigned in your namespace. This often breaksassumptions made by the authors offoo and causes all sorts ofissues.
Recreating the foreign objects each time your package is loadedmakes sure that any such changes will be taken into account. Intechnical terms, running an expression on load introducesindirection.
.onLoad()on_load() has the advantage that hooked expressions can appear inany file, in context. This is unlike.onLoad() which gathersdisparate expressions in a single block.
on_load() is implemented via.onLoad() and requiresrun_on_load() to be called from that hook.
The expressions insideon_load() do not undergo static analysisbyR CMD check. Therefore, it is advisable to only usesimple function calls insideon_load().
quote({ # Not run# First add `run_on_load()` to your `.onLoad()` hook,# then use `on_load()` anywhere in your package.onLoad <- function(lib, pkg) { run_on_load()}# Register a method on loadon_load({ s3_register("foo::bar", "my_class")})# Assign an object on loadvar <- NULLon_load({ var <- foo()})# To use `on_package_load()` at top level, wrap it in `on_load()`on_load({ on_package_load("foo", message("foo is loaded"))})# In functions it can be called directlyf <- function() on_package_load("foo", message("foo is loaded"))})quote({# Not run# First add `run_on_load()` to your `.onLoad()` hook,# then use `on_load()` anywhere in your package.onLoad<-function(lib, pkg){ run_on_load()}# Register a method on loadon_load({ s3_register("foo::bar","my_class")})# Assign an object on loadvar<-NULLon_load({ var<- foo()})# To use `on_package_load()` at top level, wrap it in `on_load()`on_load({ on_package_load("foo", message("foo is loaded"))})# In functions it can be called directlyf<-function() on_package_load("foo", message("foo is loaded"))})
This operator extracts or sets attributes for regular objects andS4 fields for S4 objects.
x %@% namex %@% name <- valuex%@% namex%@% name<- value
x | Object |
name | Attribute name |
value | New value for attribute |
# Unlike `@`, this operator extracts attributes for any kind of# objects:factor(1:3) %@% "levels"mtcars %@% classmtcars %@% class <- NULLmtcars# It also works on S4 objects:.Person <- setClass("Person", slots = c(name = "character", species = "character"))fievel <- .Person(name = "Fievel", species = "mouse")fievel %@% name# Unlike `@`, this operator extracts attributes for any kind of# objects:factor(1:3)%@%"levels"mtcars%@% classmtcars%@% class<-NULLmtcars# It also works on S4 objects:.Person<- setClass("Person", slots= c(name="character", species="character"))fievel<- .Person(name="Fievel", species="mouse")fievel%@% name
NULLThis infix operator is the conceptual opposite of%||%, providing a fallbackonly ifx is defined.
x %&&% yx%&&% y
x,y | If |
1 %&&% 2NULL %&&% 21%&&%2NULL%&&%2
NULLThis infix function makes it easy to replaceNULLs with a defaultvalue. It's inspired by the way that Ruby's or operation (||)works.
x %||% yx%||% y
x,y | If |
1 %||% 2NULL %||% 21%||%2NULL%||%2
This pairlist constructor usesdynamic dots. Useit to manually create argument lists for calls or parameter listsfor functions.
pairlist2(...)pairlist2(...)
... | <dynamic> Arguments stored in thepairlist. Empty arguments are preserved. |
# Unlike `exprs()`, `pairlist2()` evaluates its arguments.new_function(pairlist2(x = 1, y = 3 * 6), quote(x * y))new_function(exprs(x = 1, y = 3 * 6), quote(x * y))# It preserves missing arguments, which is useful for creating# parameters without defaults:new_function(pairlist2(x = , y = 3 * 6), quote(x * y))# Unlike `exprs()`, `pairlist2()` evaluates its arguments.new_function(pairlist2(x=1, y=3*6), quote(x* y))new_function(exprs(x=1, y=3*6), quote(x* y))# It preserves missing arguments, which is useful for creating# parameters without defaults:new_function(pairlist2(x=, y=3*6), quote(x* y))
These functions parse and transform text into R expressions. Thisis the first step to interpret or evaluate a piece of R codewritten by a programmer.
parse_expr() returns one expression. If the text contains morethan one expression (separated by semicolons or new lines), anerror is issued. On the other handparse_exprs() can handlemultiple expressions. It always returns a list of expressions(compare tobase::parse() which returns a base::expressionvector). All functions also support R connections.
parse_expr() concatenatesx with\\n separators prior toparsing in order to support the roundtripparse_expr(expr_deparse(x)) (deparsed expressions might bemultiline). On the other hand,parse_exprs() doesn't do anyconcatenation because it's designed to support named inputs. Thenames are matched to the expressions in the output, which isuseful when a single named string creates multiple expressions.
In other words,parse_expr() supports vector of lines whereasparse_exprs() expects vectors of complete deparsed expressions.
parse_quo() andparse_quos() are variants that create aquosure. Supplyenv = current_env() if you're parsingcode to be evaluated in your current context. Supplyenv = global_env() when you're parsing external user input to beevaluated in user context.
Unlike quosures created withenquo(),enquos(), or{{, aparsed quosure never contains injected quosures. It is thus safeto evaluate them witheval() instead ofeval_tidy(), thoughthe latter is more convenient as you don't need to extractexprandenv.
parse_expr(x)parse_exprs(x)parse_quo(x, env)parse_quos(x, env)parse_expr(x)parse_exprs(x)parse_quo(x, env)parse_quos(x, env)
x | Text containing expressions to parse_expr for |
env | The environment for the quosures. Theglobal environment (the default) may be the right choicewhen you are parsing external user inputs. You might also want toevaluate the R code in an isolated context (perhaps a child ofthe global environment or of thebase environment). |
Unlikebase::parse(), these functions never retain source referenceinformation, as doing so is slow and rarely necessary.
parse_expr() returns anexpression,parse_exprs() returns a list of expressions. Note that for theplural variants the length of the output may be greater than thelength of the input. This would happen is one of the stringscontain several expressions (such as"foo; bar"). The names ofx are preserved (and recycled in case of multiple expressions).The_quo suffixed variants return quosures.
# parse_expr() can parse any R expression:parse_expr("mtcars %>% dplyr::mutate(cyl_prime = cyl / sd(cyl))")# A string can contain several expressions separated by ; or \nparse_exprs("NULL; list()\n foo(bar)")# Use names to figure out which input produced an expression:parse_exprs(c(foo = "1; 2", bar = "3"))# You can also parse source files by passing a R connection. Let's# create a file containing R code:path <- tempfile("my-file.R")cat("1; 2; mtcars", file = path)# We can now parse it by supplying a connection:parse_exprs(file(path))# parse_expr() can parse any R expression:parse_expr("mtcars %>% dplyr::mutate(cyl_prime = cyl / sd(cyl))")# A string can contain several expressions separated by ; or \nparse_exprs("NULL; list()\n foo(bar)")# Use names to figure out which input produced an expression:parse_exprs(c(foo="1; 2", bar="3"))# You can also parse source files by passing a R connection. Let's# create a file containing R code:path<- tempfile("my-file.R")cat("1; 2; mtcars", file= path)# We can now parse it by supplying a connection:parse_exprs(file(path))
qq_show() helps examininginjected expressionsinside a function. This is useful for learning about injection andfor debugging injection code.
expr | An expression involvinginjection operators. |
qq_show() shows the intermediary expression before it isevaluated by R:
list2(!!!1:3)#> [[1]]#> [1] 1#> #> [[2]]#> [1] 2#> #> [[3]]#> [1] 3qq_show(list2(!!!1:3))#> list2(1L, 2L, 3L)
It is especially useful inside functions to reveal what an injectedexpression looks like:
my_mean <- function(data, var) { qq_show(data %>% dplyr::summarise(mean({{ var }})))}mtcars %>% my_mean(cyl)#> data %>% dplyr::summarise(mean(^cyl))quo_squash() flattens all nested quosures within an expression.For example it transforms^foo(^bar(), ^baz) to the bareexpressionfoo(bar(), baz).
This operation is safe if the squashed quosure is used forlabelling or printing (seeas_label(), but note thatas_label()squashes quosures automatically). However if the squashed quosureis evaluated, all expressions of the flattened quosures areresolved in a single environment. This is a source of bugs so it isgood practice to setwarn toTRUE to let the user know aboutthe lossy squashing.
quo_squash(quo, warn = FALSE)quo_squash(quo, warn=FALSE)
quo | A quosure or expression. |
warn | Whether to warn if the quosure contains other quosures(those will be collapsed). This is useful when you use |
# Quosures can contain nested quosures:quo <- quo(wrapper(!!quo(wrappee)))quo# quo_squash() flattens all the quosures and returns a simple expression:quo_squash(quo)# Quosures can contain nested quosures:quo<- quo(wrapper(!!quo(wrappee)))quo# quo_squash() flattens all the quosures and returns a simple expression:quo_squash(quo)
These tools inspect and modifyquosures, a type ofdefused expression that includes a reference to thecontext where it was created. A quosure is guaranteed to evaluatein its original environment and can refer to local objects safely.
You can access the quosure components withquo_get_expr() andquo_get_env().
Thequo_ prefixed predicates test the expression of a quosure,quo_is_missing(),quo_is_symbol(), etc.
Allquo_ prefixed functions expect a quosure and will fail ifsupplied another type of object. Make sure the input is a quosurewithis_quosure().
quo_is_missing(quo)quo_is_symbol(quo, name = NULL)quo_is_call(quo, name = NULL, n = NULL, ns = NULL)quo_is_symbolic(quo)quo_is_null(quo)quo_get_expr(quo)quo_get_env(quo)quo_set_expr(quo, expr)quo_set_env(quo, env)quo_is_missing(quo)quo_is_symbol(quo, name=NULL)quo_is_call(quo, name=NULL, n=NULL, ns=NULL)quo_is_symbolic(quo)quo_is_null(quo)quo_get_expr(quo)quo_get_env(quo)quo_set_expr(quo, expr)quo_set_env(quo, env)
quo | A quosure to test. |
name | The name of the symbol or function call. If |
n | An optional number of arguments that the call shouldmatch. |
ns | The namespace of the call. If Can be a character vector of namespaces, in which case the callhas to match at least one of them, otherwise |
expr | A new expression for the quosure. |
env | A new environment for the quosure. |
When missing arguments are captured as quosures, either throughenquo() orquos(), they are returned as an empty quosure. Thesequosures contain themissing argument and typicallyhave theempty environment as enclosure.
Usequo_is_missing() to test for a missing argument defused withenquo().
quo() for creating quosures byargument defusal.
new_quosure() andas_quosure() for assembling quosures fromcomponents.
What are quosures and when are they needed? for an overview.
quo <- quo(my_quosure)quo# Access and set the components of a quosure:quo_get_expr(quo)quo_get_env(quo)quo <- quo_set_expr(quo, quote(baz))quo <- quo_set_env(quo, empty_env())quo# Test wether an object is a quosure:is_quosure(quo)# If it is a quosure, you can use the specialised type predicates# to check what is inside it:quo_is_symbol(quo)quo_is_call(quo)quo_is_null(quo)# quo_is_missing() checks for a special kind of quosure, the one# that contains the missing argument:quo()quo_is_missing(quo())fn <- function(arg) enquo(arg)fn()quo_is_missing(fn())quo<- quo(my_quosure)quo# Access and set the components of a quosure:quo_get_expr(quo)quo_get_env(quo)quo<- quo_set_expr(quo, quote(baz))quo<- quo_set_env(quo, empty_env())quo# Test wether an object is a quosure:is_quosure(quo)# If it is a quosure, you can use the specialised type predicates# to check what is inside it:quo_is_symbol(quo)quo_is_call(quo)quo_is_null(quo)# quo_is_missing() checks for a special kind of quosure, the one# that contains the missing argument:quo()quo_is_missing(quo())fn<-function(arg) enquo(arg)fn()quo_is_missing(fn())
These functions take the idea ofseq_along() and apply it torepeating values.
rep_along(along, x)rep_named(names, x)rep_along(along, x)rep_named(names, x)
along | Vector whose length determine how many times |
x | Values to repeat. |
names | Names for the new vector. The length of |
new-vector
x <- 0:5rep_along(x, 1:2)rep_along(x, 1)# Create fresh vectors by repeating missing values:rep_along(x, na_int)rep_along(x, na_chr)# rep_named() repeats a value along a names vectorsrep_named(c("foo", "bar"), list(letters))x<-0:5rep_along(x,1:2)rep_along(x,1)# Create fresh vectors by repeating missing values:rep_along(x, na_int)rep_along(x, na_chr)# rep_named() repeats a value along a names vectorsrep_named(c("foo","bar"), list(letters))
rlang errors carry a backtrace that can be inspected by callinglast_error(). You can also control the default display of thebacktrace by setting the optionrlang_backtrace_on_error to oneof the following values:
"none" show nothing.
"reminder", the default in interactive sessions, displays a reminder thatyou can see the backtrace withlast_error().
"branch" displays a simplified backtrace.
"full", the default in non-interactive sessions, displays the full tree.
rlang errors are normally thrown withabort(). If you promotebase errors to rlang errors withglobal_entrace(),rlang_backtrace_on_error applies to all errors.
You can useoptions(error = rlang::entrace) to promote base errors torlang errors. This does two things:
It saves the base error as an rlang object so you can calllast_error()to print the backtrace or inspect its data.
It prints the backtrace for the current error according to therlang_backtrace_on_error option.
The display of errors depends on whether they're expected (i.e.chunk optionerror = TRUE) or unexpected:
Expected errors are controlled by the global option"rlang_backtrace_on_error_report" (note the_report suffix).The default is"none" so that your expected errors don'tinclude a reminder to runrlang::last_error(). Customise thisoption if you want to demonstrate what the error backtrace willlook like.
You can also uselast_error() to display the trace like youwould in your session, but it currently only works in the nextchunk.
Unexpected errors are controlled by the global option"rlang_backtrace_on_error". The default is"branch" so you'llsee a simplified backtrace in the knitr output to help you figureout what went wrong.
When knitr is running (as determined by theknitr.in.progressglobal option), the default top environment for backtraces is setto the chunk environmentknitr::knit_global(). This ensures thatthe part of the call stack belonging to knitr does not end up inbacktraces. If needed, you can override this by setting therlang_trace_top_env global option.
Similarly torlang_backtrace_on_error_report, you can setrlang_backtrace_on_warning_report inside RMarkdown documents totweak the display of warnings. This is useful in conjunction withglobal_entrace(). Because of technical limitations, there iscurrently no correspondingrlang_backtrace_on_warning option fornormal R sessions.
To get full entracing in an Rmd document, include this in a setupchunk before the first error or warning is signalled.
```{r setup}rlang::global_entrace()options(rlang_backtrace_on_warning_report = "full")options(rlang_backtrace_on_error_report = "full")```rlang_backtrace_on_warning
# Display a simplified backtrace on error for both base and rlang# errors:# options(# rlang_backtrace_on_error = "branch",# error = rlang::entrace# )# stop("foo")# Display a simplified backtrace on error for both base and rlang# errors:# options(# rlang_backtrace_on_error = "branch",# error = rlang::entrace# )# stop("foo")
rlang_errorabort() anderror_cnd() create errors of class"rlang_error".The differences with base errors are:
ImplementingconditionMessage() methods for subclasses of"rlang_error" is undefined behaviour. Instead, implement thecnd_header() method (and possiblycnd_body() andcnd_footer()). These methods return character vectors which areassembled by rlang when needed: whenconditionMessage.rlang_error() is called(e.g. viatry()), when the error is displayed throughprint()orformat(), and of course when the error is displayed to theuser byabort().
cnd_header(),cnd_body(), andcnd_footer() methods can beoverridden by storing closures in theheader,body, andfooter fields of the condition. This is useful to lazilygenerate messages based on state captured in the closureenvironment.
The
use_cli_formatcondition field instructs whether to use cli (or rlang's fallbackmethod if cli is not installed) to format the error message atprint time.
In this case, themessage field may be a character vector ofheader and bullets. These are formatted at the last moment totake the context into account (starting position on the screenand indentation).
Seelocal_use_cli() for automatically setting this field inerrors thrown withabort() within your package.
These predicates check for a given type and whether the vector is"scalar", that is, of length 1.
In addition to the length check,is_string() andis_bool()returnFALSE if their input is missing. This is useful fortype-checking arguments, when your function expects a single stringor a singleTRUE orFALSE.
is_scalar_list(x)is_scalar_atomic(x)is_scalar_vector(x)is_scalar_integer(x)is_scalar_double(x)is_scalar_complex(x)is_scalar_character(x)is_scalar_logical(x)is_scalar_raw(x)is_string(x, string = NULL)is_scalar_bytes(x)is_bool(x)is_scalar_list(x)is_scalar_atomic(x)is_scalar_vector(x)is_scalar_integer(x)is_scalar_double(x)is_scalar_complex(x)is_scalar_character(x)is_scalar_logical(x)is_scalar_raw(x)is_string(x, string=NULL)is_scalar_bytes(x)is_bool(x)
x | object to be tested. |
string | A string to compare to |
type-predicates,bare-type-predicates
These helpers take two endpoints and return the sequence of allintegers within that interval. Forseq2_along(), the upperendpoint is taken from the length of a vector. Unlikebase::seq(), they return an empty vector if the starting point isa larger integer than the end point.
seq2(from, to)seq2_along(from, x)seq2(from, to)seq2_along(from, x)
from | The starting point of the sequence. |
to | The end point. |
x | A vector whose length is the end point. |
An integer vector containing a strictly increasingsequence.
seq2(2, 10)seq2(10, 2)seq(10, 2)seq2_along(10, letters)seq2(2,10)seq2(10,2)seq(10,2)seq2_along(10, letters)
This is equivalent tostats::setNames(), with more features andstricter argument checking.
set_names(x, nm = x, ...)set_names(x, nm= x,...)
x | Vector to name. |
nm,... | Vector of names, the same length as You can specify names in the following ways:
|
set_names() is stable and exported in purrr.
set_names(1:4, c("a", "b", "c", "d"))set_names(1:4, letters[1:4])set_names(1:4, "a", "b", "c", "d")# If the second argument is omitted a vector is named with itselfset_names(letters[1:5])# Alternatively you can supply a functionset_names(1:10, ~ letters[seq_along(.)])set_names(head(mtcars), toupper)# If the input vector is unnamed, it is first named after itself# before the function is applied:set_names(letters, toupper)# `...` is passed to the function:set_names(head(mtcars), paste0, "_foo")# If length 1, the second argument is recycled to the length of the first:set_names(1:3, "foo")set_names(list(), "")set_names(1:4, c("a","b","c","d"))set_names(1:4, letters[1:4])set_names(1:4,"a","b","c","d")# If the second argument is omitted a vector is named with itselfset_names(letters[1:5])# Alternatively you can supply a functionset_names(1:10,~ letters[seq_along(.)])set_names(head(mtcars), toupper)# If the input vector is unnamed, it is first named after itself# before the function is applied:set_names(letters, toupper)# `...` is passed to the function:set_names(head(mtcars), paste0,"_foo")# If length 1, the second argument is recycled to the length of the first:set_names(1:3,"foo")set_names(list(),"")
splice() is an advanced feature of dynamic dots. It is rarelyneeded but can solve performance issues in edge cases.
The splicing operator!!! operates both in values contexts likelist2() anddots_list(), and in metaprogramming contexts likeexpr(),enquos(), orinject(). While the end result looks thesame, the implementation is different and much more efficient inthe value cases. This difference in implementation may causeperformance issues for instance when going from:
xs <- list(2, 3)list2(1, !!!xs, 4)
to:
inject(list2(1, !!!xs, 4))
In the former case, the performant value-splicing is used. In thelatter case, the slow metaprogramming splicing is used.
A common practical case where this may occur is when code iswrapped inside a tidyeval context likedplyr::mutate(). In thiscase, the metaprogramming operator!!! will take over thevalue-splicing operator, causing an unexpected slowdown.
To avoid this in performance-critical code, usesplice() insteadof!!!:
# These both use the fast splicing:list2(1, splice(xs), 4)inject(list2(1, splice(xs), 4))
Note thatsplice() behaves differently than!!!. The splicing happenslater and is processed bylist2() ordots_list(). It does not work in anyother tidyeval context than these list collectors.
splice(x)is_spliced(x)is_spliced_bare(x)splice(x)is_spliced(x)is_spliced_bare(x)
x | A list or vector to splice non-eagerly. |
!!!The splice operator!!! implemented indynamic dotsinjects a list of arguments into a function call. It belongs to thefamily ofinjection operators and provides the samefunctionality asdo.call().
The two main cases for splice injection are:
Turning a list of inputs into distinct arguments. This isespecially useful with functions that take data in..., such asbase::rbind().
dfs <- list(mtcars, mtcars)inject(rbind(!!!dfs))
Injectingdefused expressions likesymbolised column names.
For tidyverse APIs, this second case is no longer as usefulsince dplyr 1.0 and theacross() operator.
!!! work?!!! does not work everywhere, you can only use it within certainspecial functions:
Functions takingdynamic dots likelist2().
Functions takingdefused anddata-masked arguments, which are dynamic bydefault.
Insideinject().
Most tidyverse functions support!!! out of the box. With basefunctions you need to useinject() to enable!!!.
Using the operator out of context may lead to incorrect results,seeWhat happens if I use injection operators out of context?.
Take a function likebase::rbind() that takes data in.... Thissort of functions takes a variable number of arguments.
df1 <- data.frame(x = 1)df2 <- data.frame(x = 2)rbind(df1, df2)#> x#> 1 1#> 2 2
Passing individual arguments is only possible for a fixed amount ofarguments. When the arguments are in a list whose length isvariable (and potentially very large), we need a programmaticapproach like the splicing syntax!!!:
dfs <- list(df1, df2)inject(rbind(!!!dfs))#> x#> 1 1#> 2 2
Becauserbind() is a base function we usedinject() toexplicitly enable!!!. However, many functions implementdynamic dots with!!! implicitly enabled out of the box.
tidyr::expand_grid(x = 1:2, y = c("a", "b"))#> # A tibble: 4 x 2#> x y #> <int> <chr>#> 1 1 a #> 2 1 b #> 3 2 a #> 4 2 bxs <- list(x = 1:2, y = c("a", "b"))tidyr::expand_grid(!!!xs)#> # A tibble: 4 x 2#> x y #> <int> <chr>#> 1 1 a #> 2 1 b #> 3 2 a #> 4 2 bNote how the expanded grid has the right column names. That'sbecause we spliced anamed list. Splicing causes each name of thelist to become an argument name.
tidyr::expand_grid(!!!set_names(xs, toupper))#> # A tibble: 4 x 2#> X Y #> <int> <chr>#> 1 1 a #> 2 1 b #> 3 2 a #> 4 2 b
Another usage for!!! is to injectdefused expressions intodata-maskeddots. However this usage is no longer a common pattern forprogramming with tidyverse functions and we recommend using otherpatterns if possible.
First, instead of using thedefuse-and-inject pattern with..., you can simply passthem on as you normally would. These two expressions are completelyequivalent:
my_group_by <- function(.data, ...) { .data %>% dplyr::group_by(!!!enquos(...))}# This equivalent syntax is preferredmy_group_by <- function(.data, ...) { .data %>% dplyr::group_by(...)}Second, more complex applications such astransformation patterns can be solved with theacross()operation introduced in dplyr 1.0. Say you want to take themean() of all expressions in.... Beforeacross(), you had todefuse the... expressions, wrap them in a call tomean(), andinject them insummarise().
my_mean <- function(.data, ...) { # Defuse dots and auto-name them exprs <- enquos(..., .named = TRUE) # Wrap the expressions in a call to `mean()` exprs <- purrr::map(exprs, ~ call("mean", .x, na.rm = TRUE)) # Inject them .data %>% dplyr::summarise(!!!exprs)}It is much easier to useacross() instead:
my_mean <- function(.data, ...) { .data %>% dplyr::summarise(across(c(...), ~ mean(.x, na.rm = TRUE)))}Take thisdynamic dots function:
n_args <- function(...) { length(list2(...))}Because it takes dynamic dots you can splice with!!! out of thebox.
n_args(1, 2)#> [1] 2n_args(!!!mtcars)#> [1] 11
Equivalently you could enable!!! explicitly withinject().
inject(n_args(!!!mtcars))#> [1] 11
While the result is the same, what is going on under the hood iscompletely different.list2() is a dots collector thatspecial-cases!!! arguments. On the other hand,inject()operates on the language and creates a function call containing asmany arguments as there are elements in the spliced list. If yousupply a list of size 1e6,inject() is creating one millionarguments before evaluation. This can be much slower.
xs <- rep(list(1), 1e6)system.time( n_args(!!!xs))#> user system elapsed#> 0.009 0.000 0.009system.time( inject(n_args(!!!xs)))#> user system elapsed#> 0.445 0.012 0.457
The same issue occurs when functions taking dynamic dots are calledinside a data-masking function likedplyr::mutate(). Themechanism that enables!!! injection in these arguments is thesame as ininject().
These accessors retrieve properties of frames on the call stack.The prefix indicates for which frame a property should be accessed:
From the current frame withcurrent_ accessors.
From a calling frame withcaller_ accessors.
From a matching frame withframe_ accessors.
The suffix indicates which property to retrieve:
_fn accessors return the function running in the frame.
_call accessors return the defused call with which the functionrunning in the frame was invoked.
_env accessors return the execution environment of the functionrunning in the frame.
current_call()current_fn()current_env()caller_call(n = 1)caller_fn(n = 1)caller_env(n = 1)frame_call(frame = caller_env())frame_fn(frame = caller_env())current_call()current_fn()current_env()caller_call(n=1)caller_fn(n=1)caller_env(n=1)frame_call(frame= caller_env())frame_fn(frame= caller_env())
n | The number of callers to go back. |
frame | A frame environment of a currently running function,as returned by |
Symbols are a kind ofdefused expression thatrepresent objects in environments.
sym() andsyms() take strings as input and turn them intosymbols.
data_sym() anddata_syms() create calls of the form.data$foo instead of symbols. Subsetting the.data pronounis more robust when you expect a data-variable. SeeThe data mask ambiguity.
Only tidy eval APIs support the.data pronoun. With base Rfunctions, use simple symbols created withsym() orsyms().
sym(x)syms(x)data_sym(x)data_syms(x)sym(x)syms(x)data_sym(x)data_syms(x)
x | For |
Forsym() andsyms(), a symbol or list of symbols. Fordata_sym() anddata_syms(), calls of the form.data$foo.
# Create a symbolsym("cyl")# Create a list of symbolssyms(c("cyl", "am"))# Symbolised names refer to variableseval(sym("cyl"), mtcars)# Beware of scoping issuesCyl <- "wrong"eval(sym("Cyl"), mtcars)# Data symbols are explicitly scoped in the data masktry(eval_tidy(data_sym("Cyl"), mtcars))# These can only be used with tidy eval functionstry(eval(data_sym("Cyl"), mtcars))# The empty string returns the missing argument:sym("")# This way sym() and as_string() are inverse of each other:as_string(missing_arg())sym(as_string(missing_arg()))# Create a symbolsym("cyl")# Create a list of symbolssyms(c("cyl","am"))# Symbolised names refer to variableseval(sym("cyl"), mtcars)# Beware of scoping issuesCyl<-"wrong"eval(sym("Cyl"), mtcars)# Data symbols are explicitly scoped in the data masktry(eval_tidy(data_sym("Cyl"), mtcars))# These can only be used with tidy eval functionstry(eval(data_sym("Cyl"), mtcars))# The empty string returns the missing argument:sym("")# This way sym() and as_string() are inverse of each other:as_string(missing_arg())sym(as_string(missing_arg()))
A backtrace captures the sequence of calls that lead to the currentfunction (sometimes called the call stack). Because of lazyevaluation, the call stack in R is actually a tree, which theprint() method for this object will reveal.
Users rarely need to calltrace_back() manually. Instead,signalling an error withabort() or setting upglobal_entrace()is the most common way to create backtraces when an error isthrown. Inspect the backtrace created for the most recent errorwithlast_error().
trace_length() returns the number of frames in a backtrace.
trace_back(top = NULL, bottom = NULL)trace_length(trace)trace_back(top=NULL, bottom=NULL)trace_length(trace)
top | The first frame environment to be included in thebacktrace. This becomes the top of the backtrace tree andrepresents the oldest call in the backtrace. This is needed in particular when you call If not supplied, the |
bottom | The last frame environment to be included in thebacktrace. This becomes the rightmost leaf of the backtrace treeand represents the youngest call in the backtrace. Set this when you would like to capture a backtrace without thecapture context. Can also be an integer that will be passed to |
trace | A backtrace created by |
# Trim backtraces automatically (this improves the generated# documentation for the rlang website and the same trick can be# useful within knitr documents):options(rlang_trace_top_env = current_env())f <- function() g()g <- function() h()h <- function() trace_back()# When no lazy evaluation is involved the backtrace is linear# (i.e. every call has only one child)f()# Lazy evaluation introduces a tree like structureidentity(identity(f()))identity(try(f()))try(identity(f()))# When printing, you can request to simplify this tree to only show# the direct sequence of calls that lead to `trace_back()`x <- try(identity(f()))xprint(x, simplify = "branch")# With a little cunning you can also use it to capture the# tree from within a base NSE functionx <- NULLwith(mtcars, {x <<- f(); 10})x# Restore default top env for next exampleoptions(rlang_trace_top_env = NULL)# When code is executed indirectly, i.e. via source or within an# RMarkdown document, you'll tend to get a lot of guff at the beginning# related to the execution environment:conn <- textConnection("summary(f())")source(conn, echo = TRUE, local = TRUE)close(conn)# To automatically strip this off, specify which frame should be# the top of the backtrace. This will automatically trim off calls# prior to that frame:top <- current_env()h <- function() trace_back(top)conn <- textConnection("summary(f())")source(conn, echo = TRUE, local = TRUE)close(conn)# Trim backtraces automatically (this improves the generated# documentation for the rlang website and the same trick can be# useful within knitr documents):options(rlang_trace_top_env= current_env())f<-function() g()g<-function() h()h<-function() trace_back()# When no lazy evaluation is involved the backtrace is linear# (i.e. every call has only one child)f()# Lazy evaluation introduces a tree like structureidentity(identity(f()))identity(try(f()))try(identity(f()))# When printing, you can request to simplify this tree to only show# the direct sequence of calls that lead to `trace_back()`x<- try(identity(f()))xprint(x, simplify="branch")# With a little cunning you can also use it to capture the# tree from within a base NSE functionx<-NULLwith(mtcars,{x<<- f();10})x# Restore default top env for next exampleoptions(rlang_trace_top_env=NULL)# When code is executed indirectly, i.e. via source or within an# RMarkdown document, you'll tend to get a lot of guff at the beginning# related to the execution environment:conn<- textConnection("summary(f())")source(conn, echo=TRUE, local=TRUE)close(conn)# To automatically strip this off, specify which frame should be# the top of the backtrace. This will automatically trim off calls# prior to that frame:top<- current_env()h<-function() trace_back(top)conn<- textConnection("summary(f())")source(conn, echo=TRUE, local=TRUE)close(conn)
try_fetch() establishes handlers for conditions of a given class("error","warning","message", ...). Handlers are functionsthat take a condition object as argument and are called when thecorresponding condition class has been signalled.
A condition handler can:
Recover from conditions with a value. In this case the computation ofexpr is aborted and the recovery value is returned fromtry_fetch(). Error recovery is useful when you don't wanterrors to abruptly interrupt your program but resume at thecatching site instead.
# Recover with the value 0try_fetch(1 + "", error = function(cnd) 0)
Rethrow conditions, e.g. usingabort(msg, parent = cnd).See theparent argument ofabort(). This is typically done toadd information to low-level errors about the high-level contextin which they occurred.
try_fetch(1 + "", error = function(cnd) abort("Failed.", parent = cnd))Inspect conditions, for instance to log data about warningsor errors. In this case, the handler must return thezap()sentinel to instructtry_fetch() to ignore (or zap) thatparticular handler. The next matching handler is called if any,and errors bubble up to the user if no handler remains.
log <- NULLtry_fetch(1 + "", error = function(cnd) { log <<- cnd zap()})WhereastryCatch() catches conditions (discarding any runningcode along the way) and then calls the handler,try_fetch() firstcalls the handler with the condition on top of the currentlyrunning code (fetches it where it stands) and then catches thereturn value. This is a subtle difference that has implicationsfor the debuggability of your functions. See the comparison withtryCatch() section below.
Another difference betweentry_fetch() and the base equivalent isthat errors are matched across chains, see theparent argument ofabort(). This is a useful property that makestry_fetch()insensitive to changes of implementation or context of evaluationthat cause a classed error to suddenly get chained to a contextualerror. Note that some chained conditions are not inherited, see the.inherit argument ofabort() orwarn(). In particular,downgraded conditions (e.g. from error to warning or from warningto message) are not matched across parents.
try_fetch(expr, ...)try_fetch(expr,...)
expr | An R expression. |
... | < |
A stack overflow occurs when a program keeps adding to itself untilthe stack memory (whose size is very limited unlike heap memory) isexhausted.
# A function that calls itself indefinitely causes stack overflowsf <- function() f()f()#> Error: C stack usage 9525680 is too close to the limit
Because memory is very limited when these errors happen, it is notpossible to call the handlers on the existing program stack.Instead, error conditions are first caught bytry_fetch() and onlythen error handlers are called. Catching the error interrupts theprogram up to thetry_fetch() context, which allows R to reclaimstack memory.
The practical implication is that error handlers should neverassume that the whole call stack is preserved. For instance atrace_back() capture might miss frames.
Note that error handlers are only run for stack overflows on R >=4.2. On older versions of R the handlers are simply not run. Thisis because these errors do not inherit from the classstackOverflowError before R 4.2. Consider usingtryCatch()instead with critical error handlers that need to capture allerrors on old versions of R.
tryCatch()try_fetch() generalisestryCatch() andwithCallingHandlers()in a single function. It reproduces the behaviour of both callingand exiting handlers depending on the return value of the handler.If the handler returns thezap() sentinel, it is taken as acalling handler that declines to recover from a condition.Otherwise, it is taken as an exiting handler which returns a valuefrom the catching site.
The important difference betweentryCatch() andtry_fetch() isthat the program inexpr is still fully running when an errorhandler is called. Because the call stack is preserved, this makesit possible to capture a full backtrace from within the handler,e.g. when rethrowing the error withabort(parent = cnd).Technically,try_fetch() is more similar to (and implemented ontop of)base::withCallingHandlers() thantryCatch().
These type predicates aim to make type testing in R moreconsistent. They are wrappers aroundbase::typeof(), so operateat a level beneath S3/S4 etc.
is_list(x, n = NULL)is_atomic(x, n = NULL)is_vector(x, n = NULL)is_integer(x, n = NULL)is_double(x, n = NULL, finite = NULL)is_complex(x, n = NULL, finite = NULL)is_character(x, n = NULL)is_logical(x, n = NULL)is_raw(x, n = NULL)is_bytes(x, n = NULL)is_null(x)is_list(x, n=NULL)is_atomic(x, n=NULL)is_vector(x, n=NULL)is_integer(x, n=NULL)is_double(x, n=NULL, finite=NULL)is_complex(x, n=NULL, finite=NULL)is_character(x, n=NULL)is_logical(x, n=NULL)is_raw(x, n=NULL)is_bytes(x, n=NULL)is_null(x)
x | Object to be tested. |
n | Expected length of a vector. |
finite | Whether all values of the vector are finite. Thenon-finite values are |
Compared to base R functions:
The predicates for vectors include then argument forpattern-matching on the vector length.
Unlikeis.atomic() in R < 4.4.0,is_atomic() does not returnTRUE forNULL. Starting in R 4.4.0is.atomic(NULL) returns FALSE.
Unlikeis.vector(),is_vector() tests if an object is anatomic vector or a list.is.vector checks for the presence ofattributes (other than name).
bare-type-predicatesscalar-type-predicates
The atomic vector constructors are equivalent toc() but:
They allow you to be more explicit about the outputtype. Implicit coercions (e.g. from integer to logical) followthe rules described invector-coercion.
They usedynamic dots.
lgl(...)int(...)dbl(...)cpl(...)chr(...)bytes(...)lgl(...)int(...)dbl(...)cpl(...)chr(...)bytes(...)
... | Components of the new vector. Bare lists and explicitlyspliced lists are spliced. |
All the abbreviated constructors such aslgl() will probably bemoved to the vctrs package at some point. This is why they aremarked as questioning.
Automatic splicing is soft-deprecated and will trigger a warningin a future version. Please splice explicitly with!!!.
# These constructors are like a typed version of c():c(TRUE, FALSE)lgl(TRUE, FALSE)# They follow a restricted set of coercion rules:int(TRUE, FALSE, 20)# Lists can be spliced:dbl(10, !!! list(1, 2L), TRUE)# They splice names a bit differently than c(). The latter# automatically composes inner and outer names:c(a = c(A = 10), b = c(B = 20, C = 30))# On the other hand, rlang's constructors use the inner names and issue a# warning to inform the user that the outer names are ignored:dbl(a = c(A = 10), b = c(B = 20, C = 30))dbl(a = c(1, 2))# As an exception, it is allowed to provide an outer name when the# inner vector is an unnamed scalar atomic:dbl(a = 1)# Spliced lists behave the same way:dbl(!!! list(a = 1))dbl(!!! list(a = c(A = 1)))# bytes() accepts integerish inputsbytes(1:10)bytes(0x01, 0xff, c(0x03, 0x05), list(10, 20, 30L))# These constructors are like a typed version of c():c(TRUE,FALSE)lgl(TRUE,FALSE)# They follow a restricted set of coercion rules:int(TRUE,FALSE,20)# Lists can be spliced:dbl(10,!!! list(1,2L),TRUE)# They splice names a bit differently than c(). The latter# automatically composes inner and outer names:c(a= c(A=10), b= c(B=20, C=30))# On the other hand, rlang's constructors use the inner names and issue a# warning to inform the user that the outer names are ignored:dbl(a= c(A=10), b= c(B=20, C=30))dbl(a= c(1,2))# As an exception, it is allowed to provide an outer name when the# inner vector is an unnamed scalar atomic:dbl(a=1)# Spliced lists behave the same way:dbl(!!! list(a=1))dbl(!!! list(a= c(A=1)))# bytes() accepts integerish inputsbytes(1:10)bytes(0x01,0xff, c(0x03,0x05), list(10,20,30L))
Get key/value from a weak reference object
wref_key(x)wref_value(x)wref_key(x)wref_value(x)
x | A weak reference object. |
is_weakref() andnew_weakref().
zap() creates a sentinel object that indicates that an objectshould be removed. For instance, named zaps instructenv_bind()andcall_modify() to remove those objects from the environment orthe call.
The advantage of zap objects is that they unambiguously signal theintent of removing an object. Sentinels likeNULL ormissing_arg() are ambiguous because they represent valid Robjects.
zap()is_zap(x)zap()is_zap(x)
x | An object to test. |
# Create one zap object:zap()# Create a list of zaps:rep(list(zap()), 3)rep_named(c("foo", "bar"), list(zap()))# Create one zap object:zap()# Create a list of zaps:rep(list(zap()),3)rep_named(c("foo","bar"), list(zap()))
There are a number of situations where R creates source references:
Reading R code from a file withsource() andparse() might savesource references inside calls tofunction and{.
sys.call() includes a source reference if possible.
Creating a closure stores the source reference from the call tofunction, if any.
These source references take up space and might cause a number ofissues.zap_srcref() recursively walks through expressions andfunctions to remove all source references.
zap_srcref(x)zap_srcref(x)
x | An R object. Functions and calls are walked recursively. |