Movatterモバイル変換


[0]ホーム

URL:


Version:1.1.6
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.
License:MIT + file LICENSE
ByteCompile:true
Biarch:true
Depends:R (≥ 3.5.0)
Imports:utils
Suggests:cli (≥ 3.1.0), covr, crayon, desc, fs, glue, knitr,magrittr, methods, pillar, pkgload, rmarkdown, stats, testthat(≥ 3.2.0), tibble, usethis, vctrs (≥ 0.2.3), withr
Enhances:winch
Encoding:UTF-8
RoxygenNote:7.3.2
URL:https://rlang.r-lib.org,https://github.com/r-lib/rlang
BugReports:https://github.com/r-lib/rlang/issues
Config/build/compilation-database:true
Config/testthat/edition:3
Config/Needs/website:dplyr, tidyverse/tidytemplate
NeedsCompilation:yes
Packaged:2025-04-10 09:25:27 UTC; lionel
Author: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 <lionel@posit.co>
Repository:CRAN
Date/Publication:2025-04-11 08:40:10 UTC

rlang: Functions for Base Types and Core R and 'Tidyverse' Features

Description

logo

A toolbox for working with base types, core R features like the condition system, and core 'Tidyverse' features like tidy evaluation.

Author(s)

Maintainer: Lionel Henrylionel@posit.co

Authors:

Other contributors:

See Also

Useful links:


DeprecatedUQ() andUQS() operators

Description

[Deprecated]These operators are deprecated in favour of!! and!!!.

Usage

UQ(x)UQS(x)

Signal an error, warning, or message

Description

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:

Certain components of condition messages are formatted with unicodesymbols and terminal colours by default. These aspects can becustomised, seeCustomising condition messages.

Usage

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)

Arguments

message

The message to display, formatted as abulletedlist. The first element is displayed as analert bulletprefixed with! by default. Elements named"*","i","v","x", and"!" are formatted as regular, info, success,failure, and error bullets respectively. SeeFormatting messages with clifor more about bulleted messaging.

If a message is not supplied, it is expected that the message isgeneratedlazily throughcnd_header() andcnd_body()methods. In that case,class must be supplied. Onlyinform()allows empty messages as it is occasionally useful to build useroutput incrementally.

If a function, it is stored in theheader field of the errorcondition. This acts as acnd_header() method that is invokedlazily when the error message is displayed.

class

Subclass of the condition.

...

Additional data to be stored in the condition object.If you supply condition fields, you should usually provide aclass argument. You may consider prefixing condition fieldswith the name of your package or organisation to prevent namecollisions.

call

The execution environment of a currently runningfunction, e.g.call = caller_env(). The corresponding functioncall is retrieved and mentioned in error messages as the sourceof the error.

You only need to supplycall when throwing a condition from ahelper function which wouldn't be relevant to mention in themessage.

Can also beNULL or adefused function call torespectively not display any call or hard-code a code to display.

For more information about error calls, seeIncluding function calls in error messages.

body,footer

Additional bullets.

trace

Atrace object created bytrace_back().

parent

Supplyparent when you rethrow an error from acondition handler (e.g. withtry_fetch()).

  • Ifparent is a condition object, achained error iscreated, which is useful when you want to enhance an error withmore details, while still retaining the original information.

  • Ifparent isNA, it indicates an unchained rethrow, whichis useful when you want to take ownership over an error andrethrow it with a custom message that better fits thesurrounding context.

    Technically, supplyingNA letsabort() know it is calledfrom a condition handler. This helps it create simplerbacktraces where the condition handling context is hidden bydefault.

For more information about error calls, seeIncluding contextual information with error chains.

use_cli_format

Whether to formatmessage lazily usingcli if available. This results inprettier and more accurate formatting of messages. Seelocal_use_cli() to set this condition field by default in yourpackage namespace.

If set toTRUE,message should be a character vector ofindividual and unformatted lines. Any newline character"\\n"already present inmessage is reformatted by cli's paragraphformatter. SeeFormatting messages with cli.

.inherit

Whether the condition inherits fromparentaccording tocnd_inherits() andtry_fetch(). By default,parent conditions of higher severity are not inherited. Forinstance an error chained to a warning is not inherited to avoidunexpectedly catching an error downgraded to a warning.

.internal

IfTRUE, a footer bullet is added tomessageto let the user know that the error is internal and that theyshould report it to the package authors. This argument isincompatible withfooter.

.file

A connection or a string specifying where to print themessage. The default depends on the context, see thestdout vsstderr section.

.frame

The throwing context. Used as default for.trace_bottom, and to determine the internal package to mentionin internal errors when.internal isTRUE.

.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(tryCatch(),try_fetch(),abort(), etc.) are hidden bydefault. Defaults tocall if it is an environment, or.frameotherwise. Without effect iftrace is supplied.

.subclass

[Deprecated] This argumentwas renamed toclass in rlang 0.4.2 for consistency with ourconventions for class constructors documented inhttps://adv-r.hadley.nz/s3.html#s3-subclassing.

.frequency

How frequently should the warning or message bedisplayed? By default ("always") it is displayed at eachtime. If"regularly", it is displayed once every 8 hours. If"once", it is displayed once per session.

.frequency_id

A unique identifier for the warning ormessage. This is used when.frequency is supplied to recogniserecurring conditions. This argument must be supplied if.frequency is not set to"always".

id

The identifying string of the condition that was suppliedas.frequency_id towarn() orinform().

Details

Error prefix

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:

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().

Backtrace

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.

Muffling and silencing conditions

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:

When set to quiet, the message is not displayed and the conditionis not signalled.

stdout andstderr

By 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:

These exceptions ensure consistency of behaviour in interactive andnon-interactive sessions, and when sinks are active.

See Also

Examples

# 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 occured in `my_function()`try(g())}

Test for missing values

Description

[Questioning]

are_na() checks for missing values in a vector and is equivalenttobase::is.na(). It is a vectorised predicate, meaning that itsoutput is always the same length as its input. On the other hand,is_na() is a scalar predicate and always returns a scalarboolean,TRUE orFALSE. If its input is not scalar, it returnsFALSE. Finally, there are typed versions that check forparticularmissing types.

Usage

are_na(x)is_na(x)is_lgl_na(x)is_int_na(x)is_dbl_na(x)is_chr_na(x)is_cpl_na(x)

Arguments

x

An object to test

Details

The scalar predicates accept non-vector inputs. They are equivalenttois_null() in that respect. In contrast the vectorisedpredicateare_na() requires a vector input since it is definedover vector values.

Life cycle

These functions might be moved to the vctrs package at somepoint. This is why they are marked as questioning.

Examples

# are_na() is vectorised and works regardless of the typeare_na(c(1, 2, NA))are_na(c(1L, NA, 3L))# is_na() checks for scalar input and works for all typesis_na(NA)is_na(na_dbl)is_na(character(0))# There are typed versions as well:is_lgl_na(NA)is_lgl_na(na_dbl)

Match an argument to a character vector

Description

This is equivalent tobase::match.arg() with a few differences:

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.

Usage

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())

Arguments

arg

A symbol referring to an argument accepting strings.

values

A character vector of possible values thatarg can take.

...

These dots are for future extensions and must be empty.

multiple

Whetherarg may contain zero or several values.

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.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

arg_nm

Same aserror_arg.

Value

The string supplied toarg.

See Also

check_required()

Examples

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"))

Argument type: data-masking

Description

This page describes the⁠<data-masking>⁠ argument modifier whichindicates that the argument uses tidy evaluation withdata masking.If you've never heard of tidy evaluation before, start withvignette("programming", package = "dplyr").

Key terms

The primary motivation for tidy evaluation in tidyverse packages is that itprovidesdata masking, which blurs the distinction between two types ofvariables:

General usage

Data masking allows you to refer to variables in the "current" data frame(usually supplied in the.data argument), without any other prefix.It's what allows you to type (e.g.)filter(diamonds, x == 0 & y == 0 & z == 0)instead ofdiamonds[diamonds$x == 0 & diamonds$y == 0 & diamonds$z == 0, ].

Indirection in wrapper functions

The main challenge of data masking arises when you introduce someindirection, i.e. instead of directly typing the name of a variable youwant to supply it in a function argument or character vector.

There are two main cases:

Dot-dot-dot (...)

When this modifier is applied to..., there is one other useful techniquewhich solves the problem of creating a new variable with a name supplied bythe user. Use the interpolation syntax from the glue package:"{var}" := expression. (Note the use of⁠:=⁠ instead of= to enable this syntax).

var_name <- "l100km"mtcars %>% mutate("{var_name}" := 235 / mpg)

Note that... automatically provides indirection, so you can use it as is(i.e. without embracing) inside a function:

grouped_mean <- function(df, var, ...) {  df %>%    group_by(...) %>%    summarise(mean = mean({{ var }}))}

See Also


Helper for consistent documentation of empty dots

Description

Use⁠@inheritParams rlang::args_dots_empty⁠ in your packageto consistently document... that must be empty.

Arguments

...

These dots are for future extensions and must be empty.


Helper for consistent documentation of used dots

Description

Use⁠@inheritParams rlang::args_dots_used⁠ in your packageto consistently document... that must be used.

Arguments

...

Arguments passed to methods.


Documentation anchor for error arguments

Description

Use⁠@inheritParams rlang::args_error_context⁠ in your package todocumentarg andcall arguments (or equivalently their prefixedversionserror_arg anderror_call).

Arguments

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.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

error_call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.


Convert object to a box

Description

Usage

as_box(x, class = NULL)as_box_if(.x, .p, .class = NULL, ...)

Arguments

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,class is passed tonew_box().

.p

A predicate function.

...

Arguments passed to.p.


Transform to a closure

Description

as_closure() is likeas_function() but also wraps primitivefunctions inside closures. Some special control flow primitiveslikeif,for, orbreak can't be wrapped and will cause anerror.

Usage

as_closure(x, env = caller_env())

Arguments

x

A function or formula.

If afunction, it is used as is.

If aformula, e.g.~ .x + 2, it is converted to a functionwith up to two arguments:.x (single argument) or.x and.y(two arguments). The. placeholder can be used instead of.x.This allows you to create very compact anonymous functions (lambdas) with upto two inputs. Functions created from formulas have a specialclass. Useis_lambda() to test for it.

If astring, the function is looked up inenv. Note thatthis interface is strictly for user convenience because of thescoping issues involved. Package developers should avoidsupplying functions by name and instead supply them by value.

env

Environment in which to fetch the function in casexis a string.

Examples

# Primitive functions are regularised as closuresas_closure(list)as_closure("list")# Operators have `.x` and `.y` as arguments, just like lambda# functions created with the formula syntax:as_closure(`+`)as_closure(`~`)

Create a data mask

Description

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.

Usage

as_data_mask(data)as_data_pronoun(data)new_data_mask(bottom, top = bottom)

Arguments

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 suppliedtop, thismust be an environmentthat you own, i.e. that you have created yourself.

top

The last environment of the data mask. If the data maskis only one environment deep,top should be the same asbottom.

Thismust be an environment that you own, i.e. that you havecreated yourself. The parent oftop will be changed by the tidyeval engine and should be considered undetermined. Never makeassumption about the parent oftop.

Value

A data mask that you can supply toeval_tidy().

Why build a data mask?

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:

Building your own data mask

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:

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.

Top and bottom of data mask

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:

Consequently, all masking data should be contained between thebottom and top environment of the data mask.

Examples

# 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)

Coerce to an environment

Description

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()).

Usage

as_environment(x, parent = NULL)

Arguments

x

An object to coerce.

parent

A parent environment,empty_env() by default. Thisargument is only used whenx is data actually coerced to anenvironment (as opposed to data representing an environment, likeNULL representing the empty environment).

Details

Ifx is an environment andparent is notNULL, theenvironment is duplicated before being set a new parent. The returnvalue is therefore a different environment thanx.

Examples

# 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)

Convert to function

Description

as_function() transforms a one-sided formula into a function.This powers the lambda syntax in packages like purrr.

Usage

as_function(  x,  env = global_env(),  ...,  arg = caller_arg(x),  call = caller_env())is_lambda(x)

Arguments

x

A function or formula.

If afunction, it is used as is.

If aformula, e.g.~ .x + 2, it is converted to a functionwith up to two arguments:.x (single argument) or.x and.y(two arguments). The. placeholder can be used instead of.x.This allows you to create very compact anonymous functions (lambdas) with upto two inputs. Functions created from formulas have a specialclass. Useis_lambda() to test for it.

If astring, the function is looked up inenv. Note thatthis interface is strictly for user convenience because of thescoping issues involved. Package developers should avoidsupplying functions by name and instead supply them by value.

env

Environment in which to fetch the function in casexis a string.

...

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.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

Examples

f <- as_function(~ .x + 1)f(10)g <- as_function(~ -1 * .)g(4)h <- as_function(~ .x - .y)h(6, 3)# Functions created from a formula have a special class:is_lambda(f)is_lambda(as_function(function() "foo"))

Create a default name for an R object

Description

as_label() transforms R objects into a short, human-readabledescription. You can use labels to:

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().

Usage

as_label(x)

Arguments

x

An object.

Transformation to string

See Also

as_name() for transforming symbols back to a stringdeterministically.

Examples

# 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)

Extract names from symbols

Description

as_name() convertssymbols to character strings. Theconversion is deterministic. That is, the roundtripsymbol -> name -> symbol always gives the same result.

Usage

as_name(x)

Arguments

x

A string or symbol, possibly wrapped in aquosure.If a string, the attributes are removed, if any.

Details

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).

Value

A character vector of length 1.

See Also

as_label() for converting any object to a single stringsuitable as a label.as_string() for a lower-level version thatdoesn't unwrap quosures.

Examples

# 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))

Cast symbol to string

Description

as_string() convertssymbols to character strings.

Usage

as_string(x)

Arguments

x

A string or symbol. If a string, the attributes areremoved, if any.

Value

A character vector of length 1.

Unicode tags

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:

Because it reencodes the ASCII unicode tags to their UTF-8representation, the string -> symbol -> string roundtrip ismore stable withas_string().

See Also

as_name() for a higher-level variant ofas_string()that automatically unwraps quosures.

Examples

# 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))

Coerce to a character vector and attempt encoding conversion

Description

[Experimental]

Unlike specifying theencoding argument inas_string() andas_character(), which is only declarative, these functionsactually attempt to convert the encoding of their input. There aretwo possible cases:

When translating to UTF-8, the strings are parsed for serialisedunicode points (e.g. strings looking like "U+xxxx") withchr_unserialise_unicode(). This helps to alleviate the effects ofcharacter-to-symbol-to-character roundtrips on systems withnon-UTF-8 native encoding.

Usage

as_utf8_character(x)

Arguments

x

An object to coerce.

Examples

# Let's create a string marked as UTF-8 (which is guaranteed by the# Unicode escaping in the string):utf8 <- "caf\uE9"Encoding(utf8)charToRaw(utf8)

Bare type predicates

Description

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.

Usage

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)

Arguments

x

Object to be tested.

n

Expected length of a vector.

Details

See Also

type-predicates,scalar-type-predicates


Box a value

Description

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.

Usage

new_box(.x, class = NULL, ...)is_box(x, class = NULL)unbox(box)

Arguments

class

Fornew_box(), an additional class for theboxed value (in addition torlang_box). Foris_box(), a classor vector of classes passed toinherits_all().

...

Additional attributes passed tobase::structure().

x,.x

An R object.

box

A boxed value to unbox.

Examples

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")

Human readable memory sizes

Description

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'.

Note: Abytes() constructor will be exported soon.

Usage

as_bytes(x)parse_bytes(x)

Arguments

x

A numeric or character vector. Character representations can useshorthand sizes (see examples).

Details

These memory sizes are always assumed to be base 1000, rather than 1024.

Examples

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")))

Create a call

Description

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:

See section below for the difference betweencall2() and the baseconstructors.

Usage

call2(.fn, ..., .ns = NULL)

Arguments

.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.fn. Must be a stringor symbol.

Difference with base constructors

call2() is more flexible thanbase::call():

Caveats of inlining objects in calls

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().

See Also

call_modify()

Examples

# 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 = )

Extract arguments from a call

Description

Extract arguments from a call

Usage

call_args(call)call_args_names(call)

Arguments

call

A defused call.

Value

A named list of arguments.

See Also

fn_fmls() andfn_fmls_names()

Examples

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)

Extract function from a call

Description

[Deprecated]Deprecated in rlang 0.4.11.

Usage

call_fn(call, env = caller_env())

Arguments

call,env

[Deprecated]


Inspect a call

Description

This function is a wrapper aroundbase::match.call(). It returnsits own function call.

Usage

call_inspect(...)

Arguments

...

Arguments to display in the returned call.

Examples

# 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)

Match supplied arguments to function definition

Description

call_match() is likematch.call() with these differences:

Usage

call_match(  call = NULL,  fn = NULL,  ...,  defaults = FALSE,  dots_env = NULL,  dots_expand = TRUE)

Arguments

call

A call. The arguments will be matched tofn.

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 ifcallincludes..., the forwarded dots are matched to numbered dots(e.g...1,..2, etc). By default this is set to the emptyenvironment which means that... expands to nothing.

dots_expand

IfFALSE, arguments passed through... willnot be spliced intocall. Instead, they are gathered in apairlist and assigned to an argument named.... Gathering dotsarguments is useful if you need to separate them from the othernamed arguments.

Note that the resulting call is not meant to be evaluated since Rdoes not support passing dots through a named argument, even ifnamed"...".

Inference from the call stack

Whencall is not supplied, it is inferred from the call stackalong withfn anddots_env.

Ifcall is supplied, then you must supplyfn as well. Alsoconsider supplyingdots_env as it is set to the empty environmentwhen not inferred.

Examples

# `call_match()` supports matching missing arguments to their# defaultsfn <- function(x = "default") fncall_match(quote(fn()), fn)call_match(quote(fn()), fn, defaults = TRUE)

Modify the arguments of a call

Description

If you are working with a user-supplied call, make sure thearguments are standardised withcall_match() beforemodifying the call.

Usage

call_modify(  .call,  ...,  .homonyms = c("keep", "first", "last", "error"),  .standardise = NULL,  .env = caller_env())

Arguments

.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. Usezap()to remove arguments. Empty arguments are preserved.

.homonyms

How to treat arguments with the same name. Thedefault,"keep", preserves these arguments. Set.homonyms to"first" to only keep the first occurrences, to"last" to keepthe last occurrences, and to"error" to raise an informativeerror and indicate what arguments have duplicated names.

.standardise,.env

Deprecated as of rlang 0.3.0. Pleasecallcall_match() manually.

Value

A quosure if.call is a quosure, a call otherwise.

Examples

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")

Extract function name or namespace of a call

Description

call_name() andcall_ns() extract the function name ornamespace ofsimple calls as a string. They returnNULL forcomplex calls.

Theis_call_simple() predicate helps you determine whether a callis simple. There are two invariants you can count on:

  1. Ifis_call_simple(x) returnsTRUE,call_name(x) returns astring. Otherwise it returnsNULL.

  2. Ifis_call_simple(x, ns = TRUE) returnsTRUE,call_ns()returns a string. Otherwise it returnsNULL.

Usage

call_name(call)call_ns(call)is_call_simple(x, ns = NULL)

Arguments

call

A defused call.

x

An object to test.

ns

Whether call is namespaced. IfNULL,is_call_simple()is insensitive to namespaces. IfTRUE,is_call_simple()detects namespaced calls. IfFALSE, it detects unnamespacedcalls.

Value

The function name or namespace as a string, orNULL ifthe call is not named or namespaced.

Examples

# 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()))

Standardise a call

Description

[Deprecated]

Deprecated in rlang 0.4.11 in favour ofcall_match().call_standardise() was designed for call wrappers that include anenvironment like formulas or quosures. The function definition wasplucked from that environment. However in practice it is rare touse it with wrapped calls, and then it's easy to forget to supplythe environment. For these reasons, we have designedcall_match()as a simpler wrapper aroundmatch.call().

This is essentially equivalent tobase::match.call(), but withexperimental handling of primitive functions.

Usage

call_standardise(call, env = caller_env())

Arguments

call,env

[Deprecated]

Value

A quosure ifcall is a quosure, a raw call otherwise.


Find the caller argument for error messages

Description

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.

Arguments

arg

An argument name in the current function.

Examples

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))

Catch a condition

Description

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.

Usage

catch_cnd(expr, classes = "condition")

Arguments

expr

Expression to be evaluated with a catching conditionhandler.

classes

A character vector of condition classes to catch. Bydefault, catches all conditions.

Value

A condition if any was signalled,NULL otherwise.

Examples

catch_cnd(10)catch_cnd(abort("an error"))catch_cnd(signal("my_condition", message = "a condition"))

Check that dots are empty

Description

... 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.

Usage

check_dots_empty(  env = caller_env(),  error = NULL,  call = caller_env(),  action = abort)

Arguments

env

Environment in which to look for....

error

An optional error handler passed totry_fetch(). Usethis e.g. to demote an error into a warning.

call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

action

[Deprecated]

Details

In packages, document... with this standard tag:

 @inheritParams rlang::args_dots_empty

See Also

Other dots checking functions:check_dots_unnamed(),check_dots_used()

Examples

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)

Check that dots are empty (low level variant)

Description

check_dots_empty0() is a more efficient version ofcheck_dots_empty() with a slightly different interface. Insteadof inspecting the current environment for dots, it directly takes.... It is only meant for very low level functions where acouple microseconds make a difference.

Usage

check_dots_empty0(..., call = caller_env())

Arguments

...

Dots which should be empty.


Check that all dots are unnamed

Description

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.

Usage

check_dots_unnamed(  env = caller_env(),  error = NULL,  call = caller_env(),  action = abort)

Arguments

env

Environment in which to look for....

error

An optional error handler passed totry_fetch(). Usethis e.g. to demote an error into a warning.

call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

action

[Deprecated]

See Also

Other dots checking functions:check_dots_empty(),check_dots_used()

Examples

f <- function(..., foofy = 8) {  check_dots_unnamed()  c(...)}f(1, 2, 3, foofy = 4)try(f(1, 2, 3, foof = 4))

Check that all dots have been used

Description

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.

Usage

check_dots_used(  env = caller_env(),  call = caller_env(),  error = NULL,  action = deprecated())

Arguments

env

Environment in which to look for... and to set up handler.

call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

error

An optional error handler passed totry_fetch(). Usethis e.g. to demote an error into a warning.

action

[Deprecated]

Details

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().

See Also

Other dots checking functions:check_dots_empty(),check_dots_unnamed()

Examples

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 that arguments are mutually exclusive

Description

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.

Usage

check_exclusive(..., .require = TRUE, .frame = caller_env(), .call = .frame)

Arguments

...

Function arguments.

.require

Whether at least one argument must be supplied.

.frame

Environment where the arguments in... are defined.

.call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

Value

The supplied argument name as a string. If.require isFALSE and no argument is supplied, the empty string"" isreturned.

Examples

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()

Check that argument is supplied

Description

Throws an error ifx is missing.

Usage

check_required(x, arg = caller_arg(x), call = caller_env())

Arguments

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.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

See Also

arg_match()

Examples

f <- function(x)  {  check_required(x)}# Fails because `x` is not suppliedtry(f())# Succeedsf(NULL)

Create a child environment

Description

[Deprecated]

env() now supports creating child environments, please use itinstead.

Usage

child_env(.parent, ...)

Translate unicode points to UTF-8

Description

[Experimental]

For historical reasons, R translates strings to the native encodingwhen they are converted to symbols. This string-to-symbolconversion is not a rare occurrence and happens for instance to thenames of a list of arguments converted to a call bydo.call().

If the string contains unicode characters that cannot berepresented in the native encoding, R serialises those as an ASCIIsequence representing the unicode point. This is why Windows userswith western locales often see strings looking like⁠<U+xxxx>⁠. Toalleviate some of the pain, rlang parses strings and looks forserialised unicode points to translate them back to the properUTF-8 representation. This transformation occurs automatically infunctions likeenv_names() and can be manually triggered withas_utf8_character() andchr_unserialise_unicode().

Usage

chr_unserialise_unicode(chr)

Arguments

chr

A character vector.

Life cycle

This function is experimental.

Examples

ascii <- "<U+5E78>"chr_unserialise_unicode(ascii)identical(chr_unserialise_unicode(ascii), "\u5e78")

Create a condition object

Description

These constructors create subclassed conditions, the objects thatpower the error, warning, and message system in R.

Usecnd_signal() to emit the relevant signal for a particularcondition class.

Usage

cnd(class, ..., message = "", call = NULL, use_cli_format = NULL)error_cnd(  class = NULL,  ...,  message = "",  call = NULL,  trace = NULL,  parent = NULL,  use_cli_format = NULL)warning_cnd(  class = NULL,  ...,  message = "",  call = NULL,  use_cli_format = NULL)message_cnd(  class = NULL,  ...,  message = "",  call = NULL,  use_cli_format = NULL)

Arguments

class

The condition subclass.

...

<dynamic> Named data fields stored insidethe condition object.

message

A default message to inform the user about thecondition when it is signalled.

call

A function call to be included in the error message.If an execution environment of a running function, thecorresponding function call is retrieved.

use_cli_format

Whether to use the cli package to formatmessage. Seelocal_use_cli().

trace

Atrace object created bytrace_back().

parent

A parent condition object.

See Also

cnd_signal(),try_fetch().

Examples

# Create a condition inheriting only from the S3 class "foo":cnd <- cnd("foo")# Signal the condition to potential handlers. Since this is a bare# condition the signal has no effect if no handlers are set up:cnd_signal(cnd)# When a relevant handler is set up, the signal transfers control# to the handlerwith_handlers(cnd_signal(cnd), foo = function(c) "caught!")tryCatch(cnd_signal(cnd), foo = function(c) "caught!")

Does a condition or its ancestors inherit from a class?

Description

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().

Usage

cnd_inherits(cnd, class)

Arguments

cnd

A condition to test.

class

A class passed toinherits().

Capture an error withcnd_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"

Build an error message from parts

Description

cnd_message() assembles an error message from three generics:

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.

Usage

cnd_message(cnd, ..., inherit = TRUE, prefix = FALSE)cnd_header(cnd, ...)cnd_body(cnd, ...)cnd_footer(cnd, ...)

Arguments

cnd

A condition object.

...

Arguments passed to methods.

inherit

Wether to include parent messages. Parent messagesare printed with a "Caused by error:" prefix, even ifprefix isFALSE.

prefix

Whether to print the full message, including thecondition prefix (⁠Error:⁠,⁠Warning:⁠,⁠Message:⁠, or⁠Condition:⁠). The prefix mentions thecall field if present,and thesrcref info if present. Ifcnd has aparent field(i.e. the condition is chained), the parent messages are includedin the message with a⁠Caused by⁠ prefix.

Overriding header, body, and footer methods

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.


Muffle a condition

Description

Unlikeexiting() handlers,calling() handlers must be explicitthat they have handled a condition to stop it from propagating toother handlers. Usecnd_muffle() within a calling handler (or asa calling handler, see examples) to prevent any other handlers frombeing called for that condition.

Usage

cnd_muffle(cnd)

Arguments

cnd

A condition to muffle.

Value

Ifcnd is mufflable,cnd_muffle() jumps to the mufflerestart and doesn't return. Otherwise, it returnsFALSE.

Mufflable conditions

Most conditions signalled by base R are muffable, although the nameof the restart varies. cnd_muffle() will automatically call thecorrect restart for you. It is compatible with the followingconditions:

If you callcnd_muffle() with a condition that is not mufflableyou will cause a new error to be signalled.

Examples

fn <- function() {  inform("Beware!", "my_particular_msg")  inform("On your guard!")  "foobar"}# Let's install a muffling handler for the condition thrown by `fn()`.# This will suppress all `my_particular_wng` warnings but let other# types of warnings go through:with_handlers(fn(),  my_particular_msg = calling(function(cnd) {    inform("Dealt with this particular message")    cnd_muffle(cnd)  }))# Note how execution of `fn()` continued normally after dealing# with that particular message.# cnd_muffle() can also be passed to with_handlers() as a calling# handler:with_handlers(fn(),  my_particular_msg = calling(cnd_muffle))

Signal a condition object

Description

cnd_signal() takes a condition as argument and emits thecorresponding signal. The type of signal depends on the class ofthe condition:

Usage

cnd_signal(cnd, ...)

Arguments

cnd

A condition object (seecnd()). IfNULL,cnd_signal() returns without signalling a condition.

...

These dots are for future extensions and must be empty.

See Also

Examples

# 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))

What type is a condition?

Description

Usecnd_type() to check what type a condition is.

Usage

cnd_type(cnd)

Arguments

cnd

A condition object.

Value

A string, either"condition","message","warning","error" or"interrupt".

Examples

cnd_type(catch_cnd(abort("Abort!")))cnd_type(catch_cnd(interrupt()))

Advanced defusal operators

Description

These advanced operatorsdefuse R expressions.expr(),enquo(), andenquos() are sufficient for mostpurposes but rlang provides these other operations, either forcompleteness or because they are useful to experts.

Usage

enexpr(arg)exprs(  ...,  .named = FALSE,  .ignore_empty = c("trailing", "none", "all"),  .unquote_names = TRUE)enexprs(  ...,  .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)ensym(arg)ensyms(  ...,  .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)quo(expr)quos(  ...,  .named = FALSE,  .ignore_empty = c("trailing", "none", "all"),  .unquote_names = TRUE)enquo0(arg)enquos0(...)

Arguments

arg

An unquoted argument name. The expressionsupplied to that argument is defused and returned.

...

Forenexprs(),ensyms() andenquos(), names ofarguments to defuse. Forexprs() andquos(), expressionsto defuse.

.named

IfTRUE, unnamed inputs are automatically namedwithas_label(). This is equivalent to applyingexprs_auto_name() on the result. IfFALSE, unnamed elementsare left as is and, if fully unnamed, the list is given minimalnames (a vector of""). IfNULL, fully unnamed results areleft withNULL names.

.ignore_empty

Whether to ignore empty arguments. Can be oneof"trailing","none","all". If"trailing", only thelast argument is ignored if it is empty. Named arguments are notconsidered empty.

.unquote_names

Whether to treat⁠:=⁠ as=. Unlike=, the⁠:=⁠ syntax supportsnames injection.

.ignore_null

Whether to ignore unnamed null arguments. Can be"none" or"all".

.homonyms

How to treat arguments with the same name. Thedefault,"keep", preserves these arguments. Set.homonyms to"first" to only keep the first occurrences, to"last" to keepthe last occurrences, and to"error" to raise an informativeerror and indicate what arguments have duplicated names.

.check_assign

Whether to check for⁠<-⁠ calls. WhenTRUE awarning recommends users to use= if they meant to match afunction parameter or wrap the⁠<-⁠ call in curly braces otherwise.This ensures assignments are explicit.

expr

An expression to defuse.

Examples

# `exprs()` is the plural variant of `expr()`exprs(foo, bar, bar)# `quo()` and `quos()` are the quosure variants of `expr()` and `exprs()`quo(foo)quos(foo, bar)# `enexpr()` and `enexprs()` are the naked variants of `enquo()` and `enquos()`my_function1 <- function(arg) enexpr(arg)my_function2 <- function(arg, ...) enexprs(arg, ...)my_function1(1 + 1)my_function2(1 + 1, 10 * 2)# `ensym()` and `ensyms()` are symbol variants of `enexpr()` and `enexprs()`my_function3 <- function(arg) ensym(arg)my_function4 <- function(arg, ...) ensyms(arg, ...)# The user must supply symbolsmy_function3(foo)my_function4(foo, bar)# Complex expressions are an errortry(my_function3(1 + 1))try(my_function4(1 + 1, 10 * 2))# `enquo0()` and `enquos0()` disable injection operatorsautomatic_injection <- function(x) enquo(x)no_injection <- function(x) enquo0(x)automatic_injection(foo(!!!1:3))no_injection(foo(!!!1:3))# Injection can still be done explicitlyinject(no_injection(foo(!!!1:3)))

Development notes -dots.R

Description

Development notes -dots.R

.__error_call__. flag in dots collectors

Dots collectors likedots_list() are a little tricky because theymay error out in different situations. Do we want to forward thecontext, i.e. set the call flag to the calling environment?Collectors throw errors in these cases:

  1. While checking their own parameters, in which case the relevantcontext is the collector itself and we don't forward.

  2. While collecting the dots, during evaluation of the suppliedarguments. In this case forwarding or not is irrelevant becauseexpressions in... are evaluated in their own environmentwhich is not connected to the collector's context.

  3. While collecting the dots, during argument constraints checkssuch as determined by the.homonyms argument. In this case wewant to forward the context because the caller of the dotscollector is the one who determines the constraints for itsusers.


Box a final value for early termination

Description

A value boxed withdone() signals to its caller that itshould stop iterating. Use it to shortcircuit a loop.

Usage

done(x)is_done_box(x, empty = NULL)

Arguments

x

Fordone(), a value to box. Foris_done_box(), avalue to test.

empty

Whether the box is empty. IfNULL,is_done_box()returnsTRUE for all done boxes. IfTRUE, it returnsTRUEonly for empty boxes. Otherwise it returnsTRUE only fornon-empty boxes.

Value

Aboxed value.

Examples

done(3)x <- done(3)is_done_box(x)

.data and.env pronouns

Description

The.data and.env pronouns make it explicit where to findobjects when programming withdata-maskedfunctions.

m <- 10mtcars %>% mutate(disp = .data$disp * .env$m)

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.

Where does.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 a⁠R 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 the⁠rlang::⁠ qualifier in data maskingcode. Use the unqualified.data symbol that is automatically putin scope by data-masking functions.


How many arguments are currently forwarded in dots?

Description

This returns the number of arguments currently forwarded in...as an integer.

Usage

dots_n(...)

Arguments

...

Forwarded arguments.

Examples

fn <- function(...) dots_n(..., baz)fn(foo, bar)

Splice lists

Description

[Deprecated]

dots_splice() is likedots_list() but automatically spliceslist inputs.

Usage

dots_splice(  ...,  .ignore_empty = c("trailing", "none", "all"),  .preserve_empty = FALSE,  .homonyms = c("keep", "first", "last", "error"),  .check_assign = FALSE)

Arguments

...

Arguments to collect in a list. These dots aredynamic.

.ignore_empty

Whether to ignore empty arguments. Can be oneof"trailing","none","all". If"trailing", only thelast argument is ignored if it is empty.

.preserve_empty

Whether to preserve the empty arguments thatwere not ignored. IfTRUE, empty arguments are stored withmissing_arg() values. IfFALSE (the default) an error isthrown when an empty argument is detected.

.homonyms

How to treat arguments with the same name. Thedefault,"keep", preserves these arguments. Set.homonyms to"first" to only keep the first occurrences, to"last" to keepthe last occurrences, and to"error" to raise an informativeerror and indicate what arguments have duplicated names.

.check_assign

Whether to check for⁠<-⁠ calls. WhenTRUE awarning recommends users to use= if they meant to match afunction parameter or wrap the⁠<-⁠ call in curly braces otherwise.This ensures assignments are explicit.


Evaluate dots with preliminary splicing

Description

This is a tool for advanced users. It captures dots, processesunquoting and splicing operators, and evaluates them. Unlikedots_list(), it does not flatten spliced objects, instead theyare attributed aspliced class (seesplice()). You can processspliced objects manually, perhaps with a custom predicate (seeflatten_if()).

Usage

dots_values(  ...,  .ignore_empty = c("trailing", "none", "all"),  .preserve_empty = FALSE,  .homonyms = c("keep", "first", "last", "error"),  .check_assign = FALSE)

Arguments

...

Arguments to evaluate and process splicing operators.

.ignore_empty

Whether to ignore empty arguments. Can be oneof"trailing","none","all". If"trailing", only thelast argument is ignored if it is empty.

.preserve_empty

Whether to preserve the empty arguments thatwere not ignored. IfTRUE, empty arguments are stored withmissing_arg() values. IfFALSE (the default) an error isthrown when an empty argument is detected.

.homonyms

How to treat arguments with the same name. Thedefault,"keep", preserves these arguments. Set.homonyms to"first" to only keep the first occurrences, to"last" to keepthe last occurrences, and to"error" to raise an informativeerror and indicate what arguments have duplicated names.

.check_assign

Whether to check for⁠<-⁠ calls. WhenTRUE awarning recommends users to use= if they meant to match afunction parameter or wrap the⁠<-⁠ call in curly braces otherwise.This ensures assignments are explicit.

Examples

dots <- dots_values(!!! list(1, 2), 3)dots# Flatten the objects marked as spliced:flatten_if(dots, is_spliced)

Duplicate an R object

Description

duplicate() is an interface to the C-levelduplicate() andshallow_duplicate() functions. It is mostly meant for users ofthe C API of R, e.g. for debugging, experimenting, or prototyping Ccode in R.

Usage

duplicate(x, shallow = FALSE)

Arguments

x

An R object. Uncopyable objects like symbols andenvironments are returned as is (just like with⁠<-⁠).

shallow

Recursive data structures like lists, calls andpairlists are duplicated in full by default. A shallow copy onlyduplicates the top-level data structure.

See Also

pairlist


Dynamic dots features

Description

The base... syntax supports:

Dynamic dots offer a few additional features,injection in particular:

  1. You cansplice arguments saved in a list with the spliceoperator!!!.

  2. You caninject names withglue syntax onthe left-hand side of⁠:=⁠.

  3. Trailing commas are ignored, making it easier to copy and pastelines of arguments.

Add dynamic dots support in your functions

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.

Examples

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")}

Embrace operator⁠{{⁠

Description

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 }}))}

Under the hood

⁠{{⁠ combinesenquo() and!! in onestep. The snippet above is equivalent to:

my_mean <- function(data, var) {  var <- enquo(var)  dplyr::summarise(data, mean = mean(!!var))}

See Also


Get the empty environment

Description

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()).

Usage

empty_env()

Examples

# Create environments with nothing in scope:child_env(empty_env())

Defuse function arguments with glue

Description

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.

Usage

englue(x, env = caller_env(), error_call = current_env(), error_arg = "x")

Arguments

x

A string to interpolate with glue operators.

env

User environment where the interpolation data lives incase you're wrappingenglue() in another function.

error_call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

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.

Details

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 your⁠Imports:⁠ section.

usethis::use_package("glue", "Imports")

Wrappingenglue()

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:

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.

See Also

Examples

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)

Defuse function arguments

Description

enquo() andenquos()defuse function arguments.A defused expression can be examined, modified, and injected intoother expressions.

Defusing function arguments is useful for:

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.

Usage

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)

Arguments

arg

An unquoted argument name. The expressionsupplied to that argument is defused and returned.

...

Names of arguments to defuse.

.named

IfTRUE, unnamed inputs are automatically namedwithas_label(). This is equivalent to applyingexprs_auto_name() on the result. IfFALSE, unnamed elementsare left as is and, if fully unnamed, the list is given minimalnames (a vector of""). IfNULL, fully unnamed results areleft withNULL names.

.ignore_empty

Whether to ignore empty arguments. Can be oneof"trailing","none","all". If"trailing", only thelast argument is ignored if it is empty. Named arguments are notconsidered empty.

.ignore_null

Whether to ignore unnamed null arguments. Can be"none" or"all".

.unquote_names

Whether to treat⁠:=⁠ as=. Unlike=, the⁠:=⁠ syntax supportsnames injection.

.homonyms

How to treat arguments with the same name. Thedefault,"keep", preserves these arguments. Set.homonyms to"first" to only keep the first occurrences, to"last" to keepthe last occurrences, and to"error" to raise an informativeerror and indicate what arguments have duplicated names.

.check_assign

Whether to check for⁠<-⁠ calls. WhenTRUE awarning recommends users to use= if they meant to match afunction parameter or wrap the⁠<-⁠ call in curly braces otherwise.This ensures assignments are explicit.

Value

enquo() returns aquosure andenquos()returns a list of quosures.

Implicit injection

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.

See Also

Examples

# `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)

Add backtrace from error handler

Description

entrace() is a low level function. Seeglobal_entrace() for auser-friendly way of enriching errors and other conditions fromyour RProfile.

entrace() also works as anoption(error = ) handler forcompatibility with versions of R older than 4.0.

When used as calling handler, rlang trims the handler invokationcontext from the backtrace.

Usage

entrace(cnd, ..., top = NULL, bottom = NULL)cnd_entrace(cnd, ..., top = NULL, bottom = NULL)

Arguments

cnd

Whenentrace() is used as a calling handler,cnd isthe condition to handle.

...

Unused. These dots are for future extensions.

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 calltrace_back()indirectly or from a larger context, for example in tests orinside an RMarkdown document where you don't want all of theknitr evaluation mechanisms to appear in the backtrace.

If not supplied, therlang_trace_top_env global option isconsulted. This makes it possible to trim the embedding contextfor all backtraces created while the option is set. If knitr isin progress, the default value for this option isknitr::knit_global() so that the knitr context is trimmed outof backtraces.

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 tocaller_env().

See Also

global_entrace() for configuring errors withentrace().cnd_entrace() to manually add a backtrace to acondition.

Examples

quote({  # Not run# Set `entrace()` globally in your RProfileglobalCallingHandlers(error = rlang::entrace)# On older R versions which don't feature `globalCallingHandlers`,# set the error handler like this:options(error = rlang::entrace)})

Create a new environment

Description

These functions create new environments.

Usage

env(...)new_environment(data = list(), parent = empty_env())

Arguments

...,data

<dynamic> Named values. You cansupply one unnamed to specify a custom parent, otherwise itdefaults to the current environment.

parent

A parent environment.

Environments as objects

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).

Inheritance

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().

Reference semantics

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.

See Also

env_has(),env_bind().

Examples

# 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"))

Bind symbols to objects in an environment

Description

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.

Usage

env_bind(.env, ...)env_bind_lazy(.env, ..., .eval_env = caller_env())env_bind_active(.env, ...)lhs %<~% rhs

Arguments

.env

An environment.

...

<dynamic> Named objects (env_bind()),expressionsenv_bind_lazy(), or functions (env_bind_active()).Usezap() to remove bindings.

.eval_env

The environment where the expressions will beevaluated when the symbols are forced.

lhs

The variable name to whichrhs will be lazily assigned.

rhs

An expression lazily evaluated and assigned tolhs.

Value

The input object.env, with its associated environmentmodified in place, invisibly.

Side effects

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.

See Also

env_poke() for binding a single element.

Examples

# 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

What kind of environment binding?

Description

[Experimental]

Usage

env_binding_are_active(env, nms = NULL)env_binding_are_lazy(env, nms = NULL)

Arguments

env

An environment.

nms

Names of bindings. Defaults to all bindings inenv.

Value

A logical vector as long asnms and named after it.


Lock or unlock environment bindings

Description

[Experimental]

Locked environment bindings trigger an error when an attempt ismade to redefine the binding.

Usage

env_binding_lock(env, nms = NULL)env_binding_unlock(env, nms = NULL)env_binding_are_locked(env, nms = NULL)

Arguments

env

An environment.

nms

Names of bindings. Defaults to all bindings inenv.

Value

env_binding_are_unlocked() returns a logical vector aslong asnms and named after it.env_binding_lock() andenv_binding_unlock() return the old value ofenv_binding_are_unlocked() invisibly.

See Also

env_lock() for locking an environment.

Examples

# Bindings are unlocked by default:env <- env(a = "A", b = "B")env_binding_are_locked(env)# But can optionally be locked:env_binding_lock(env, "a")env_binding_are_locked(env)# If run, the following would now return an error because `a` is locked:# env_bind(env, a = "foo")# with_env(env, a <- "bar")# Let's unlock it. Note that the return value indicate which# bindings were locked:were_locked <- env_binding_unlock(env)were_locked# Now that it is unlocked we can modify it again:env_bind(env, a = "foo")with_env(env, a <- "bar")env$a

Browse environments

Description

[Defunct]

Usage

env_browse(env, value = TRUE)env_is_browsed(env)

Arguments

env

An environment.

value

Whether to browseenv.

Value

env_browse() returns the previous value ofenv_is_browsed() (a logical), invisibly.


Mask bindings by defining symbols deeper in a scope

Description

[Superseded]

This function is superseded. Please useenv() (and possiblyset_env() if you're masking the bindings for another object likea closure or a formula) instead.

env_bury() is likeenv_bind() but it creates the bindings in anew child environment. This makes sure the new bindings haveprecedence over old ones, without altering existing environments.Unlikeenv_bind(), this function does not have side effects andreturns a new environment (or object wrapping that environment).

Usage

env_bury(.env, ...)

Arguments

.env

An environment.

...

<dynamic> Named objects (env_bind()),expressionsenv_bind_lazy(), or functions (env_bind_active()).Usezap() to remove bindings.

Value

A copy of.env enclosing the new environment containingbindings to... arguments.

See Also

env_bind(),env_unbind()

Examples

orig_env <- env(a = 10)fn <- set_env(function() a, orig_env)# fn() currently sees `a` as the value `10`:fn()# env_bury() will bury the current scope of fn() behind a new# environment:fn <- env_bury(fn, a = 1000)fn()# Even though the symbol `a` is still defined deeper in the scope:orig_env$a

Cache a value in an environment

Description

env_cache() is a wrapper aroundenv_get() andenv_poke()designed to retrieve a cached value fromenv.

Usage

env_cache(env, nm, default)

Arguments

env

An environment.

nm

Name of binding, a string.

default

The default value to store inenv ifnm does notexist yet.

Value

Either the value ofnm ordefault if it did not existyet.

Examples

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$b

Clone or coalesce an environment

Description

Both these functions preserve active bindings and promises (thelatter are only preserved on R >= 4.0.0).

Usage

env_clone(env, parent = env_parent(env))env_coalesce(env, from)

Arguments

env

An environment.

parent

The parent of the cloned environment.

from

Environment to copy bindings from.

Examples

# 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)

Depth of an environment chain

Description

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).

Usage

env_depth(env)

Arguments

env

An environment.

Value

An integer.

See Also

The section on inheritance inenv() documentation.

Examples

env_depth(empty_env())env_depth(pkg_env("rlang"))

Get an object in an environment

Description

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.

Usage

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())

Arguments

env

An environment.

nm

Name of binding, a string.

default

A default value in case there is no binding fornminenv.

inherit

Whether to look for bindings in the parentenvironments.

last

Last environment inspected wheninherit isTRUE.Can be useful in conjunction withbase::topenv().

nms

Names of bindings, a character vector.

Value

An object if it exists. Otherwise, throws an error.

See Also

env_cache() for a variant ofenv_get() designed tocache a value in an environment.

Examples

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")

Does an environment have or see bindings?

Description

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).

Usage

env_has(env = caller_env(), nms, inherit = FALSE)

Arguments

env

An environment.

nms

A character vector of binding names for which to checkexistence.

inherit

Whether to look for bindings in the parentenvironments.

Value

A named logical vector as long asnms.

Examples

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)

Does environment inherit from another environment?

Description

This returnsTRUE ifx hasancestor among its parents.

Usage

env_inherits(env, ancestor)

Arguments

env

An environment.

ancestor

Another environment from whichx might inherit.


Is frame environment user facing?

Description

Detects ifenv is user-facing, that is, whether it's an environmentthat inherits from:

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.

Usage

env_is_user_facing(env)

Arguments

env

An environment.

Escape hatch

You can override the return value ofenv_is_user_facing() bysetting the global option"rlang_user_facing" to:

Examples

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())

Lock an environment

Description

[Experimental]

Locked environments cannot be modified. An important example isnamespace environments which are locked by R when loaded in asession. Once an environment is locked it normally cannot beunlocked.

Note that only the environment as a container is locked, not theindividual bindings. You can't remove or add a binding but you canstill modify the values of existing bindings. Seeenv_binding_lock() for locking individual bindings.

Usage

env_lock(env)env_is_locked(env)

Arguments

env

An environment.

Value

The old value ofenv_is_locked() invisibly.

See Also

env_binding_lock()

Examples

# New environments are unlocked by default:env <- env(a = 1)env_is_locked(env)# Use env_lock() to lock them:env_lock(env)env_is_locked(env)# Now that `env` is locked, it is no longer possible to remove or# add bindings. If run, the following would fail:# env_unbind(env, "a")# env_bind(env, b = 2)# Note that even though the environment as a container is locked,# the individual bindings are still unlocked and can be modified:env$a <- 10

Label of an environment

Description

Special environments like the global environment have their ownnames.env_name() returns:

env_label() is exactly likeenv_name() but returns the memoryaddress of anonymous environments as fallback.

Usage

env_name(env)env_label(env)

Arguments

env

An environment.

Examples

# 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())

Names and numbers of symbols bound in an environment

Description

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.

Usage

env_names(env)env_length(env)

Arguments

env

An environment.

Value

A character vector of object names.

Names of symbols and objects

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).

Encoding

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.

Examples

env <- env(a = 1, b = 2)env_names(env)

Get parent environments

Description

See the section oninheritance inenv()'s documentation.

Usage

env_parent(env = caller_env(), n = 1)env_tail(env = caller_env(), last = global_env())env_parents(env = caller_env(), last = global_env())

Arguments

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.

env_tail() returns the environment which haslast as parentandenv_parents() returns the list of environments up tolast.

Value

An environment forenv_parent() andenv_tail(), a listof environments forenv_parents().

Examples

# 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())

Poke an object in an environment

Description

env_poke() will assign or reassign a binding inenv ifcreateisTRUE. Ifcreate isFALSE and a binding does not alreadyexists, an error is issued.

Usage

env_poke(env = caller_env(), nm, value, inherit = FALSE, create = !inherit)

Arguments

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.

Details

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.

Value

The old value ofnm or azap sentinel if thebinding did not exist yet.

See Also

env_bind() for binding multiple elements.env_cache()for a variant ofenv_poke() designed to cache values.


Pretty-print an environment

Description

This prints:

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.

Usage

env_print(env = caller_env())

Arguments

env

An environment, or object that can be converted to anenvironment byget_env().


Remove bindings from an environment

Description

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.

Usage

env_unbind(env = caller_env(), nms, inherit = FALSE)

Arguments

env

An environment.

nms

A character vector of binding names to remove.

inherit

Whether to look for bindings in the parentenvironments.

Value

The input objectenv with its associated environmentmodified in place, invisibly.

Examples

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)

Unlock an environment

Description

[Defunct]. This function is now defunctbecause recent versions of R no longer make it possible tounlock an environment.

Usage

env_unlock(env)

Arguments

env

An environment.

Value

Whether the environment has been unlocked.


Evaluate an expression in an environment

Description

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.

Usage

eval_bare(expr, env = parent.frame())

Arguments

expr

An expression to evaluate.

env

The environment in which to evaluate the expression.

Details

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():

The flip side of the semantics ofeval_bare() is that it can'tevaluatebreak ornext expressions even if called within aloop.

See Also

eval_tidy() for evaluation with data mask and quosuresupport.

Examples

# 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)

Evaluate an expression with quosures and pronoun support

Description

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:

Usage

eval_tidy(expr, data = NULL, env = caller_env())

Arguments

expr

Anexpression orquosure to evaluate.

data

A data frame, or named list or vector. Alternatively, adata mask created withas_data_mask() ornew_data_mask(). Objects indata have priority over those inenv. See the section about data masking.

env

The environment in which to evaluateexpr. Thisenvironment is not applicable for quosures because they havetheir own environments.

When should eval_tidy() be used instead of eval()?

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)))

Stack semantics ofeval_tidy()

eval_tidy() always evaluates in a data mask, even whendata isNULL. Because of this, it has different stack semantics thanbase::eval():

See alsoeval_bare() for more information about these differences.

See Also

Examples

# 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)

Execute a function

Description

This function constructs and evaluates a call to.fn.It has two primary uses:

Usage

exec(.fn, ..., .env = caller_env())

Arguments

.fn

A function, or function name as a string.

...

<dynamic> Arguments for.fn.

.env

Environment in which to evaluate the call. This will bemost useful if.fn is a string, or the function has side-effects.

Examples

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)

Defuse an R expression

Description

expr()defuses an R expression withinjection support.

It is equivalent tobase::bquote().

Arguments

expr

An expression to defuse.

See Also

Examples

# 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)))

Process unquote operators in a captured expression

Description

[Deprecated]expr_interp() is deprecated, please useinject() instead.

Usage

expr_interp(x, env = NULL)

Arguments

x,env

[Deprecated]


Turn an expression to a label

Description

[Questioning]

expr_text() turns the expression into a single string, whichmight be multi-line.expr_name() is suitable for formattingnames. It works best with symbols and scalar types, but alsoaccepts calls.expr_label() formats the expression nicely for usein messages.

Usage

expr_label(expr)expr_name(expr)expr_text(expr, width = 60L, nlines = Inf)

Arguments

expr

An expression to labellise.

width

Width of each line.

nlines

Maximum number of lines to extract.

Examples

# To labellise a function argument, first capture it with# substitute():fn <- function(x) expr_label(substitute(x))fn(x:y)# Strings are encodedexpr_label("a\nb")# Names and expressions are quoted with ``expr_label(quote(x))expr_label(quote(a + b + c))# Long expressions are collapsedexpr_label(quote(foo({  1 + 2  print(x)})))

Print an expression

Description

expr_print(), powered byexpr_deparse(), is an alternativeprinter for R expressions with a few improvements over the base Rprinter.

Usage

expr_print(x, ...)expr_deparse(x, ..., width = peek_option("width"))

Arguments

x

An object or expression to print.

...

Arguments passed toexpr_deparse().

width

The width of the deparsed or printed expression.Defaults to the global optionwidth.

Value

expr_deparse() returns a character vector of lines.expr_print() returns its input invisibly.

Examples

# 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)

Ensure that all elements of a list of expressions are named

Description

This gives default names to unnamed elements of a list ofexpressions (or expression wrappers such as formulas orquosures), deparsed withas_label().

Usage

exprs_auto_name(  exprs,  ...,  repair_auto = c("minimal", "unique"),  repair_quiet = FALSE)quos_auto_name(quos)

Arguments

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?vctrs::vec_as_namesfor information about name repairing.

repair_quiet

Whether to inform user about repaired names.

quos

A list of quosures.


Get or set formula components

Description

f_rhs extracts the righthand side,f_lhs extracts the lefthandside, andf_env extracts the environment. All functions throw anerror iff is not a formula.

Usage

f_rhs(f)f_rhs(x) <- valuef_lhs(f)f_lhs(x) <- valuef_env(f)f_env(x) <- value

Arguments

f,x

A formula

value

The value to replace with.

Value

f_rhs andf_lhs return language objects (i.e. atomicvectors of length 1, a name, or a call).f_env returns anenvironment.

Examples

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)

Turn RHS of formula into a string or label

Description

Equivalent ofexpr_text() andexpr_label() for formulas.

Usage

f_text(x, width = 60L, nlines = Inf)f_name(x)f_label(x)

Arguments

x

A formula.

width

Width of each line.

nlines

Maximum number of lines to extract.

Examples

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)}))

Global options for rlang

Description

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.


Internal API for standalone-types-check

Description

Internal API for standalone-types-check


Flatten or squash a list of lists into a simpler vector

Description

[Deprecated]

These functions are deprecated in favour ofpurrr::list_c() andpurrr::list_flatten().

flatten() removes one level hierarchy from a list, whilesquash() removes all levels. These functions are similar tounlist() but they are type-stable so you always know what thetype of the output is.

Usage

flatten(x)flatten_lgl(x)flatten_int(x)flatten_dbl(x)flatten_cpl(x)flatten_chr(x)flatten_raw(x)squash(x)squash_lgl(x)squash_int(x)squash_dbl(x)squash_cpl(x)squash_chr(x)squash_raw(x)flatten_if(x, predicate = is_spliced)squash_if(x, predicate = is_spliced)

Arguments

x

A list to flatten or squash. The contents of the list canbe anything for unsuffixed functionsflatten() andsquash()(as a list is returned), but the contents must match the type forthe other functions.

predicate

A function of one argument returning whether itshould be spliced.

Value

flatten() returns a list,flatten_lgl() a logicalvector,flatten_int() an integer vector,flatten_dbl() adouble vector, andflatten_chr() a character vector. Similarlyforsquash() and the typed variants (squash_lgl() etc).

Examples

x <- replicate(2, sample(4), simplify = FALSE)xflatten(x)flatten_int(x)# With flatten(), only one level gets removed at a time:deep <- list(1, list(2, list(3)))flatten(deep)flatten(flatten(deep))# But squash() removes all levels:squash(deep)squash_dbl(deep)# The typed flatten functions remove one level and coerce to an atomic# vector at the same time:flatten_dbl(list(1, list(2)))# Only bare lists are flattened, but you can splice S3 lists# explicitly:foo <- set_attrs(list("bar"), class = "foo")str(flatten(list(1, foo, list(100))))str(flatten(list(1, splice(foo), list(100))))# Instead of splicing manually, flatten_if() and squash_if() let# you specify a predicate function:is_foo <- function(x) inherits(x, "foo") || is_bare_list(x)str(flatten_if(list(1, foo, list(100)), is_foo))# squash_if() does the same with deep lists:deep_foo <- list(1, list(foo, list(foo, 100)))str(deep_foo)str(squash(deep_foo))str(squash_if(deep_foo, is_foo))

Get or set function body

Description

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, unlike⁠body<-⁠.

Usage

fn_body(fn = caller_fn())fn_body(fn) <- value

Arguments

fn

A function. It is looked up in the calling frame if notsupplied.

value

New formals or formals names forfn.

Examples

# fn_body() is like body() but always returns a block:fn <- function() do()body(fn)fn_body(fn)# It also throws an error when used on a primitive function:try(fn_body(base::list))

Return the closure environment of a function

Description

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.

Usage

fn_env(fn)fn_env(x) <- value

Arguments

fn,x

A function.

value

A new closure environment for the function.

Details

fn_env() returns the closure environment offn. There is alsoan assignment method to set a new closure environment.

Examples

env <- child_env("base")fn <- with_env(env, function() NULL)identical(fn_env(fn), env)other_env <- child_env("base")fn_env(fn) <- other_envidentical(fn_env(fn), other_env)

Extract arguments from a function

Description

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.

Usage

fn_fmls(fn = caller_fn())fn_fmls_names(fn = caller_fn())fn_fmls_syms(fn = caller_fn())fn_fmls(fn) <- valuefn_fmls_names(fn) <- value

Arguments

fn

A function. It is looked up in the calling frame if notsupplied.

value

New formals or formals names forfn.

Details

Unlikeformals(), these helpers throw an error with primitivefunctions instead of returningNULL.

See Also

call_args() andcall_args_names()

Examples

# Extract from current call:fn <- function(a = 1, b = 2) fn_fmls()fn()# fn_fmls_syms() makes it easy to forward arguments:call2("apply", !!! fn_fmls_syms(lapply))# You can also change the formals:fn_fmls(fn) <- list(A = 10, B = 20)fn()fn_fmls_names(fn) <- c("foo", "bar")fn()

Format bullets for error messages

Description

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:

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 errorconditon 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().

Usage

format_error_bullets(x)

Arguments

x

A named character vector of messages. Named elements areprefixed with the corresponding bullet. Elements named with asingle space" " trigger a line break from the previous bullet.

Examples

# 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")))

Validate and format a function call for use in error messages

Description

Usage

format_error_call(call)error_call(call)

Arguments

call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

Value

Either a string formatted as code orNULL if a simplecall could not be generated.

Details of formatting

Examples

# Arguments are strippedwriteLines(format_error_call(quote(foo(bar, baz))))# Returns `NULL` with complex calls such as those that contain# inlined functionsformat_error_call(call2(list))# Operators are formatted using their names rather than in# function call formwriteLines(format_error_call(quote(1 + 2)))

Format a type for error messages

Description

[Deprecated]

friendly_type() is deprecated. Please use thestandalone-obj-type.R file instead. You can import itin your package withusethis::use_standalone("r-lib/rlang", "obj-type").

Usage

friendly_type(type)

Arguments

type

A type as returned bytypeof().

Value

A string of the prettified type, qualified with anindefinite article.


Get or set the environment of an object

Description

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).

Usage

get_env(env, default = NULL)set_env(env, new_env = caller_env())env_poke_parent(env, new_env)

Arguments

env

An environment.

default

The default environment in caseenv does not wrapan environment. IfNULL and no environment could be extracted,an error is issued.

new_env

An environment to replaceenv with.

Details

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.

See Also

quo_get_env() andquo_set_env() for versions ofget_env() andset_env() that only work on quosures.

Examples

# 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)

Entrace unexpected errors

Description

global_entrace() enriches base errors, warnings, and messageswith rlang features.

Set global entracing in your RProfile with:

rlang::global_entrace()

Usage

global_entrace(enable = TRUE, class = c("error", "warning", "message"))

Arguments

enable

Whether to enable or disable global handling.

class

A character vector of one or several classes ofconditions to be entraced.

Inside RMarkdown documents

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")```

Under the hood

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).


Register default global handlers

Description

global_handle() sets up a default configuration for error,warning, and message handling. It calls:

Usage

global_handle(entrace = TRUE, prompt_install = TRUE)

Arguments

entrace

Passed asenable argument toglobal_entrace().

prompt_install

Passed asenable argument toglobal_prompt_install().


Prompt user to install missing packages

Description

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.

Usage

global_prompt_install(enable = TRUE)

Arguments

enable

Whether to enable or disable global handling.


Name injection with"{" and"{{"

Description

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     1

Inside 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            2

See 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.19

Using the wrong operator causes unexpected results:

x <- "name"list2("{{ x }}" := 1)#> $`"name"`#> [1] 1list2("{x}" := 1)#> $name#> [1] 1

Ideally, 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.

Allow overriding default names

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

What's the deal with⁠:=⁠?

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=.

Using glue syntax in packages

Since rlang does not depend directly on glue, you will have to ensure that glue is installed by adding it to your⁠Imports:⁠ section.

usethis::use_package("glue", "Imports")

How long is an object?

Description

This is a function for the common task of testing the length of anobject. It checks the length of an object in a non-generic way:base::length() methods are ignored.

Usage

has_length(x, n = NULL)

Arguments

x

A R object.

n

A specific length to testx with. IfNULL,has_length() returnsTRUE ifx has length greater thanzero, andFALSE otherwise.

Examples

has_length(list())has_length(list(), 0)has_length(letters)has_length(letters, 20)has_length(letters, 26)

Does an object have an element with this name?

Description

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().

Usage

has_name(x, name)

Arguments

x

A data frame or another named object

name

Element name(s) to check

Details

Unnamed objects are treated as if all names are empty strings.NAinput givesFALSE as output.

Value

A logical vector of the same length asname

Examples

has_name(iris, "Species")has_name(mtcars, "gears")

Hashing

Description

The generated hash is guaranteed to be reproducible across platforms thathave the same endianness and are using the same R version.

Usage

hash(x)hash_file(path)

Arguments

x

An object.

path

A character vector of paths to the files to be hashed.

Details

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. On R >= 3.5.0, serialization version 3 is used, otherwise version 2 isused. Seeserialize() for more information about the serialization version.

Value

Examples

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)

Does an object inherit from a set of classes?

Description

Usage

inherits_any(x, class)inherits_all(x, class)inherits_only(x, class)

Arguments

x

An object to test for inheritance.

class

A character vector of classes.

Examples

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 objects in an R expression

Description

inject() evaluates an expression withinjectionsupport. There are three main usages:

Usage

inject(expr, env = caller_env())

Arguments

expr

An argument to evaluate. This argument is immediatelyevaluated inenv (the current environment by default) withinjected objects and expressions.

env

The environment in which to evaluateexpr. Defaults tothe current environment. For expert use only.

Examples

# 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))

Injection operator⁠!!⁠

Description

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.

Where does⁠!!⁠ work?

⁠!!⁠ does not work everywhere, you can only use it within certainspecial functions:

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.

Injecting values

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.

Injecting expressions

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.1875

Sincewith() 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.

When do I need⁠!!⁠?

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.

See Also


Simulate interrupt condition

Description

interrupt() simulates a user interrupt of the kind that issignalled withCtrl-C. It is currently not possible to createcustom interrupt condition objects.

Usage

interrupt()

Invoke a function with a list of arguments

Description

[Deprecated]Deprecated in rlang 0.4.0 in favour ofexec().

Usage

invoke(.fn, .args = list(), ..., .env = caller_env(), .bury = c(".fn", ""))

Arguments

.fn,args,...,.env,.bury

[Deprecated]


Is object a call?

Description

This function tests ifx is acall. This is apattern-matching predicate that returnsFALSE ifname andnare supplied and the call does not match these properties.

Usage

is_call(x, name = NULL, n = NULL, ns = NULL)

Arguments

x

An object to test. Formulas and quosures are treatedliterally.

name

An optional name that the call should match. It ispassed tosym() before matching. This argument is vectorisedand you can supply a vector of names to match. In this case,is_call() returnsTRUE if at least one name matches.

n

An optional number of arguments that the call shouldmatch.

ns

The namespace of the call. IfNULL, the namespacedoesn't participate in the pattern-matching. If an empty string"" andx is a namespaced call,is_call() returnsFALSE. If any other string,is_call() checks thatx isnamespaced withinns.

Can be a character vector of namespaces, in which case the callhas to match at least one of them, otherwiseis_call() returnsFALSE.

See Also

is_expression()

Examples

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 an object callable?

Description

A callable object is an object that can appear in the functionposition of a call (as opposed to argument position). This includessymbolic objects that evaluate to a function orliteral functions embedded in the call.

Usage

is_callable(x)

Arguments

x

An object to test.

Details

Note that strings may look like callable objects becauseexpressions of the form"list"() are valid R code. However,that's only because the R parser transforms strings to symbols. Itis not legal to manually set language heads to strings.

Examples

# Symbolic objects and functions are callable:is_callable(quote(foo))is_callable(base::identity)# node_poke_car() lets you modify calls without any checking:lang <- quote(foo(10))node_poke_car(lang, current_env())# Use is_callable() to check an input object is safe to put as CAR:obj <- base::identityif (is_callable(obj)) {  lang <- node_poke_car(lang, obj)} else {  abort("`obj` must be callable")}eval_bare(lang)

Is object a condition?

Description

Is object a condition?

Usage

is_condition(x)is_error(x)is_warning(x)is_message(x)

Arguments

x

An object to test.


Is an object copyable?

Description

When an object is modified, R generally copies it (sometimeslazily) to enforcevalue semantics.However, some internal types are uncopyable. If you try to copythem, either with⁠<-⁠ or by argument passing, you actually createreferences to the original object rather than actualcopies. Modifying these references can thus have far reaching sideeffects.

Usage

is_copyable(x)

Arguments

x

An object to test.

Examples

# Let's add attributes with structure() to uncopyable types. Since# they are not copied, the attributes are changed in place:env <- env()structure(env, foo = "bar")env# These objects that can only be changed with side effect are not# copyable:is_copyable(env)

Is a vector uniquely named?

Description

Likeis_named() but also checks that names are unique.

Usage

is_dictionaryish(x)

Arguments

x

A vector.


Is object an empty vector or NULL?

Description

Is object an empty vector or NULL?

Usage

is_empty(x)

Arguments

x

object to test

Examples

is_empty(NULL)is_empty(list())is_empty(list(NULL))

Is object an environment?

Description

is_bare_environment() tests whetherx is an environment without a s3 ors4 class.

Usage

is_environment(x)is_bare_environment(x)

Arguments

x

object to test


Is an object an expression?

Description

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:

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.

Usage

is_expression(x)is_syntactic_literal(x)is_symbolic(x)

Arguments

x

An object to test.

Details

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.

See Also

is_call() for a call predicate.

Examples

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 object a formula?

Description

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.

Usage

is_formula(x, scoped = NULL, lhs = NULL)is_bare_formula(x, scoped = TRUE, lhs = NULL)

Arguments

x

An object to test.

scoped

A boolean indicating whether the quosure is scoped,that is, has a valid environment attribute and inherits from"formula". IfNULL, the scope is not inspected.

lhs

A boolean indicating whether the formula has a left-handside. IfNULL, the LHS is not inspected andis_formula()returnsTRUE for both one- and two-sided formulas.

Dealing with unevaluated formulas

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.

Examples

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 object a function?

Description

The R language defines two different types of functions: primitivefunctions, which are low-level, and closures, which are the regularkind of functions.

Usage

is_function(x)is_closure(x)is_primitive(x)is_primitive_eager(x)is_primitive_lazy(x)

Arguments

x

Object to be tested.

Details

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.

Examples

# Primitive functions are not closures:is_closure(base::c)is_primitive(base::c)# On the other hand, internal functions are wrapped in a closure# and appear as such from the R side:is_closure(base::eval)# Both closures and primitives are functions:is_function(base::c)is_function(base::eval)# 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)

Are packages installed in any of the libraries?

Description

These functions check that packages are installed with minimal sideeffects. If installed, the packages will be loaded but notattached.

You can disable the prompt by setting therlib_restart_package_not_found global option toFALSE. In thatcase, missing packages always cause an error.

Usage

is_installed(pkg, ..., version = NULL, compare = NULL)check_installed(  pkg,  reason = NULL,  ...,  version = NULL,  compare = NULL,  action = NULL,  call = caller_env())

Arguments

pkg

The package names. Can include version requirements,e.g."pkg (>= 1.0.0)".

...

These dots must be empty.

version

Minimum versions forpkg. If supplied, must be thesame length aspkg.NA elements stand for any versions.

compare

A character vector of comparison operators to useforversion. If supplied, must be the same length asversion. IfNULL,>= is used as default for allelements.NA elements incompare are also set to>= bydefault.

reason

Optional string indicating why ispkg needed.Appears in error messages (if non-interactive) and user prompts(if interactive).

action

An optional function takingpkg and...arguments. It is called bycheck_installed() when the userchooses to update outdated packages. The function is passed themissing and outdated packages as a character vector of names.

call

The execution environment of a currentlyrunning function, e.g.caller_env(). The function will bementioned in error messages as the source of the error. See thecall argument ofabort() for more information.

Value

is_installed() returnsTRUE ifall package namesprovided inpkg are installed,FALSEotherwise.check_installed() either doesn't return or returnsNULL.

Handling package not found errors

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.

Examples

is_installed("utils")is_installed(c("base", "ggplot5"))is_installed(c("base", "ggplot5"), version = c(NA, "5.1.0"))

Is a vector integer-like?

Description

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:

Usage

is_integerish(x, n = NULL, finite = NULL)is_bare_integerish(x, n = NULL, finite = NULL)is_scalar_integerish(x, finite = NULL)

Arguments

x

Object to be tested.

n

Expected length of a vector.

finite

Whether all values of the vector are finite. Thenon-finite values areNA,Inf,-Inf andNaN. Setting thisto something other thanNULL can be expensive because the wholevector needs to be traversed and checked.

See Also

is_bare_numeric() for testing whether an object is abase numeric type (a bare double or integer vector).

Examples

is_integerish(10L)is_integerish(10.0)is_integerish(10.0, n = 2)is_integerish(10.000001)is_integerish(TRUE)

Is R running interactively?

Description

Likebase::interactive(),is_interactive() returnsTRUE whenthe function runs interactively andFALSE when it runs in batchmode. It also checks, in this order:

with_interactive() andlocal_interactive() set the globaloption conveniently.

Usage

is_interactive()local_interactive(value = TRUE, frame = caller_env())with_interactive(expr, value = TRUE)

Arguments

value

A singleTRUE orFALSE. This overrides the returnvalue ofis_interactive().

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 tovalue.


Is object a call?

Description

[Deprecated]These functions are deprecated, please useis_call() and itsnargument instead.

Usage

is_lang(x, name = NULL, n = NULL, ns = NULL)

Arguments

x

An object to test. Formulas and quosures are treatedliterally.

name

An optional name that the call should match. It ispassed tosym() before matching. This argument is vectorisedand you can supply a vector of names to match. In this case,is_call() returnsTRUE if at least one name matches.

n

An optional number of arguments that the call shouldmatch.

ns

The namespace of the call. IfNULL, the namespacedoesn't participate in the pattern-matching. If an empty string"" andx is a namespaced call,is_call() returnsFALSE. If any other string,is_call() checks thatx isnamespaced withinns.

Can be a character vector of namespaces, in which case the callhas to match at least one of them, otherwiseis_call() returnsFALSE.


Is object named?

Description

Usage

is_named(x)is_named2(x)have_name(x)

Arguments

x

A vector to test.

Details

is_named() always returnsTRUE for empty vectors because

Value

is_named() andis_named2() are scalar predicates thatreturnTRUE orFALSE.have_name() is vectorised and returnsa logical vector as long as the input.

Examples

# 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?

Description

Is an object a namespace environment?

Usage

is_namespace(x)

Arguments

x

An object to test.


Is object a node or pairlist?

Description

Usage

is_pairlist(x)is_node(x)is_node_list(x)

Arguments

x

Object to test.

Life cycle

These functions are experimental. We are still figuring out a goodnaming convention to refer to the different lisp-like lists in R.

See Also

is_call() tests for language nodes.


Is an object referencing another?

Description

There are typically two situations where two symbols may refer tothe same object.

Usage

is_reference(x, y)

Arguments

x,y

R objects.

Examples

# Reassigning an uncopyable object such as an environment creates a# reference:env <- env()ref <- envis_reference(ref, env)# Due to copy-on-write optimisation, a copied vector can# temporarily reference the original vector:vec <- 1:10copy <- vecis_reference(copy, vec)# Once you modify on of them, the copy is triggered in the# background and the objects cease to reference each other:vec[[1]] <- 100is_reference(copy, vec)

Is object a symbol?

Description

Is object a symbol?

Usage

is_symbol(x, name = NULL)

Arguments

x

An object to test.

name

An optional name or vector of names that the symbolshould match.


Is object identical to TRUE or FALSE?

Description

These functions bypass R's automatic conversion rules and checkthatx is literallyTRUE orFALSE.

Usage

is_true(x)is_false(x)

Arguments

x

object to test

Examples

is_true(TRUE)is_true(1)is_false(FALSE)is_false(0)

Is object a weak reference?

Description

Is object a weak reference?

Usage

is_weakref(x)

Arguments

x

An object to test.


Create a call

Description

[Deprecated]These functions are deprecated, please usecall2() andnew_call() instead.

Usage

lang(.fn, ..., .ns = NULL)

Arguments

.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.fn. Must be a stringor symbol.


Lastabort() error

Description

Usage

last_error()last_trace(drop = NULL)

Arguments

drop

Whether to drop technical calls. These are hidden fromusers by default, setdrop toFALSE to see the full backtrace.

See Also


Display last messages and warnings

Description

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.

Usage

last_warnings(n = NULL)last_messages(n = NULL)

Arguments

n

How many warnings or messages to display. Defaults to all.

Examples

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() : baz

Calllast_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()

See Also

last_error()


Collect dynamic dots in a list

Description

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.

Usage

list2(...)dots_list(  ...,  .named = FALSE,  .ignore_empty = c("trailing", "none", "all"),  .preserve_empty = FALSE,  .homonyms = c("keep", "first", "last", "error"),  .check_assign = FALSE)

Arguments

...

Arguments to collect in a list. These dots aredynamic.

.named

IfTRUE, unnamed inputs are automatically namedwithas_label(). This is equivalent to applyingexprs_auto_name() on the result. IfFALSE, unnamed elementsare left as is and, if fully unnamed, the list is given minimalnames (a vector of""). IfNULL, fully unnamed results areleft withNULL names.

.ignore_empty

Whether to ignore empty arguments. Can be oneof"trailing","none","all". If"trailing", only thelast argument is ignored if it is empty.

.preserve_empty

Whether to preserve the empty arguments thatwere not ignored. IfTRUE, empty arguments are stored withmissing_arg() values. IfFALSE (the default) an error isthrown when an empty argument is detected.

.homonyms

How to treat arguments with the same name. Thedefault,"keep", preserves these arguments. Set.homonyms to"first" to only keep the first occurrences, to"last" to keepthe last occurrences, and to"error" to raise an informativeerror and indicate what arguments have duplicated names.

.check_assign

Whether to check for⁠<-⁠ calls. WhenTRUE awarning recommends users to use= if they meant to match afunction parameter or wrap the⁠<-⁠ call in curly braces otherwise.This ensures assignments are explicit.

Details

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.

Value

A list containing the... inputs.

Examples

# 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 })

Temporarily change bindings of an environment

Description

Usage

local_bindings(..., .env = .frame, .frame = caller_env())with_bindings(.expr, ..., .env = caller_env())

Arguments

...

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.

Value

local_bindings() returns the values of old bindingsinvisibly;with_bindings() returns the value ofexpr.

Examples

foo <- "foo"bar <- "bar"# `foo` will be temporarily rebinded while executing `expr`with_bindings(paste(foo, bar), foo = "rebinded")paste(foo, bar)

Set local error call in an execution environment

Description

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().

Usage

local_error_call(call, frame = caller_env())

Arguments

call

This can be:

  • A call to be used as context for an error thrown in thatexecution environment.

  • TheNULL value to show no context.

  • An execution environment, e.g. as returned bycaller_env().Thesys.call() for that environment is taken as context.

frame

The execution environment in which to set the localerror call.

Motivation for setting local error calls

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.

Error call flags in performance-critical functions

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"

Examples

# 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())}

Change global options

Description

Usage

local_options(..., .frame = caller_env())with_options(.expr, ...)push_options(...)peek_options(...)peek_option(name)

Arguments

...

Forlocal_options() andpush_options(), namedvalues defining new option values. Forpeek_options(), stringsor character vectors of option names.

.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.

Value

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.

Life cycle

These functions are experimental.

Examples

# 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")

Use cli to format error messages

Description

[Experimental]

local_use_cli() marks a package namespace or the environment of arunning function with a special flag that instructsabort() touse cli to format error messages. This formatting happens lazily,at print-time, in various places:

cli formats messages and bullets with indentation andwidth-wrapping to produce a polished display of messages.

Usage

local_use_cli(..., format = TRUE, inline = FALSE, frame = caller_env())

Arguments

...

These dots are for future extensions and must be empty.

format

Whether to use cli at print-time to format messagesand bullets.

inline

[Experimental] Whether to usecli at throw-time to format the inline parts of a message. Thismakes it possible to use cli interpolation and formatting withabort().

frame

A package namespace or an environment of a runningfunction.

Usage

To use cli formatting automatically in your package:

  1. Make surerun_on_load() is called from your.onLoad() hook.

  2. Callon_load(local_use_cli()) at the top level of your namespace.

It is also possible to calllocal_use_cli() inside a runningfunction, in which case the flag only applies within that function.


Missing values

Description

[Questioning]

Missing values are represented in R with the general symbolNA. They can be inserted in almost all data containers: allatomic vectors except raw vectors can contain missing values. Toachieve this, R automatically converts the generalNA symbol to atyped missing value appropriate for the target vector. The objectsprovided here are aliases for those typedNA objects.

Usage

na_lglna_intna_dblna_chrna_cpl

Format

An object of classlogical of length 1.

An object of classinteger of length 1.

An object of classnumeric of length 1.

An object of classcharacter of length 1.

An object of classcomplex of length 1.

Details

Typed missing values are necessary because R needs sentinel valuesof the same type (i.e. the same machine representation of the data)as the containers into which they are inserted. The official typedmissing values areNA_integer_,NA_real_,NA_character_ andNA_complex_. The missing value for logical vectors is simply thedefaultNA. The aliases provided in rlang are consistently namedand thus simpler to remember. Also,na_lgl is provided as analias toNA that makes intent clearer.

Sincena_lgl is the defaultNA, expressions such asc(NA, NA)yield logical vectors as no data is available to give a clue of thetarget type. In the same way, since lists and environments cancontain any types, expressions likelist(NA) store a logicalNA.

Life cycle

These shortcuts might be moved to the vctrs package at somepoint. This is why they are marked as questioning.

Examples

typeof(NA)typeof(na_lgl)typeof(na_int)# Note that while the base R missing symbols cannot be overwritten,# that's not the case for rlang's aliases:na_dbl <- NAtypeof(na_dbl)

Generate or handle a missing argument

Description

These functions help using the missing argument as a regular Robject.

Usage

missing_arg()is_missing(x)maybe_missing(x, default = missing_arg())

Arguments

x

An object that might be the missing argument.

default

The object to return if the input is missing,defaults tomissing_arg().

Other ways to reify the missing argument

is_missing() and default arguments

The 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.

Fragility of the missing argument object

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.

Examples

# 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]])

Get names of a vector

Description

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 variant⁠names2<-⁠ never addsNA names andinstead fills unnamed vectors with"".

Usage

names2(x)names2(x) <- value

Arguments

x

A vector.

value

New names.

Examples

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)

Inform about name repair

Description

Inform about name repair

Usage

names_inform_repair(old, new)

Arguments

old

Original names vector.

new

Repaired names vector.

Muffling and silencing messages

Name repair messages are signaled withinform() and are given the class"rlib_message_name_repair". These messages can be muffled withbase::suppressMessages().

Name repair messages can also be silenced with the global optionrlib_name_repair_verbosity. This option takes the values:

When set to quiet, the message is not displayed and the condition is notsignaled. This is particularly useful for silencing messages during testingwhen combined withlocal_options().


Create vectors matching a given length

Description

[Questioning]

These functions construct vectors of a given length, with attributesspecified via dots. Except fornew_list() andnew_raw(), theempty vectors are filled with typedmissing values. This is incontrast to the base functionbase::vector() which createszero-filled vectors.

Usage

new_logical(n, names = NULL)new_integer(n, names = NULL)new_double(n, names = NULL)new_character(n, names = NULL)new_complex(n, names = NULL)new_raw(n, names = NULL)new_list(n, names = NULL)

Arguments

n

The vector length.

names

Names for the new vector.

Lifecycle

These functions are likely to be replaced by a vctrs equivalent inthe future. They are in the questioning lifecycle stage.

See Also

rep_along

Examples

new_list(10)new_logical(10)

Create a new call from components

Description

Create a new call from components

Usage

new_call(car, cdr = NULL)

Arguments

car

The head of the call. It should be acallable object: a symbol, call, or literalfunction.

cdr

The tail of the call, i.e. apairlist ofarguments.


Create a formula

Description

Create a formula

Usage

new_formula(lhs, rhs, env = caller_env())

Arguments

lhs,rhs

A call, name, or atomic vector.

env

An environment.

Value

A formula object.

See Also

new_quosure()

Examples

new_formula(quote(a), quote(b))new_formula(NULL, quote(b))

Create a function

Description

This constructs a new function given its three components:list of arguments, body code and parent environment.

Usage

new_function(args, body, env = caller_env())

Arguments

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 functionpairlist2(). If you need quoteddefaults, useexprs().

body

A language object representing the code inside thefunction. Usually this will be most easily generated withbase::quote()

env

The parent environment of the function, defaults to thecalling environment ofnew_function()

Examples

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))

Helpers for pairlist and language nodes

Description

Important: These functions are for expert R programmers only.You should only use them if you feel comfortable manipulating lowlevel R data structures at the C level. We export them at the R levelin order to make it easy to prototype C code. They don't performany type checking and can crash R very easily (try to take the CARof an integer vector — save any important objects beforehand!).

Usage

new_node(car, cdr = NULL)node_car(x)node_cdr(x)node_caar(x)node_cadr(x)node_cdar(x)node_cddr(x)node_poke_car(x, newcar)node_poke_cdr(x, newcdr)node_poke_caar(x, newcar)node_poke_cadr(x, newcar)node_poke_cdar(x, newcdr)node_poke_cddr(x, newcdr)node_tag(x)node_poke_tag(x, newtag)

Arguments

car,newcar,cdr,newcdr

The new CAR or CDR for the node. Thesecan be any R objects.

x

A language or pairlist node. Note that these functions arebarebones and do not perform any type checking.

newtag

The new tag for the node. This should be a symbol.

Value

Setters likenode_poke_car() invisibly returnx modifiedin place. Getters return the requested node component.

See Also

duplicate() for creating copy-safe objects andbase::pairlist() for an easier way of creating a linked list ofnodes.


Create a quosure from components

Description

Usage

new_quosure(expr, env = caller_env())as_quosure(x, env = NULL)is_quosure(x)

Arguments

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.

See Also

Examples

# `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)))

Create a list of quosures

Description

This small S3 class provides methods for[ andc() and ensuresthe following invariants:

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.

Usage

new_quosures(x)as_quosures(x, env, named = FALSE)is_quosures(x)

Arguments

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 withquos_auto_name().


Create a weak reference

Description

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.

Usage

new_weakref(key, value = NULL, finalizer = NULL, on_quit = FALSE)

Arguments

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 beNULL, if youwant to use the weak reference like a weak pointer.

finalizer

A function that is run after the key becomes unreachable.

on_quit

Should the finalizer be run when R exits?

See Also

is_weakref(),wref_key() andwref_value().

Examples

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)

Get the namespace of a package

Description

Namespaces are the environment where all the functions of a packagelive. The parent environments of namespaces are theimportsenvironments, which contain all the functions imported from otherpackages.

Usage

ns_env(x = caller_env())ns_imports_env(x = caller_env())ns_env_name(x = caller_env())

Arguments

x
  • Forns_env(), the name of a package or an environment as astring.

    • An environment (the current environment by default).

    • A function.

    In the latter two cases, the environment ancestry is searched fora namespace withbase::topenv(). If the environment doesn'tinherit from a namespace, this is an error.

See Also

pkg_env()


Return the namespace registry env

Description

Note that the namespace registry does not behave like a normalenvironment because the parent isNULL instead of the emptyenvironment. This is exported for expert usage in development toolsonly.

Usage

ns_registry_env()

Address of an R object

Description

Address of an R object

Usage

obj_address(x)

Arguments

x

Any R object.

Value

Its address in memory in a string.


Run expressions on load

Description

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.

Usage

on_load(expr, env = parent.frame(), ns = topenv(env))run_on_load(ns = topenv(parent.frame()))on_package_load(pkg, expr, env = parent.frame())

Arguments

expr

An expression to run on load.

env

The environment in which to evaluateexpr. Defaults tothe current environment, which is your package namespace if yourunon_load() at top level.

ns

The namespace in which to hookexpr.

pkg

Package to hook expression into.

When should I run expressions on load?

There are two main use cases for running expressions on load:

  1. When a side effect, such as registering a method withs3_register(), must occur in the user session rather than thepackage builder session.

  2. 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.

Comparison with.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 analysisby⁠R CMD check⁠. Therefore, it is advisable to only usesimple function calls insideon_load().

Examples

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"))})

Infix attribute accessor and setter

Description

This operator extracts or sets attributes for regular objects andS4 fields for S4 objects.

Usage

x %@% namex %@% name <- value

Arguments

x

Object

name

Attribute name

value

New value for attributename.

Examples

# 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

Replace missing values

Description

Note: This operator is now out of scope for rlang. It will bereplaced by a vctrs-powered operator (probably in thefuns package) at which point therlang version of⁠%|%⁠ will be deprecated.

This infix function is similar to%||% but is vectorisedand provides a default value for missing elements. It is fasterthan usingbase::ifelse() and does not perform type conversions.

Usage

x %|% y

Arguments

x

The original values.

y

The replacement values. Must be of length 1 or the same length asx.

See Also

op-null-default

Examples

c("a", "b", NA, "c") %|% "default"c(1L, NA, 3L, NA, NA) %|% (6L:10L)

Default value forNULL

Description

This infix function makes it easy to replaceNULLs with a defaultvalue. It's inspired by the way that Ruby's or operation (||)works.

Usage

x %||% y

Arguments

x,y

Ifx is NULL, will returny; otherwise returnsx.

Examples

1 %||% 2NULL %||% 2

Collect dynamic dots in a pairlist

Description

This pairlist constructor usesdynamic dots. Useit to manually create argument lists for calls or parameter listsfor functions.

Usage

pairlist2(...)

Arguments

...

<dynamic> Arguments stored in thepairlist. Empty arguments are preserved.

Examples

# 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))

Parse R code

Description

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.

Usage

parse_expr(x)parse_exprs(x)parse_quo(x, env)parse_quos(x, env)

Arguments

x

Text containing expressions to parse_expr forparse_expr() andparse_exprs(). Can also be an R connection,for instance to a file. If the supplied connection is not open,it will be automatically closed and destroyed.

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).

Details

Unlikebase::parse(), these functions never retain source referenceinformation, as doing so is slow and rarely necessary.

Value

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.

See Also

base::parse()

Examples

# 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))

Name of a primitive function

Description

Name of a primitive function

Usage

prim_name(prim)

Arguments

prim

A primitive function such asbase::c().


Show injected expression

Description

qq_show() helps examininginjected expressionsinside a function. This is useful for learning about injection andfor debugging injection code.

Arguments

expr

An expression involvinginjection operators.

Examples

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))

See Also


Squash a quosure

Description

[Deprecated]This function is deprecated, please usequo_squash() instead.

Usage

quo_expr(quo, warn = FALSE)

Arguments

quo

A quosure or expression.

warn

Whether to warn if the quosure contains other quosures(those will be collapsed). This is useful when you usequo_squash() in order to make a non-tidyeval API compatiblewith quosures. In that case, getting rid of the nested quosuresis likely to cause subtle bugs and it is good practice to warnthe user about it.


Format quosures for printing or labelling

Description

[Superseded]

Note: You should now useas_label() oras_name() insteadofquo_name(). See life cycle section below.

These functions take an arbitrary R object, typically anexpression, and represent it as a string.

These deparsers are only suitable for creating default names orprinting output at the console. The behaviour of your functionsshould not depend on deparsed objects. If you are looking for a wayof transforming symbols to strings, useas_string() instead ofquo_name(). Unlike deparsing, the transformation between symbolsand strings is non-lossy and well defined.

Usage

quo_label(quo)quo_text(quo, width = 60L, nlines = Inf)quo_name(quo)

Arguments

quo

A quosure or expression.

width

Width of each line.

nlines

Maximum number of lines to extract.

Life cycle

These functions are superseded.

See Also

expr_label(),f_label()

Examples

# Quosures can contain nested quosures:quo <- quo(foo(!! quo(bar)))quo# quo_squash() unwraps all quosures and returns a raw expression:quo_squash(quo)# This is used by quo_text() and quo_label():quo_text(quo)# Compare to the unwrapped expression:expr_text(quo)# quo_name() is helpful when you need really short labels:quo_name(quo(sym))quo_name(quo(!! sym))

Squash a quosure

Description

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.

Usage

quo_squash(quo, warn = FALSE)

Arguments

quo

A quosure or expression.

warn

Whether to warn if the quosure contains other quosures(those will be collapsed). This is useful when you usequo_squash() in order to make a non-tidyeval API compatiblewith quosures. In that case, getting rid of the nested quosuresis likely to cause subtle bugs and it is good practice to warnthe user about it.

Examples

# 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)

Quosure getters, setters and predicates

Description

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.

Allquo_ prefixed functions expect a quosure and will fail ifsupplied another type of object. Make sure the input is a quosurewithis_quosure().

Usage

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)

Arguments

quo

A quosure to test.

name

The name of the symbol or function call. IfNULL thename is not tested.

n

An optional number of arguments that the call shouldmatch.

ns

The namespace of the call. IfNULL, the namespacedoesn't participate in the pattern-matching. If an empty string"" andx is a namespaced call,is_call() returnsFALSE. If any other string,is_call() checks thatx isnamespaced withinns.

Can be a character vector of namespaces, in which case the callhas to match at least one of them, otherwiseis_call() returnsFALSE.

expr

A new expression for the quosure.

env

A new environment for the quosure.

Empty quosures and missing arguments

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().

See Also

Examples

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())

Serialize a raw vector to a string

Description

[Experimental]

This function converts a raw vector to a hexadecimal string,optionally adding a prefix and a suffix.It is roughly equivalent topaste0(prefix, paste(format(x), collapse = ""), suffix)and much faster.

Usage

raw_deparse_str(x, prefix = NULL, suffix = NULL)

Arguments

x

A raw vector.

prefix,suffix

Prefix and suffix strings, or 'NULL.

Value

A string.

Examples

raw_deparse_str(raw())raw_deparse_str(charToRaw("string"))raw_deparse_str(raw(10), prefix = "'0x", suffix = "'")

Create vectors matching the length of a given vector

Description

These functions take the idea ofseq_along() and apply it torepeating values.

Usage

rep_along(along, x)rep_named(names, x)

Arguments

along

Vector whose length determine how many timesxis repeated.

x

Values to repeat.

names

Names for the new vector. The length ofnamesdetermines how many timesx is repeated.

See Also

new-vector

Examples

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))

Jump to or from a frame

Description

[Questioning]

Whilebase::return() can only return from the current localframe,return_from() will return from any frame on thecurrent evaluation stack, between the global and the currentlyactive context.

Usage

return_from(frame, value = NULL)

Arguments

frame

An execution environment of a currently runningfunction.

value

The return value.

Examples

fn <- function() {  g(current_env())  "ignored"}g <- function(env) {  h(env)  "ignored"}h <- function(env) {  return_from(env, "early return")  "ignored"}fn()

Display backtrace on error

Description

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:

rlang errors are normally thrown withabort(). If you promotebase errors to rlang errors withglobal_entrace(),rlang_backtrace_on_error applies to all errors.

Promote base errors to rlang errors

You can useoptions(error = rlang::entrace) to promote base errors torlang errors. This does two things:

Warnings and errors in RMarkdown

The display of errors depends on whether they're expected (i.e.chunk optionerror = TRUE) or unexpected:

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")```

See Also

rlang_backtrace_on_warning

Examples

# Display a simplified backtrace on error for both base and rlang# errors:# options(#   rlang_backtrace_on_error = "branch",#   error = rlang::entrace# )# stop("foo")

Errors of classrlang_error

Description

abort() anderror_cnd() create errors of class"rlang_error".The differences with base errors are:


Backtrace specification

Description

[Experimental]

Structure

An r-lib backtrace is a data frame that contains the followingcolumns:

A backtrace data frame may contain extra columns. If you addadditional columns, make sure to prefix their names with the nameof your package or organisation to avoid potential conflicts withfuture extensions of this spec, e.g."mypkg_column".

Operations


Scalar type predicates

Description

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.

Usage

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)

Arguments

x

object to be tested.

string

A string to compare tox. If a character vector,returnsTRUE if at least one element is equal tox.

See Also

type-predicates,bare-type-predicates


Deprecatedscoped functions

Description

[Deprecated]

These functions are deprecated as of rlang 0.3.0. Please useis_attached() instead.

Usage

scoped_env(nm)is_scoped(nm)

Arguments

nm

The name of an environment attached to the searchpath. Callbase::search() to see what is currently on the path.


Deprecatedscoped_ functions

Description

[Deprecated]

Deprecated as of rlang 0.4.2. Uselocal_interactive(),local_options(), orlocal_bindings() instead.

Usage

scoped_interactive(value = TRUE, frame = caller_env())scoped_options(..., .frame = caller_env())scoped_bindings(..., .env = .frame, .frame = caller_env())

Arguments

value

A singleTRUE orFALSE. This overrides the returnvalue ofis_interactive().

frame,.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.

...

Forlocal_options() andpush_options(), namedvalues defining new option values. Forpeek_options(), stringsor character vectors of option names.

.env

An environment.


Search path environments

Description

The search path is a chain of environments containing exportedfunctions of attached packages.

The API includes:

Usage

search_envs()search_env(name)pkg_env(pkg)pkg_env_name(pkg)is_attached(x)base_env()global_env()

Arguments

name

The name of an environment attached to the searchpath. Callbase::search() to get the names of environmentscurrently attached to the search path. Note that the search nameof a package environment is prefixed with"package:".

pkg

The name of a package.

x

An environment or a search name.

The search path

This chain of environments determines what objects are visible fromthe global workspace. It contains the following elements:

Examples

# List the search names of environments attached to the search path:search()# Get the corresponding environments:search_envs()# The global environment and the base package are always first and# last in the chain, respectively:envs <- search_envs()envs[[1]]envs[[length(envs)]]# These two environments have their own shortcuts:global_env()base_env()# Packages appear in the search path with a special name. Use# pkg_env_name() to create that name:pkg_env_name("rlang")search_env(pkg_env_name("rlang"))# Alternatively, get the scoped environment of a package with# pkg_env():pkg_env("utils")

Increasing sequence of integers in an interval

Description

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.

Usage

seq2(from, to)seq2_along(from, x)

Arguments

from

The starting point of the sequence.

to

The end point.

x

A vector whose length is the end point.

Value

An integer vector containing a strictly increasingsequence.

Examples

seq2(2, 10)seq2(10, 2)seq(10, 2)seq2_along(10, letters)

Add attributes to an object

Description

[Deprecated]

Usage

set_attrs(.x, ...)

Arguments

.x,...

[Deprecated]


Set and get an expression

Description

These helpers are useful to make your function work genericallywith quosures and raw expressions. First callget_expr() toextract an expression. Once you're done processing the expression,callset_expr() on the original object to update the expression.You can return the result ofset_expr(), either a formula or anexpression depending on the input type. Note thatset_expr() doesnot change its input, it creates a new object.

Usage

set_expr(x, value)get_expr(x, default = x)

Arguments

x

An expression, closure, or one-sided formula. In addition,set_expr() accept frames.

value

An updated expression.

default

A default expression to return whenx is not anexpression wrapper. Defaults tox itself.

Value

The updated original input forset_expr(). A rawexpression forget_expr().

See Also

quo_get_expr() andquo_set_expr() for versions ofget_expr() andset_expr() that only work on quosures.

Examples

f <- ~foo(bar)e <- quote(foo(bar))frame <- identity(identity(ctxt_frame()))get_expr(f)get_expr(e)get_expr(frame)set_expr(f, quote(baz))set_expr(e, quote(baz))

Set names of a vector

Description

This is equivalent tostats::setNames(), with more features andstricter argument checking.

Usage

set_names(x, nm = x, ...)

Arguments

x

Vector to name.

nm,...

Vector of names, the same length asx. If length 1,nm is recycled to the length ofx following the recyclingrules of the tidyverse..

You can specify names in the following ways:

  • If not supplied,x will be named toas.character(x).

  • Ifx already has names, you can provide a function or formulato transform the existing names. In that case,... is passedto the function.

  • Otherwise if... is supplied,x is named toc(nm, ...).

  • Ifnm isNULL, the names are removed (if present).

Life cycle

set_names() is stable and exported in purrr.

Examples

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 ommitted 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 values at dots collection time

Description

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.

Usage

splice(x)is_spliced(x)is_spliced_bare(x)

Arguments

x

A list or vector to splice non-eagerly.


Splice operator⁠!!!⁠

Description

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:

Where does⁠!!!⁠ work?

⁠!!!⁠ does not work everywhere, you can only use it within certainspecial functions:

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?.

Splicing a list of arguments

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 b

Note 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

Splicing a list of expressions

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)))}

Performance of injected dots and dynamic dots

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().

See Also


Get properties of the current or caller frame

Description

These accessors retrieve properties of frames on the call stack.The prefix indicates for which frame a property should be accessed:

The suffix indicates which property to retrieve:

Usage

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())

Arguments

n

The number of callers to go back.

frame

A frame environment of a currently running function,as returned bycaller_env().NULL is returned if theenvironment does not exist on the stack.

See Also

caller_env() andcurrent_env()


Call stack information

Description

[Deprecated]Deprecated as of rlang 0.3.0.

Usage

ctxt_frame(n = 1)global_frame()

Arguments

n

The number of frames to go back in the stack.


Create a string

Description

[Experimental]

These base-type constructors allow more control over the creationof strings in R. They take character vectors or string-like objects(integerish or raw vectors), and optionally set the encoding. Thestring version checks that the input contains a scalar string.

Usage

string(x, encoding = NULL)

Arguments

x

A character vector or a vector or list of string-likeobjects.

encoding

If non-null, set an encoding mark. This is onlydeclarative, no encoding conversion is performed.

Examples

# As everywhere in R, you can specify a string with Unicode# escapes. The characters corresponding to Unicode codepoints will# be encoded in UTF-8, and the string will be marked as UTF-8# automatically:cafe <- string("caf\uE9")Encoding(cafe)charToRaw(cafe)# In addition, string() provides useful conversions to let# programmers control how the string is represented in memory. For# encodings other than UTF-8, you'll need to supply the bytes in# hexadecimal form. If it is a latin1 encoding, you can mark the# string explicitly:cafe_latin1 <- string(c(0x63, 0x61, 0x66, 0xE9), "latin1")Encoding(cafe_latin1)charToRaw(cafe_latin1)

Dispatch on base types

Description

[Soft-deprecated][Experimental]

switch_type() is equivalent toswitch(type_of(x, ...)), whileswitch_class() switchpatches based onclass(x). Thecoerce_versions are intended for type conversion and provide a standarderror message when conversion fails.

Usage

switch_type(.x, ...)coerce_type(.x, .to, ...)switch_class(.x, ...)coerce_class(.x, .to, ...)

Arguments

.x

An object from which to dispatch.

...

Named clauses. The names should be types as returned bytype_of().

.to

This is useful when you switchpatch within a coercingfunction. If supplied, this should be a string indicating thetarget type. A catch-all clause is then added to signal an errorstating the conversion failure. This type is prettified unless.to inherits from the S3 class"AsIs" (seebase::I()).

Examples

switch_type(3L,  double = "foo",  integer = "bar",  "default")# Use the coerce_ version to get standardised error handling when no# type matches:to_chr <- function(x) {  coerce_type(x, "a chr",    integer = as.character(x),    double = as.character(x)  )}to_chr(3L)# Strings have their own type:switch_type("str",  character = "foo",  string = "bar",  "default")# Use a fallthrough clause if you need to dispatch on all character# vectors, including strings:switch_type("str",  string = ,  character = "foo",  "default")# special and builtin functions are treated as primitive, since# there is usually no reason to treat them differently:switch_type(base::list,  primitive = "foo",  "default")switch_type(base::`$`,  primitive = "foo",  "default")# closures are not primitives:switch_type(rlang::switch_type,  primitive = "foo",  "default")

Create a symbol or list of symbols

Description

Symbols are a kind ofdefused expression thatrepresent objects in environments.

Only tidy eval APIs support the.data pronoun. With base Rfunctions, use simple symbols created withsym() orsyms().

Usage

sym(x)syms(x)data_sym(x)data_syms(x)

Arguments

x

Forsym() anddata_sym(), a string. Forsyms() anddata_syms(), a list of strings.

Value

Forsym() andsyms(), a symbol or list of symbols. Fordata_sym() anddata_syms(), calls of the form.data$foo.

See Also

Examples

# 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()))

Customising condition messages

Description

Various aspects of the condition messages displayed byabort(),warn(), andinform() can be customised using options from thecli package.

Turning off unicode bullets

By default, bulleted lists are prefixed with unicode symbols:

rlang::abort(c(  "The error message.",  "*" = "Regular bullet.",  "i" = "Informative bullet.",  "x" = "Cross bullet.",  "v" = "Victory bullet.",  ">" = "Arrow bullet."))#> Error:#> ! The error message.#> • Regular bullet.#> ℹ Informative bullet.#> ✖ Cross bullet.#> ✔ Victory bullet.#> → Arrow bullet.

Set this option to use simple letters instead:

options(cli.condition_unicode_bullets = FALSE)rlang::abort(c(  "The error message.",  "*" = "Regular bullet.",  "i" = "Informative bullet.",  "x" = "Cross bullet.",  "v" = "Victory bullet.",  ">" = "Arrow bullet."))#> Error:#> ! The error message.#> * Regular bullet.#> i Informative bullet.#> x Cross bullet.#> v Victory bullet.#> > Arrow bullet.

Changing the bullet symbols

You can specify what symbol to use for each type of bullet through your cli user theme. For instance, here is how to uniformly use* for all bullet kinds:

options(cli.user_theme = list(  ".cli_rlang .bullet-*" = list(before = "* "),  ".cli_rlang .bullet-i" = list(before = "* "),  ".cli_rlang .bullet-x" = list(before = "* "),  ".cli_rlang .bullet-v" = list(before = "* "),  ".cli_rlang .bullet->" = list(before = "* ")))rlang::abort(c(  "The error message.",  "*" = "Regular bullet.",  "i" = "Informative bullet.",  "x" = "Cross bullet.",  "v" = "Victory bullet.",  ">" = "Arrow bullet."))#> Error:#> ! The error message.#> * Regular bullet.#> * Informative bullet.#> * Cross bullet.#> * Victory bullet.#> * Arrow bullet.

If you want all the bullets to be the same, including the leading bullet, you can achieve this using thebullet class:

options(cli.user_theme = list(  ".cli_rlang .bullet" = list(before = "* ")))rlang::abort(c(  "The error message.",  "*" = "Regular bullet.",  "i" = "Informative bullet.",  "x" = "Cross bullet.",  "v" = "Victory bullet.",  ">" = "Arrow bullet."))#> Error:#> * The error message.#> * Regular bullet.#> * Informative bullet.#> * Cross bullet.#> * Victory bullet.#> * Arrow bullet.

Changing the foreground and background colour of error calls

When called inside a function,abort() displays the function call to help contextualise the error:

splash <- function() {  abort("Can't splash without water.")}splash()#> Error in `splash()`:#> ! Can't splash without water.

The call is formatted with cli as acode element. This is not visible in the manual, but code text is formatted with a highlighted background colour by default. When this can be reliably detected, that background colour is different depending on whether you're using a light or dark theme.

You can override the colour of code elements in your cli theme. Here is my personal configuration that fits well with the colour theme I currently use in my IDE:

options(cli.user_theme = list(  span.code = list(    "background-color" = "#3B4252",    color = "#E5E9F0"  )))

Formatting messages with cli

Description

Condition formatting is a set of operations applied to raw inputs for error messages that includes:

While the rlang package embeds rudimentary formatting routines, the main formatting engine is implemented in thecli package.

Formatting messages with cli

By default, rlang uses an internal mechanism to format bullets. It is preferable to delegate formatting to thecli package by usingcli::cli_abort(),cli::cli_warn(), andcli::cli_inform() instead of the rlang versions. These wrappers enable cli formatting with sophisticated paragraph wrapping and bullet indenting that make long lines easier to read. In the following example, a long! bullet is broken with an indented newline:

rlang::global_entrace(class = "errorr")#> Error in `rlang::global_entrace()`:#> ! `class` must be one of "error", "warning", or "message",#>   not "errorr".#> i Did you mean "error"?

The cli wrappers also add many features such as interpolation, semantic formatting of text elements, and pluralisation:

inform_marbles <- function(n_marbles) {  cli::cli_inform(c(    "i" = "I have {n_marbles} shiny marble{?s} in my bag.",    "v" = "Way to go {.code cli::cli_inform()}!"  ))}inform_marbles(1)#> i I have 1 shiny marble in my bag.#> v Way to go `cli::cli_inform()`!inform_marbles(2)#> i I have 2 shiny marbles in my bag.#> v Way to go `cli::cli_inform()`!

Transitioning fromabort() tocli_abort()

If you plan to mass-rename calls fromabort() tocli::cli_abort(), be careful if you assemble error messages from user inputs. If these individual pieces contain cli or glue syntax, this will result in hard-to-debug errors and possiblyunexpected behaviour.

user_input <- "{base::stop('Wrong message.', call. = FALSE)}"cli::cli_abort(sprintf("Can't handle input `%s`.", user_input))#> Error:#> ! ! Could not evaluate cli `{}` expression: `base::stop('Wrong...`.#> Caused by error: #> ! Wrong message.

To avoid this, protect your error messages by using cli to assemble the pieces:

user_input <- "{base::stop('Wrong message.', call. = FALSE)}"cli::cli_abort("Can't handle input {.code {user_input}}.")#> Error:#> ! Can't handle input `{base::stop('Wrong message.', call. = FALSE)}`.

Enabling cli formatting globally

To enable cli formatting for allabort() calls in your namespace, calllocal_use_cli() in theonLoad hook of your package. Usingon_load() (make sure to callrun_on_load() in your hook):

on_load(local_use_cli())

Enabling cli formatting inabort() is useful for:


What is data-masking and why do I need⁠{{⁠?

Description

Data-masking is a distinctive feature of R whereby programming is performed directly on a data set, with columns defined as normal objects.

# Unmasked programmingmean(mtcars$cyl + mtcars$am)#> [1] 6.59375# Referring to columns is an error - Where is the data?mean(cyl + am)#> Error:#> ! object 'cyl' not found# Data-maskingwith(mtcars, mean(cyl + am))#> [1] 6.59375

While data-masking makes it easy to program interactively with data frames, it makes it harder to create functions. Passing data-masked arguments to functions requires injection with the embracing operator{{ or, in more complex cases, the injection operator!!.

Why does data-masking require embracing and injection?

Injection (also known as quasiquotation) is a metaprogramming feature that allows you to modify parts of a program. This is needed because under the hood data-masking works bydefusing R code to prevent its immediate evaluation. The defused code is resumed later on in a context where data frame columns are defined.

Let's see what happens when we pass arguments to a data-masking function likesummarise() in the normal way:

my_mean <- function(data, var1, var2) {  dplyr::summarise(data, mean(var1 + var2))}my_mean(mtcars, cyl, am)#> Error in `dplyr::summarise()`:#> i In argument: `mean(var1 + var2)`.#> Caused by error:#> ! object 'cyl' not found

The problem here is thatsummarise() defuses the R code it was supplied, i.e.mean(var1 + var2). Instead we want it to seemean(cyl + am). This is why we need injection, we need to modify that piece of code by injecting the code supplied to the function in place ofvar1 andvar2.

To inject a function argument in data-masked context, just embrace it with⁠{{⁠:

my_mean <- function(data, var1, var2) {  dplyr::summarise(data, mean({{ var1 }} + {{ var2 }}))}my_mean(mtcars, cyl, am)#> # A tibble: 1 x 1#>   `mean(cyl + am)`#>              <dbl>#> 1             6.59

SeeData mask programming patterns to learn more about creating functions around data-masking functions.

What does "masking" mean?

In normal R programming objects are defined in the current environment, for instance in the global environment or the environment of a function.

factor <- 1000# Can now use `factor` in computationsmean(mtcars$cyl * factor)#> [1] 6187.5

This environment also contains all functions currently in scope. In a script this includes the functions attached withlibrary() calls; in a package, the functions imported from other packages. If evaluation was performed only in the data frame, we'd lose track of these objects and functions necessary to perform computations.

To keep these objects and functions in scope, the data frame is inserted at the bottom of the current chain of environments. It comes first and has precedence over the user environment. In other words, itmasks the user environment.

Since masking blends the data and the user environment by giving priority to the former, R can sometimes use a data frame column when you really intended to use a local object.

# Defining an env-variablecyl <- 1000# Referring to a data-variabledplyr::summarise(mtcars, mean(cyl))#> # A tibble: 1 x 1#>   `mean(cyl)`#>         <dbl>#> 1        6.19

The tidy eval framework providespronouns to help disambiguate between the mask and user contexts. It is often a good idea to use these pronouns in production code.

cyl <- 1000mtcars %>%  dplyr::summarise(    mean_data = mean(.data$cyl),    mean_env = mean(.env$cyl)  )#> # A tibble: 1 x 2#>   mean_data mean_env#>       <dbl>    <dbl>#> 1      6.19     1000

Read more about this inThe data mask ambiguity.

How does data-masking work?

Data-masking relies on three language features:

History

The tidyverse embraced the data-masking approach in packages like ggplot2 and dplyr and eventually developed its own programming framework in the rlang package. None of this would have been possible without the following landmark developments from S and R authors.

See also


The data mask ambiguity

Description

Data masking is an R feature that blends programming variables that live inside environments (env-variables) with statistical variables stored in data frames (data-variables). This mixture makes it easy to refer to data frame columns as well as objects defined in the current environment.

x <- 100mtcars %>% dplyr::summarise(mean(disp / x))#> # A tibble: 1 x 1#>   `mean(disp/x)`#>            <dbl>#> 1           2.31

However this convenience introduces an ambiguity between data-variables and env-variables which might causecollisions.

Column collisions

In the following snippet, are we referring to the env-variablex or to the data-variable of the same name?

df <- data.frame(x = NA, y = 2)x <- 100df %>% dplyr::mutate(y = y / x)#>    x  y#> 1 NA NA

A column collision occurs when you want to use an object defined outside of the data frame, but a column of the same name happens to exist.

Object collisions

The opposite problem occurs when there is a typo in a data-variable name and an env-variable of the same name exists:

df <- data.frame(foo = "right")ffo <- "wrong"df %>% dplyr::mutate(foo = toupper(ffo))#>     foo#> 1 WRONG

Instead of a typo, it might also be that you were expecting a column in the data frame which is unexpectedly missing. In both cases, if a variable can't be found in the data mask, R looks for variables in the surrounding environment. This isn't what we intended here and it would have been better to fail early with a "Column not found" error.

Preventing collisions

In casual scripts or interactive programming, data mask ambiguity is not a huge deal compared to the payoff of iterating quickly while developing your analysis. However in production code and in package functions, the ambiguity might cause collision bugs in the long run.

Fortunately it is easy to be explicit about the scoping of variables with a little more verbose code. This topic lists the solutions and workarounds that have been created to solve ambiguity issues in data masks.

The.data and.env pronouns

The simplest solution is to use the.data and.env pronouns to disambiguate between data-variables and env-variables.

df <- data.frame(x = 1, y = 2)x <- 100df %>% dplyr::mutate(y = .data$y / .env$x)#>   x    y#> 1 1 0.02

This is especially useful in functions because the data frame is not known in advance and potentially contain masking columns for any of the env-variables in scope in the function:

my_rescale <- function(data, var, factor = 10) {  data %>% dplyr::mutate("{{ var }}" := {{ var }} / factor)}# This worksdata.frame(value = 1) %>% my_rescale(value)#>   value#> 1   0.1# Oh no!data.frame(factor = 0, value = 1) %>% my_rescale(value)#>   factor value#> 1      0   Inf

Subsetting function arguments with.env ensures we never hit a masking column:

my_rescale <- function(data, var, factor = 10) {  data %>% dplyr::mutate("{{ var }}" := {{ var }} / .env$factor)}# Yay!data.frame(factor = 0, value = 1) %>% my_rescale(value)#>   factor value#> 1      0   0.1
Subsetting.data with env-variables

The.data pronoun may be used as a name-to-data-mask pattern (seeData mask programming patterns):

var <- "cyl"mtcars %>% dplyr::summarise(mean = mean(.data[[var]]))#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19

In this example, the env-variablevar is used inside the data mask to subset the.data pronoun. Does this mean thatvar is at risk of a column collision if the input data frame contains a column of the same name? Fortunately not:

var <- "cyl"mtcars2 <- mtcarsmtcars2$var <- "wrong"mtcars2 %>% dplyr::summarise(mean = mean(.data[[var]]))#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19

The evaluation of.data[[var]] is set up in such a way that there is no ambiguity. The.data pronoun can only be subsetted with env-variables, not data-variables. Technically, this is because[[ behaves like aninjection operator when applied to.data. It is evaluated very early before the data mask is even created. See the⁠!!⁠ section below.

Injecting env-variables with⁠!!⁠

Injection operators such as!! have interesting properties regarding the ambiguity problem. They modify a piece of code early on by injecting objects or other expressions before any data-masking logic comes into play. If you inject thevalue of a variable, it becomes inlined in the expression. R no longer needs to look up any variable to find the value.

Taking the earlier division example, let's use⁠!!⁠ to inject the value of the env-variablex inside the division expression:

df <- data.frame(x = NA, y = 2)x <- 100df %>% dplyr::mutate(y = y / !!x)#>    x    y#> 1 NA 0.02

While injection solves issues of ambiguity, it is a bit heavy handed compared to using the.env pronoun. Big objects inlined in expressions might cause issues in unexpected places, for instance they might make the calls in atraceback() less readable.

No ambiguity in tidy selections

Tidy selection is a dialect of R that optimises column selection in tidyverse packages. Examples of functions that use tidy selections aredplyr::select() andtidyr::pivot_longer().

Unlike data masking, tidy selections do not suffer from ambiguity. The selection language is designed in such a way that evaluation of expressions is either scoped in the data mask only, or in the environment only. Take this example:

mtcars %>% dplyr::select(gear:ncol(mtcars))

gear is a symbol supplied to a selection operator: and thus scoped in the data mask only. Any other kind of expression, such asncol(mtcars), is evaluated as normal R code outside of any data context. This is why there is no column collision here:

data <- data.frame(x = 1, data = 1:3)data %>% dplyr::select(data:ncol(data))#>   data#> 1    1#> 2    2#> 3    3

It is useful to introduce two new terms. Tidy selections distinguish data-expressions and env-expressions:

To learn more about the difference between the two kinds of expressions, see thetechnical description of the tidy selection syntax.

Names pattern withall_of()

all_of() is often used in functions as aprogramming pattern that connects column names to a data mask, similarly to the.data pronoun. A simple example is:

my_group_by <- function(data, vars) {  data %>% dplyr::group_by(across(all_of(vars)))}

If tidy selections were affected by the data mask ambiguity, this function would be at risk of a column collision. It would break as soon as the user supplies a data frame containing avars column. However,all_of() is an env-expression that is evaluated outside of the data mask, so there is no possibility of collisions.


Data mask programming patterns

Description

Data-masking functions require special programming patterns when used inside other functions. In this topic we'll review and compare the different patterns that can be used to solve specific problems.

If you are a beginner, you might want to start with one of these tutorials:

If you'd like to go further and learn about defusing and injecting expressions, read themetaprogramming patterns topic.

Choosing a pattern

Two main considerations determine which programming pattern you need to wrap a data-masking function:

  1. What behaviour does thewrapped function implement?

  2. What behaviour shouldyour function implement?

Depending on the answers to these questions, you can choose between these approaches:

You will also need to use different solutions for single named arguments than for multiple arguments in....

Argument behaviours

In a regular function, arguments can be defined in terms of atype of objects that they accept. An argument might accept a character vector, a data frame, a single logical value, etc. Data-masked arguments are more complex. Not only do they generally accept a specific type of objects (for instancedplyr::mutate() accepts vectors), they exhibit special computational behaviours.

To let users know about the capabilities of your function arguments, document them with the following tags, depending on which set of semantics they inherit from:

@param foo <[`data-masked`][dplyr::dplyr_data_masking]> What `foo` does.@param bar <[`tidy-select`][dplyr::dplyr_tidy_select]> What `bar` does.@param ... <[`dynamic-dots`][rlang::dyn-dots]> What these dots do.

Forwarding patterns

With the forwarding patterns, arguments inherit the behaviour of the data-masked arguments they are passed in.

Embrace with⁠{{⁠

The embrace operator{{ is a forwarding syntax for single arguments. You can forward an argument in data-masked context:

my_summarise <- function(data, var) {  data %>% dplyr::summarise({{ var }})}

Or in tidyselections:

my_pivot_longer <- function(data, var) {  data %>% tidyr::pivot_longer(cols = {{ var }})}

The function automatically inherits the behaviour of the surrounding context. For instance arguments forwarded to a data-masked context may refer to columns or use the.data pronoun:

mtcars %>% my_summarise(mean(cyl))x <- "cyl"mtcars %>% my_summarise(mean(.data[[x]]))

And arguments forwarded to a tidy selection may use all tidyselect features:

mtcars %>% my_pivot_longer(cyl)mtcars %>% my_pivot_longer(vs:gear)mtcars %>% my_pivot_longer(starts_with("c"))x <- c("cyl", "am")mtcars %>% my_pivot_longer(all_of(x))

Forward...

Simple forwarding of... arguments does not require any special syntax since dots are already a forwarding syntax. Just pass them to another function like you normally would. This works with data-masked arguments:

my_group_by <- function(.data, ...) {  .data %>% dplyr::group_by(...)}mtcars %>% my_group_by(cyl = cyl * 100, am)

As well as tidy selections:

my_select <- function(.data, ...) {  .data %>% dplyr::select(...)}mtcars %>% my_select(starts_with("c"), vs:carb)

Some functions take a tidy selection in a single named argument. In that case, pass the... insidec():

my_pivot_longer <- function(.data, ...) {  .data %>% tidyr::pivot_longer(c(...))}mtcars %>% my_pivot_longer(starts_with("c"), vs:carb)

Inside a tidy selection,c() is not a vector concatenator but a selection combinator. This makes it handy to interface between functions that take... and functions that take a single argument.

Names patterns

With the names patterns you refer to columns by name with strings or character vectors stored in env-variables. Whereas the forwarding patterns are exclusively used within a function to passarguments, the names patterns can be used anywhere.

Subsetting the.data pronoun

The.data pronoun is a tidy eval feature that is enabled in all data-masked arguments, just like{{. The pronoun represents the data mask and can be subsetted with[[ and$. These three statements are equivalent:

mtcars %>% dplyr::summarise(mean = mean(cyl))mtcars %>% dplyr::summarise(mean = mean(.data$cyl))var <- "cyl"mtcars %>% dplyr::summarise(mean = mean(.data[[var]]))

The.data pronoun can be subsetted in loops:

vars <- c("cyl", "am")for (var in vars) print(dplyr::summarise(mtcars, mean = mean(.data[[var]])))#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1 0.406purrr::map(vars, ~ dplyr::summarise(mtcars, mean =  mean(.data[[.x]])))#> [[1]]#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19#> #> [[2]]#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1 0.406

And it can be used to connect function arguments to a data-variable:

my_mean <- function(data, var) {  data %>% dplyr::summarise(mean = mean(.data[[var]]))}my_mean(mtcars, "cyl")#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19

With this implementation,my_mean() is completely insulated from data-masking behaviour and is called like an ordinary function.

# No maskingam <- "cyl"my_mean(mtcars, am)#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19# Programmablemy_mean(mtcars, tolower("CYL"))#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19

Character vector of names

The.data pronoun can only be subsetted with single column names. It doesn't support single-bracket indexing:

mtcars %>% dplyr::summarise(.data[c("cyl", "am")])#> Error in `dplyr::summarise()`:#> i In argument: `.data[c("cyl", "am")]`.#> Caused by error in `.data[c("cyl", "am")]`:#> ! `[` is not supported by the `.data` pronoun, use `[[` or $ instead.

There is no plural variant of.data built in tidy eval. Instead, we'll used theall_of() operator available in tidy selections to supply character vectors. This is straightforward in functions that take tidy selections, liketidyr::pivot_longer():

vars <- c("cyl", "am")mtcars %>% tidyr::pivot_longer(all_of(vars))#> # A tibble: 64 x 11#>     mpg  disp    hp  drat    wt  qsec    vs  gear  carb name  value#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <dbl>#> 1    21   160   110   3.9  2.62  16.5     0     4     4 cyl       6#> 2    21   160   110   3.9  2.62  16.5     0     4     4 am        1#> 3    21   160   110   3.9  2.88  17.0     0     4     4 cyl       6#> 4    21   160   110   3.9  2.88  17.0     0     4     4 am        1#> # i 60 more rows

If the function does not take a tidy selection, it might be possible to use abridge pattern. This option is presented in the bridge section below. If a bridge is impossible or inconvenient, a little metaprogramming with thesymbolise-and-inject pattern can help.

Bridge patterns

Sometimes the function you are calling does not implement the behaviour you would like to give to the arguments of your function. To work around this may require a little thought since there is no systematic way of turning one behaviour into another. The general technique consists in forwarding the arguments inside a context that implements the behaviour that you want. Then, find a way to bridge the result to the target verb or function.

across() as a selection to data-mask bridge

dplyr 1.0 added support for tidy selections in all verbs viaacross(). This function is normally used for mapping over columns but can also be used to perform a simple selection. For instance, if you'd like to pass an argument togroup_by() with a tidy-selection interface instead of a data-masked one, useacross() as a bridge:

my_group_by <- function(data, var) {  data %>% dplyr::group_by(across({{ var }}))}mtcars %>% my_group_by(starts_with("c"))

Sinceacross() takes selections in a single argument (unlikeselect() which takes multiple arguments), you can't directly pass.... Instead, take them withinc(), which is the tidyselect way of supplying multiple selections within a single argument:

my_group_by <- function(.data, ...) {  .data %>% dplyr::group_by(across(c(...)))}mtcars %>% my_group_by(starts_with("c"), vs:gear)

across(all_of()) as a names to data mask bridge

If instead of forwarding variables inacross() you pass them toall_of(), you create a names to data mask bridge.

my_group_by <- function(data, vars) {  data %>% dplyr::group_by(across(all_of(vars)))}mtcars %>% my_group_by(c("cyl", "am"))

Use this bridge technique to connect vectors of names to a data-masked context.

transmute() as a data-mask to selection bridge

Passing data-masked arguments to a tidy selection is a little more tricky and requires a three step process.

my_pivot_longer <- function(data, ...) {  # Forward `...` in data-mask context with `transmute()`  # and save the inputs names  inputs <- dplyr::transmute(data, ...)  names <- names(inputs)    # Update the data with the inputs  data <- dplyr::mutate(data, !!!inputs)  # Select the inputs by name with `all_of()`  tidyr::pivot_longer(data, cols = all_of(names))}mtcars %>% my_pivot_longer(cyl, am = am * 100)
  1. In a first step we pass the... expressions totransmute(). Unlikemutate(), it creates a new data frame from the user inputs. The only goal of this step is to inspect the names in..., including the default names created for unnamed arguments.

  2. Once we have the names, we inject the arguments intomutate() to update the data frame.

  3. Finally, we pass the names to the tidy selection viaall_of().

Transformation patterns

Named inputs versus...

In the case of a named argument, transformation is easy. We simply surround the embraced input in R code. For instance, themy_summarise() function is not exactly useful compared to just callingsummarise():

my_summarise <- function(data, var) {  data %>% dplyr::summarise({{ var }})}

We can make it more useful by adding code around the variable:

my_mean <- function(data, var) {  data %>% dplyr::summarise(mean = mean({{ var }}, na.rm = TRUE))}

For inputs in... however, this technique does not work. We would need some kind of templating syntax for dots that lets us specify R code with a placeholder for the dots elements. This isn't built in tidy eval but you can use operators likedplyr::across(),dplyr::if_all(), ordplyr::if_any(). When that isn't possible, you can template the expression manually.

Transforming inputs withacross()

Theacross() operation in dplyr is a convenient way of mapping an expression across a set of inputs. We will create a variant ofmy_mean() that computes themean() of all arguments supplied in.... The easiest way it to forward the dots toacross() (which causes... to inherit its tidy selection behaviour):

my_mean <- function(data, ...) {  data %>% dplyr::summarise(across(c(...), ~ mean(.x, na.rm = TRUE)))}mtcars %>% my_mean(cyl, carb)#> # A tibble: 1 x 2#>     cyl  carb#>   <dbl> <dbl>#> 1  6.19  2.81mtcars %>% my_mean(foo = cyl, bar = carb)#> # A tibble: 1 x 2#>     foo   bar#>   <dbl> <dbl>#> 1  6.19  2.81mtcars %>% my_mean(starts_with("c"), mpg:disp)#> # A tibble: 1 x 4#>     cyl  carb   mpg  disp#>   <dbl> <dbl> <dbl> <dbl>#> 1  6.19  2.81  20.1  231.

Transforming inputs withif_all() andif_any()

dplyr::filter() requires a different operation thanacross() because it needs to combine the logical expressions with& or|. To solve this problem dplyr introduced theif_all() andif_any() variants ofacross().

In the following example, we filter all rows for which a set of variables are not equal to their minimum value:

filter_non_baseline <- function(.data, ...) {  .data %>% dplyr::filter(if_all(c(...), ~ .x != min(.x, na.rm = TRUE)))}mtcars %>% filter_non_baseline(vs, am, gear)

Defusing R expressions

Description

When a piece of R code is defused, R doesn't return its value like it normally would. Instead it returns the expression in a special tree-like object that describes how to compute a value. These defused expressions can be thought of as blueprints or recipes for computing values.

Usingexpr() we can observe the difference between computing an expression and defusing it:

# Return the result of `1 + 1`1 + 1#> [1] 2# Return the expression `1 + 1`expr(1 + 1)#> 1 + 1

Evaluation of a defused expression can be resumed at any time witheval() (see alsoeval_tidy()).

# Return the expression `1 + 1`e <- expr(1 + 1)# Return the result of `1 + 1`eval(e)#> [1] 2

The most common use case for defusing expressions is to resume its evaluation in adata mask. This makes it possible for the expression to refer to columns of a data frame as if they were regular objects.

e <- expr(mean(cyl))eval(e, mtcars)#> [1] 6.1875

Do I need to know about defused expressions?

As a tidyverse user you will rarely need to defuse expressions manually withexpr(), and even more rarely need to resume evaluation witheval() oreval_tidy(). Instead, you calldata-masking functions which take care of defusing your arguments and resuming them in the context of a data mask.

mtcars %>% dplyr::summarise(  mean(cyl)  # This is defused and data-masked)#> # A tibble: 1 x 1#>   `mean(cyl)`#>         <dbl>#> 1        6.19

It is important to know that a function defuses its arguments because it requires slightly different methods when called from a function. The main thing is that arguments must be transported with theembrace operator⁠{{⁠. It allows the data-masking function to defuse the correct expression.

my_mean <- function(data, var) {  dplyr::summarise(data, mean = mean({{ var }}))}

Read more about this in:

The booby trap analogy

The term "defusing" comes from an analogy to the evaluation model in R. As you may know, R uses lazy evaluation, which means that arguments are only evaluated when they are needed for a computation. Let's take two functions,ignore() which doesn't do anything with its argument, andforce() which returns it:

ignore <- function(arg) NULLforce <- function(arg) argignore(warning("boom"))#> NULLforce(warning("boom"))#> Warning in force(warning("boom")): boom

A warning is only emitted when the function actuallytriggers evaluation of its argument. Evaluation of arguments can be chained by passing them to other functions. If one of the functions ignores its argument, it breaks the chain of evaluation.

f <- function(x) g(x)g <- function(y) h(y)h <- function(z) ignore(z)f(warning("boom"))#> NULL

In a way, arguments are likebooby traps which explode (evaluate) when touched. Defusing an argument can be seen as defusing the booby trap.

expr(force(warning("boom")))#> force(warning("boom"))

Types of defused expressions

You can create new call or symbol objects by using the defusing functionexpr():

# Create a symbol representing objects called `foo`expr(foo)#> foo# Create a call representing the computation of the mean of `foo`expr(mean(foo, na.rm = TRUE))#> mean(foo, na.rm = TRUE)# Return a constantexpr(1)#> [1] 1expr(NULL)#> NULL

Defusing is not the only way to create defused expressions. You can also assemble them from data:

# Assemble a symbol from a stringvar <- "foo"sym(var)# Assemble a call from strings, symbols, and constantscall("mean", sym(var), na.rm = TRUE)

Local expressions versus function arguments

There are two main ways to defuse expressions, to which correspond two functions in rlang,expr() andenquo():

Defuse and inject

One purpose for defusing evaluation of an expression is to interface withdata-masking functions by injecting the expression back into another function with⁠!!⁠. This is thedefuse-and-inject pattern.

my_summarise <- function(data, arg) {  # Defuse the user expression in `arg`  arg <- enquo(arg)  # Inject the expression contained in `arg`  # inside a `summarise()` argument  data |> dplyr::summarise(mean = mean(!!arg, na.rm = TRUE))}

Defuse-and-inject is usually performed in a single step with the embrace operator{{.

my_summarise <- function(data, arg) {  # Defuse and inject in a single step with the embracing operator  data |> dplyr::summarise(mean = mean({{ arg }}, na.rm = TRUE))}

Usingenquo() and⁠!!⁠ separately is useful in more complex cases where you need access to the defused expression instead of just passing it on.

Defused arguments and quosures

If you inspect the return values ofexpr() andenquo(), you'll notice that the latter doesn't return a raw expression like the former. Instead it returns aquosure, a wrapper containing an expression and an environment.

expr(1 + 1)#> 1 + 1my_function <- function(arg) enquo(arg)my_function(1 + 1)#> <quosure>#> expr: ^1 + 1#> env:  global

R needs information about the environment to properly evaluate argument expressions because they come from a different context than the current function. For instance when a function in your package callsdplyr::mutate(), the quosure environment indicates where all the private functions of your package are defined.

Read more about the role of quosures inWhat are quosures and when are they needed?.

Comparison with base R

Defusing is known asquoting in other frameworks.


The double evaluation problem

Description

One inherent risk to metaprogramming is to evaluate multiple times a piece of code that appears to be evaluated only once. Take this data-masking function which takes a single input and produces two summaries:

summarise_stats <- function(data, var) {  data %>%    dplyr::summarise(      mean = mean({{ var }}),      sd = sd({{ var }})    )}summarise_stats(mtcars, cyl)#> # A tibble: 1 x 2#>    mean    sd#>   <dbl> <dbl>#> 1  6.19  1.79

This function is perfectly fine if the user supplies simple column names. However, data-masked arguments may also includecomputations.

summarise_stats(mtcars, cyl * 100)#> # A tibble: 1 x 2#>    mean    sd#>   <dbl> <dbl>#> 1  619.  179.

Computations may be slow and may produce side effects. For thesereasons, they should only be performed as many times as they appear in the code (unless explicitly documented, e.g. once per group with grouped data frames). Let's try again with a more complex computation:

times100 <- function(x) {  message("Takes a long time...")  Sys.sleep(0.1)  message("And causes side effects such as messages!")  x * 100}summarise_stats(mtcars, times100(cyl))#> Takes a long time...#> And causes side effects such as messages!#> Takes a long time...#> And causes side effects such as messages!#> # A tibble: 1 x 2#>    mean    sd#>   <dbl> <dbl>#> 1  619.  179.

Because of the side effects and the long running time, it is clear thatsummarise_stats() evaluates its input twice. This is because we've injected a defused expression in two different places. The data-masked expression created down the line looks like this (with caret signs representingquosure boundaries):

dplyr::summarise(  mean = ^mean(^times100(cyl)),  sd = ^sd(^times100(cyl)))

Thetimes100(cyl) expression is evaluated twice, even though it only appears once in the code. We have a double evaluation bug.

One simple way to fix it is to assign the defused input to a constant. You can then refer to that constant in the remaining of the code.

summarise_stats <- function(data, var) {  data %>%    dplyr::transmute(      var = {{ var }},    ) %>%    dplyr::summarise(      mean = mean(var),      sd = sd(var)    )}

The defused input is now evaluated only once because it is injected only once:

summarise_stats(mtcars, times100(cyl))#> Takes a long time...#> And causes side effects such as messages!#> # A tibble: 1 x 2#>    mean    sd#>   <dbl> <dbl>#> 1  619.  179.

What about glue strings?

⁠{{⁠embracing in glue strings doesn't suffer from the double evaluation problem:

summarise_stats <- function(data, var) {  data %>%    dplyr::transmute(      var = {{ var }},    ) %>%    dplyr::summarise(      "mean_{{ var }}" := mean(var),      "sd_{{ var }}" := sd(var)    )}summarise_stats(mtcars, times100(cyl))#> Takes a long time...#> And causes side effects such as messages!#> # A tibble: 1 x 2#>   `mean_times100(cyl)` `sd_times100(cyl)`#>                  <dbl>              <dbl>#> 1                 619.               179.

Since a glue string doesn't need the result of an expression, only the original code converted (deparsed) to a string, it doesn't evaluate injected expressions.


Why are strings and other constants enquosed in the empty environment?

Description

Function arguments aredefused intoquosures that keep track of the environment of the defused expression.

quo(1 + 1)#> <quosure>#> expr: ^1 + 1#> env:  global

You might have noticed that when constants are supplied, the quosure tracks the empty environment instead of the current environmnent.

quos("foo", 1, NULL)#> <list_of<quosure>>#>#> [[1]]#> <quosure>#> expr: ^"foo"#> env:  empty#>#> [[2]]#> <quosure>#> expr: ^1#> env:  empty#>#> [[3]]#> <quosure>#> expr: ^NULL#> env:  empty

The reason for this has to do with compilation of R code which makes it impossible to consistently capture environments of constants from function arguments. Argument defusing relies on thepromise mechanism of R for lazy evaluation of arguments. When functions are compiled and R notices that an argument is constant, it avoids creating a promise since they slow down function evaluation. Instead, the function is directly supplied a naked constant instead of constant wrapped in a promise.

Concrete case of promise unwrapping by compilation

We can observe this optimisation by calling into the C-levelfindVar() function to capture promises.

# Return the object bound to `arg` without triggering evaluation of# promisesf <- function(arg) {  rlang:::find_var(current_env(), sym("arg"))}# Call `f()` with a symbol or with a constantg <- function(symbolic) {  if (symbolic) {    f(letters)  } else {    f("foo")  }}# Make sure these small functions are compiledf <- compiler::cmpfun(f)g <- compiler::cmpfun(g)

Whenf() is called with a symbolic argument, we get the promise object created by R.

g(symbolic = TRUE)#> <promise: 0x7ffd79bac130>

However, supplying a constant to"f" returns the constant directly.

g(symbolic = FALSE)#> [1] "foo"

Without a promise, there is no way to figure out the original environment of an argument.

Do we need environments for constants?

Data-masking APIs in the tidyverse are intentionally designed so that they don't need an environment for constants.


Does⁠{{⁠ work on regular objects?

Description

The embrace operator{{ should be used exclusively with function arguments:

fn <- function(arg) {  quo(foo({{ arg }}))}fn(1 + 1)#> <quosure>#> expr: ^foo(^1 + 1)#> env:  0x7ffd89aac518

However you may have noticed that it also works on regular objects:

fn <- function(arg) {  arg <- force(arg)  quo(foo({{ arg }}))}fn(1 + 1)#> <quosure>#> expr: ^foo(^2)#> env:  0x7ffd8a633398

In that case,⁠{{⁠ captures thevalue of the expression instead of a defused expression. That's because only function arguments can be defused.

Note that this issue also applies toenquo() (on which⁠{{⁠ is based).

Why is this not an error?

Ideally we would have made⁠{{⁠ on regular objects an error. However this is not possible because in compiled R code it is not always possible to distinguish a regular variable from a function argument. SeeWhy are strings and other constants enquosed in the empty environment? for more about this.


Including function calls in error messages

Description

Starting with rlang 1.0,abort() includes the erroring function in the message by default:

my_function <- function() {  abort("Can't do that.")}my_function()#> Error in `my_function()`:#> ! Can't do that.

This works well whenabort() is called directly within the failing function. However, when theabort() call is exported to another function (which we call an "error helper"), we need to be explicit about which functionabort() is throwing an error for.

Passing the user context

There are two main kinds of error helpers:

In both cases, the default error call is not very helpful to the end user because it reflects an internal function rather than a user function:

my_function <- function(x) {  check_string(x)  stop_my_class("Unimplemented")}
my_function(NA)#> Error in `check_string()`:#> ! `x` must be a string.
my_function("foo")#> Error in `stop_my_class()`:#> ! Unimplemented

To fix this, letabort() know about the function that it is throwing the error for by passing the corresponding function environment as thecall argument:

stop_my_class <- function(message, call = caller_env()) {  abort(message, class = "my_class", call = call)}check_string <- function(x, arg = "x", call = caller_env()) {  if (!is_string(x)) {    cli::cli_abort("{.arg {arg}} must be a string.", call = call)  }}
my_function(NA)#> Error in `my_function()`:#> ! `x` must be a string.
my_function("foo")#> Error in `my_function()`:#> ! Unimplemented

Input checkers andcaller_arg()

Thecaller_arg() helper is useful in input checkers which check an input on the behalf of another function. Instead of hard-codingarg = "x", and forcing the callers to supply it if"x" is not the name of the argument being checked, usecaller_arg().

check_string <- function(x,                         arg = caller_arg(x),                         call = caller_env()) {  if (!is_string(x)) {    cli::cli_abort("{.arg {arg}} must be a string.", call = call)  }}

It is a combination ofsubstitute() andrlang::as_label() which provides a more generally applicable default:

my_function <- function(my_arg) {  check_string(my_arg)}my_function(NA)#> Error in `my_function()`:#> ! `my_arg` must be a string.

testthat workflow

Error snapshots are the main way of checking that the correct error call is included in an error message. However you'll need to opt into a new testthat display for warning and error snapshots. With the new display, these are printed by rlang, including thecall field. This makes it easy to monitor the full appearance of warning and error messages as they are displayed to users.

This display is not applied to all packages yet. With testthat 3.1.2, depend explicitly on rlang >= 1.0.0 to opt in. Starting from testthat 3.1.3, depending on rlang, no matter the version, is sufficient to opt in. In the future, the new display will be enabled for all packages.

Once enabled, create error snapshots with:

expect_snapshot(error = TRUE, {  my_function()})

You'll have to make sure that the snapshot coverage for error messages is sufficient for your package.


Including contextual information with error chains

Description

Error chaining is a mechanism for providing contextual information when an error occurs. There are multiple situations in which you might be able to provide context that is helpful to quickly understand the cause or origin of an error:

Here is an example of a chained error from dplyr that shows the pipeline step (mutate()) and the iteration context (group ID) in which a function called by the user failed:

add <- function(x, y) x + ymtcars |>  dplyr::group_by(cyl) |>  dplyr::mutate(new = add(disp, "foo"))#> Error in `dplyr::mutate()`:#> i In argument: `new = add(disp, "foo")`.#> i In group 1: `cyl = 4`.#> Caused by error in `x + y`:#> ! non-numeric argument to binary operator

In all these cases, there are two errors in play, chained together:

  1. Thecausal error, which interrupted the current course of action.

  2. Thecontextual error, which expresses higher-level information when something goes wrong.

There may be more than one contextual error in an error chain, but there is always only one causal error.

Rethrowing errors

To create an error chain, you must first capture causal errors when they occur. We recommend usingtry_fetch() instead oftryCatch() orwithCallingHandlers().

In practice,try_fetch() works just liketryCatch(). It takes pairs of error class names and handling functions. To chain an error, simply rethrow it from an error handler by passing it asparent argument.

In this example, we'll create awith_ function. That is, a function that sets up some configuration (in this case, chained errors) before executing code supplied as input:

with_chained_errors <- function(expr) {  try_fetch(    expr,    error = function(cnd) {      abort("Problem during step.", parent = cnd)    }  )}with_chained_errors(1 + "")#> Error in `with_chained_errors()`:#> ! Problem during step.#> Caused by error in `1 + ""`:#> ! non-numeric argument to binary operator

Typically, you'll use this error helper from another user-facing function.

my_verb <- function(expr) {  with_chained_errors(expr)}my_verb(add(1, ""))#> Error in `with_chained_errors()`:#> ! Problem during step.#> Caused by error in `x + y`:#> ! non-numeric argument to binary operator

Altough we have created a chained error, the error call of the contextual error is not quite right. It mentions the name of the error helper instead of the name of the user-facing function.

If you've readIncluding function calls in error messages, you may suspect that we need to pass acall argument toabort(). That's exactly what needs to happen to fix the call and backtrace issues:

with_chained_errors <- function(expr, call = caller_env()) {  try_fetch(    expr,    error = function(cnd) {      abort("Problem during step.", parent = cnd, call = call)    }  )}

Now that we've passed the caller environment ascall argument,abort() automatically picks up the correspondin function call from the execution frame:

my_verb(add(1, ""))#> Error in `my_verb()`:#> ! Problem during step.#> Caused by error in `x + y`:#> ! non-numeric argument to binary operator

Side note about missing arguments

my_verb() is implemented with a lazy evaluation pattern. The user input kept unevaluated until the error chain context is set up. A downside of this arrangement is that missing argument errors are reported in the wrong context:

my_verb()#> Error in `my_verb()`:#> ! Problem during step.#> Caused by error in `my_verb()`:#> ! argument "expr" is missing, with no default

To fix this, simply require these arguments before setting up the chained error context, for instance with thecheck_required() input checker exported from rlang:

my_verb <- function(expr) {  check_required(expr)  with_chained_errors(expr)}my_verb()#> Error in `my_verb()`:#> ! `expr` is absent but must be supplied.

Taking full ownership of a causal error

It is also possible to completely take ownership of a causal error and rethrow it with a more user-friendly error message. In this case, the original error is completely hidden from the end user. Opting for his approach instead of chaining should be carefully considered because hiding the causal error may deprive users from precious debugging information.

To rethow an error without chaining it, and completely take over the causal error from the user point of view, fetch it withtry_fetch() and throw a new error. The only difference with throwing a chained error is that theparent argument is set toNA. You could also omit theparent argument entirely, but passingNA letsabort() know it is rethrowing an error from a handler and that it should hide the corresponding error helpers in the backtrace.

with_own_scalar_errors <- function(expr, call = caller_env()) {  try_fetch(    expr,    vctrs_error_scalar_type = function(cnd) {      abort(        "Must supply a vector.",        parent = NA,        error = cnd,        call = call      )    }  )}my_verb <- function(expr) {  check_required(expr)  with_own_scalar_errors(    vctrs::vec_assert(expr)  )}my_verb(env())#> Error in `my_verb()`:#> ! Must supply a vector.

When a low-level error is overtaken, it is good practice to store it in the high-level error object, so that it can be inspected for debugging purposes. In the snippet above, we stored it in theerror field. Here is one way of accessing the original error by subsetting the object returned bylast_error():

rlang::last_error()$error

Case study: Mapping with chained errors

One good use case for chained errors is adding information about the iteration state when looping over a set of inputs. To illustrate this, we'll implement a version ofmap() /lapply() that chains an iteration error to any captured user error.

Here is a minimal implementation ofmap():

my_map <- function(.xs, .fn, ...) {  out <- new_list(length(.xs))  for (i in seq_along(.xs)) {    out[[i]] <- .fn(.xs[[i]], ...)  }  out}list(1, 2) |> my_map(add, 100)#> [[1]]#> [1] 101#> #> [[2]]#> [1] 102

With this implementation, the user has no idea which iteration failed when an error occurs:

list(1, "foo") |> my_map(add, 100)#> Error in `x + y`:#> ! non-numeric argument to binary operator

Rethrowing with iteration information

To improve on this we'll wrap the loop in atry_fetch() call that rethrow errors with iteration information. Make sure to calltry_fetch() on the outside of the loop to avoid a massive performance hit:

my_map <- function(.xs, .fn, ...) {  out <- new_list(length(.xs))  i <- 0L  try_fetch(    for (i in seq_along(.xs)) {      out[[i]] <- .fn(.xs[[i]], ...)    },    error = function(cnd) {      abort(        sprintf("Problem while mapping element %d.", i),        parent = cnd      )    }  )  out}

And that's it, the error chain created by the rethrowing handler now provides users with the number of the failing iteration:

list(1, "foo") |> my_map(add, 100)#> Error in `my_map()`:#> ! Problem while mapping element 2.#> Caused by error in `x + y`:#> ! non-numeric argument to binary operator

Dealing with errors thrown from the mapped function

One problem though, is that the user error call is not very informative when the error occurs immediately in the function supplied tomy_map():

my_function <- function(x) {  if (!is_string(x)) {    abort("`x` must be a string.")  }}list(1, "foo") |> my_map(my_function)#> Error in `my_map()`:#> ! Problem while mapping element 1.#> Caused by error in `.fn()`:#> ! `x` must be a string.

Functions have no names by themselves. Only the variable that refers to the function has a name. In this case, the mapped function is passed by argument to the variable.fn. So, when an error happens, this is the name that is reported to users.

One approach to fix this is to inspect thecall field of the error. When we detect a.fn call, we replace it by the defused code supplied as.fn argument:

my_map <- function(.xs, .fn, ...) {  # Capture the defused code supplied as `.fn`  fn_code <- substitute(.fn)  out <- new_list(length(.xs))  for (i in seq_along(.xs)) {    try_fetch(      out[[i]] <- .fn(.xs[[i]], ...),      error = function(cnd) {        # Inspect the `call` field to detect `.fn` calls        if (is_call(cnd$call, ".fn")) {          # Replace `.fn` by the defused code.          # Keep existing arguments.          cnd$call[[1]] <- fn_code        }        abort(          sprintf("Problem while mapping element %s.", i),          parent = cnd        )      }    )  }  out}

And voilà!

list(1, "foo") |> my_map(my_function)#> Error in `my_map()`:#> ! Problem while mapping element 1.#> Caused by error in `my_function()`:#> ! `x` must be a string.

Injecting with⁠!!⁠,⁠!!!⁠, and glue syntax

Description

The injection operators are extensions of R implemented by rlang to modify a piece of code before R processes it. There are two main families:

Dots injection

Unlike regular...,dynamic dots are programmable with injection operators.

Splicing with⁠!!!⁠

For instance, take a function likerbind() which takes data in.... To bind rows, you supply them as separate arguments:

rbind(a = 1:2, b = 3:4)#>   [,1] [,2]#> a    1    2#> b    3    4

But how do you bind a variable number of rows stored in a list? The base R solution is to invokerbind() withdo.call():

rows <- list(a = 1:2, b = 3:4)do.call("rbind", rows)#>   [,1] [,2]#> a    1    2#> b    3    4

Functions that implement dynamic dots include a built-in way of folding a list of arguments in.... To illustrate this, we'll create a variant ofrbind() that takes dynamic dots by collecting... withlist2():

rbind2 <- function(...) {  do.call("rbind", list2(...))}

It can be used just likerbind():

rbind2(a = 1:2, b = 3:4)#>   [,1] [,2]#> a    1    2#> b    3    4

And a list of arguments can be supplied bysplicing the list with!!!:

rbind2(!!!rows, c = 5:6)#>   [,1] [,2]#> a    1    2#> b    3    4#> c    5    6

Injecting names with⁠"{"⁠

A related problem comes up when an argument name is stored in a variable. With dynamic dots, you can inject the name usingglue syntax with"{":

name <- "foo"rbind2("{name}" := 1:2, bar = 3:4)#>     [,1] [,2]#> foo    1    2#> bar    3    4rbind2("prefix_{name}" := 1:2, bar = 3:4)#>            [,1] [,2]#> prefix_foo    1    2#> bar           3    4

Metaprogramming injection

Data-masked arguments support the following injection operators. They can also be explicitly enabled withinject().

Embracing with⁠{{⁠

The embracing operator{{ is made specially for function arguments. Itdefuses the expression supplied as argument and immediately injects it in place. The injected argument is then evaluated in another context such as adata mask.

# Inject function arguments that might contain# data-variables by embracing them with {{ }}mean_by <- function(data, by, var) {  data %>%    dplyr::group_by({{ by }}) %>%    dplyr::summarise(avg = mean({{ var }}, na.rm = TRUE))}# The data-variables `cyl` and `disp` inside the# env-variables `by` and `var` are injected inside `group_by()`# and `summarise()`mtcars %>% mean_by(by = cyl, var = disp)#> # A tibble: 3 x 2#>     cyl   avg#>   <dbl> <dbl>#> 1     4  105.#> 2     6  183.#> 3     8  353.

Learn more about this pattern inData mask programming patterns.

Injecting with⁠!!⁠

Unlike!!! which injects a list of arguments, the injection operator!! (pronounced "bang-bang") injects asingle object. One use case for⁠!!⁠ is to substitute an environment-variable (created with⁠<-⁠) with a data-variable (inside a data frame).

# The env-variable `var` contains a data-symbol object, in this# case a reference to the data-variable `height`var <- data_sym("disp")# We inject the data-variable contained in `var` inside `summarise()` mtcars %>%  dplyr::summarise(avg = mean(!!var, na.rm = TRUE))#> # A tibble: 1 x 1#>     avg#>   <dbl>#> 1  231.

Another use case is to inject a variable by value to avoidname collisions.

df <- data.frame(x = 1)# This name conflicts with a column in `df`x <- 100# Inject the env-variabledf %>%  dplyr::mutate(x = x / !!x)#>      x#> 1 0.01

Note that in most cases you don't need injection with⁠!!⁠. For instance, the.data and.env pronouns provide more intuitive alternatives to injecting a column name and injecting a value.

Splicing with⁠!!!⁠

The splice operator!!! of dynamic dots can also be used in metaprogramming context (insidedata-masked arguments and insideinject()). For instance, we could reimplement therbind2() function presented above usinginject() instead ofdo.call():

rbind2 <- function(...) {  inject(rbind(!!!list2(...)))}

There are two things going on here. We collect... withlist2() so that the callers ofrbind2() may use⁠!!!⁠. And we useinject() so thatrbind2() itself may use⁠!!!⁠ to splice the list of arguments passed torbind2().

Injection in other languages

Injection is known asquasiquotation in other programming languages and in computer science.expr() is similar to a quasiquotation operator and⁠!!⁠ is the unquote operator. These terms have a rich history in Lisp languages, and live on in modern languages likeJulia andRacket. In base R, quasiquotation is performed withbquote().

The main difference between rlang and other languages is that quasiquotation is often implicit instead of explicit. You can use injection operators in any defusing / quoting function (unless that function defuses its argument with a special operator likeenquo0()). This is not the case in lisp languages for example where injection / unquoting is explicit and only enabled within a backquote.

See also


What happens if I use injection operators out of context?

Description

Theinjection operators{{,!!, and!!! are an extension of the R syntax developed for tidyverse packages. Because they are not part of base R, they suffer from some limitations. In particular no specific error is thrown when they are used in unexpected places.

Using⁠{{⁠ out of context

The embrace operator{{ is a feature available indata-masked arguments powered by tidy eval. If you use it elsewhere, it is interpreted as a double⁠{⁠ wrapping.

In the R language,⁠{⁠ is like( but takes multiple expressions instead of one:

{  1 # Discarded  2}#> [1] 2list(  { message("foo"); 2 })#> foo#> [[1]]#> [1] 2

Just like you can wrap an expression in as many parentheses as you'd like, you can wrap multiple times with braces:

((1))#> [1] 1{{ 2 }}#> [1] 2

So nothing prevents you from embracing a function argument in a context where this operation is not implemented. R will just treat the braces like a set of parentheses and silently return the result:

f <- function(arg) list({{ arg }})f(1)#> [[1]]#> [1] 1

This sort of no-effect embracing should be avoided in real code because it falsely suggests that the function supports the tidy eval operator and that something special is happening.

However in many cases embracing is done to implementdata masking. It is likely that the function will be called with data-variables references which R won't be able to resolve properly:

my_mean <- function(data, var) {  with(data, mean({{ var }}))}my_mean(mtcars, cyl)#> Error:#> ! object 'cyl' not found

Sincewith() is a base data-masking function that doesn't support tidy eval operators, the embrace operator does not work and we get an object not found error.

Using⁠!!⁠ and⁠!!!⁠ out of context

The injection operators!! and!!! are implemented indata-masked arguments,dynamic dots, and withininject(). When used in other contexts, they are interpreted by R as double and triplenegations.

Double negation can be used in ordinary code to convert an input to logical:

!!10#> [1] TRUE!!0#> [1] FALSE

Triple negation is essentially the same as simple negation:

!10#> [1] FALSE!!!10#> [1] FALSE

This means that when injection operators are used in the wrong place, they will be interpreted as negation. In the best case scenario you will get a type error:

!"foo"#> Error in `!"foo"`:#> ! invalid argument type!quote(foo)#> Error in `!quote(foo)`:#> ! invalid argument type!quote(foo())#> Error in `!quote(foo())`:#> ! invalid argument type

In the worst case, R will silently convert the input to logical. Unfortunately there is no systematic way of checking for these errors.


Metaprogramming patterns

Description

The patterns covered in this article rely onmetaprogramming, the ability to defuse, create, expand, and inject R expressions. A good place to start if you're new to programming on the language is theMetaprogramming chapter of theAdvanced R book.

If you haven't already, readData mask programming patterns which covers simpler patterns that do not require as much theory to get up to speed. It covers concepts like argument behaviours and the various patterns you can add to your toolbox (forwarding, names, bridge, and transformative patterns).

Forwarding patterns

Defuse and inject

{{ and... are sufficient for most purposes. Sometimes however, it is necessary to decompose the forwarding action into its two constitutive steps,defusing andinjecting.

⁠{{⁠ is the combination ofenquo() and!!. These functions are completely equivalent:

my_summarise <- function(data, var) {  data %>% dplyr::summarise({{ var }})}my_summarise <- function(data, var) {  data %>% dplyr::summarise(!!enquo(var))}

Passing... is equivalent to the combination ofenquos() and!!!:

my_group_by <- function(.data, ...) {  .data %>% dplyr::group_by(...)}my_group_by <- function(.data, ...) {  .data %>% dplyr::group_by(!!!enquos(...))}

The advantage of decomposing the steps is that you gain access to thedefused expressions. Once defused, you can inspect or modify the expressions before injecting them in their target context.

Inspecting input labels

For instance, here is how to create an automatic name for a defused argument usingas_label():

f <- function(var) {  var <- enquo(var)  as_label(var)}f(cyl)#> [1] "cyl"f(1 + 1)#> [1] "1 + 1"

This is essentially equivalent to formatting an argument usingenglue():

f2 <- function(var) {  englue("{{ var }}")}f2(1 + 1)#> [1] "1 + 1"

With multiple arguments, use the plural variantenquos(). Set.named toTRUE to automatically callas_label() on the inputs for which the user has not provided a name (the same behaviour as in most dplyr verbs):

g <- function(...) {  vars <- enquos(..., .named = TRUE)  names(vars)}g(cyl, 1 + 1)#> [1] "cyl"   "1 + 1"

Just like withdplyr::mutate(), the user can override automatic names by supplying explicit names:

g(foo = cyl, bar = 1 + 1)#> [1] "foo" "bar"

Defuse-and-inject patterns are most useful for transforming inputs. Some applications are explored in the Transformation patterns section.

Names patterns

Symbolise and inject

The symbolise-and-inject pattern is anames pattern that you can use whenacross(all_of()) is not supported. It consists in creatingdefused expressions that refer to the data-variables represented in the names vector. These are then injected in the data mask context.

Symbolise a single string withsym() ordata_sym():

var <- "cyl"sym(var)#> cyldata_sym(var)#> .data$cyl

Symbolise a character vector withsyms() ordata_syms().

vars <- c("cyl", "am")syms(vars)#> [[1]]#> cyl#> #> [[2]]#> amdata_syms(vars)#> [[1]]#> .data$cyl#> #> [[2]]#> .data$am

Simple symbols returned bysym() andsyms() work in a wider variety of cases (with base functions in particular) but we'll use mostly usedata_sym() anddata_syms() because they are more robust (seeThe data mask ambiguity). Note that these do not returnsymbols per se, instead they createcalls to$ that subset the.data pronoun.

Since the.data pronoun is a tidy eval feature, you can't use it in base functions. As a rule, prefer thedata_-prefixed variants when you're injecting in tidy eval functions and the unprefixed functions for base functions.

A list of symbols can be injected in data-masked dots with the splice operator!!!, which injects each element of the list as a separate argument. For instance, to implement agroup_by() variant that takes a character vector of column names, you might write:

my_group_by <- function(data, vars) {  data %>% dplyr::group_by(!!!data_syms(vars))}my_group_by(vars)

In more complex case, you might want to add R code around the symbols. This requirestransformation patterns, see the section below.

Bridge patterns

mutate() as a data-mask to selection bridge

This is a variant of thetransmute() bridge pattern described inData mask programming patterns that does not materialise... in the intermediate step. Instead, the... expressions are defused and inspected. Then the expressions, rather than the columns, are spliced inmutate().

my_pivot_longer <- function(data, ...) {  # Defuse the dots and inspect the names  dots <- enquos(..., .named = TRUE)  names <- names(dots)  # Pass the inputs to `mutate()`  data <- data %>% dplyr::mutate(!!!dots)  # Select `...` inputs by name with `all_of()`  data %>%    tidyr::pivot_longer(cols = all_of(names))}mtcars %>% my_pivot_longer(cyl, am = am * 100)
  1. Defuse the... expressions. The.named argument ensures unnamed inputs get a default name, just like they would if passed tomutate(). Take the names of the list of inputs.

  2. Once we have the names, inject the argument expressions intomutate() to update the data frame.

  3. Finally, pass the names to the tidy selection viaall_of().

Transformation patterns

Transforming inputs manually

Ifacross() and variants are not available, you will need to transform the inputs yourself using metaprogramming techniques. To illustrate the technique we'll reimplementmy_mean() and without usingacross(). The pattern consists in defusing the input expression, building larger calls around them, and finally inject the modified expressions inside the data-masking functions.

We'll start with a single named argument for simplicity:

my_mean <- function(data, var) {  # Defuse the expression  var <- enquo(var)  # Wrap it in a call to `mean()`  var <- expr(mean(!!var, na.rm = TRUE))  # Inject the expanded expression  data %>% dplyr::summarise(mean = !!var)}mtcars %>% my_mean(cyl)#> # A tibble: 1 x 1#>    mean#>   <dbl>#> 1  6.19

With... the technique is similar, though a little more involved. We'll use the plural variantsenquos() and!!!. We'll also loop over the variable number of inputs usingpurrr::map(). But the pattern is otherwise basically the same:

my_mean <- function(.data, ...) {  # Defuse the dots. Make sure they are automatically named.  vars <- enquos(..., .named = TRUE)  # Map over each defused expression and wrap it in a call to `mean()`  vars <- purrr::map(vars, ~ expr(mean(!!.x, na.rm = TRUE)))  # Inject the expressions  .data %>% dplyr::summarise(!!!vars)}mtcars %>% my_mean(cyl)#> # A tibble: 1 x 1#>     cyl#>   <dbl>#> 1  6.19

Note that we are inheriting the data-masking behaviour ofsummarise() because we have effectively forwarded... inside that verb. This is different than transformation patterns based onacross() which inherit tidy selection behaviour. In practice, this means the function doesn't support selection helpers and syntax. Instead, it gains the ability to create new vectors on the fly:

mtcars %>% my_mean(cyl = cyl * 100)#> # A tibble: 1 x 1#>     cyl#>   <dbl>#> 1  619.

Base patterns

In this section, we review patterns for programming withbase data-masking functions. They essentially consist in building and evaluating expressions in the data mask. We review these patterns and compare them to rlang idioms.

Data-maskedget()

In the simplest version of this pattern,get() is called with a variable name to retrieve objects from the data mask:

var <- "cyl"with(mtcars, mean(get(var)))#> [1] 6.1875

This sort of pattern is susceptible tonames collisions. For instance, the input data frame might contain a variable calledvar:

df <- data.frame(var = "wrong")with(df, mean(get(var)))#> Error in `get()`:#> ! object 'wrong' not found

In general, prefer symbol injection overget() to prevent this sort of collisions. With base functions you will need to enable injection operators explicitly usinginject():

inject(  with(mtcars, mean(!!sym(var))))#> [1] 6.1875

SeeThe data mask ambiguity for more information about names collisions.

Data-maskedparse() andeval()

A more involved pattern consists in building R code in a string and evaluating it in the mask:

var1 <- "am"var2 <- "vs"code <- paste(var1, "==", var2)with(mtcars, mean(eval(parse(text = code))))#> [1] 0.59375

As before, thecode variable is vulnerable tonames collisions. More importantly, ifvar1 andvar2 are user inputs, they could containadversarial code. Evaluating code assembled from strings is always a risky business:

var1 <- "(function() {  Sys.sleep(Inf)  # Could be a coin mining routine})()"var2 <- "vs"code <- paste(var1, "==", var2)with(mtcars, mean(eval(parse(text = code))))

This is not a big deal if your code is only used internally. However, this code could be part of a public Shiny app which Internet users could exploit. But even internally, parsing is a source of bugs when variable names contain syntactic symbols like- or:.

var1 <- ":var:"var2 <- "vs"code <- paste(var1, "==", var2)with(mtcars, mean(eval(parse(text = code))))#> Error in `parse()`:#> ! <text>:1:1: unexpected ':'#> 1: :#>     ^

For these reasons, always prefer tobuild code instead of parsing code. Building variable names withsym() is a way of sanitising inputs.

var1 <- "(function() {  Sys.sleep(Inf)  # Could be a coin mining routine})()"var2 <- "vs"code <- call("==", sym(var1), sym(var2))code#> `(function() {\n  Sys.sleep(Inf)  # Could be a coin mining routine\n})()` == #>     vs

The adversarial input now produces an error:

with(mtcars, mean(eval(code)))#> Error:#> ! object '(function() {\n  Sys.sleep(Inf)  # Could be a coin mining routine\n})()' not found

Finally, it is recommended to inject the code instead of evaluating it to avoid names collisions:

var1 <- "am"var2 <- "vs"code <- call("==", sym(var1), sym(var2))inject(  with(mtcars, mean(!!code)))#> [1] 0.59375

Taking multiple columns without...

Description

In this guide we compare ways of taking multiple columns in a single function argument.

As a refresher (see theprogramming patterns article), there are two common ways of passing arguments todata-masking functions. For single arguments, embrace with{{:

my_group_by <- function(data, var) {  data %>% dplyr::group_by({{ var }})}my_pivot_longer <- function(data, var) {  data %>% tidyr::pivot_longer({{ var }})}

For multiple arguments in..., pass them on to functions that also take... likegroup_by(), or pass them withinc() for functions taking tidy selection in a single argument likepivot_longer():

# Pass dots throughmy_group_by <- function(.data, ...) {  .data %>% dplyr::group_by(...)}my_pivot_longer <- function(.data, ...) {  .data %>% tidyr::pivot_longer(c(...))}

But what if you want to take multiple columns in a single named argument rather than in...?

Using tidy selections

The idiomatic tidyverse way of taking multiple columns in a single argument is to take atidy selection (see theArgument behaviours section). In tidy selections, the syntax for passing multiple columns in a single argument isc():

mtcars %>% tidyr::pivot_longer(c(am, cyl, vs))

Since⁠{{⁠ inherits behaviour, this implementation ofmy_pivot_longer() automatically allows multiple columns passing:

my_pivot_longer <- function(data, var) {  data %>% tidyr::pivot_longer({{ var }})}mtcars %>% my_pivot_longer(c(am, cyl, vs))

Forgroup_by(), which takes data-masked arguments, we'll useacross() as abridge (seeBridge patterns).

my_group_by <- function(data, var) {  data %>% dplyr::group_by(across({{ var }}))}mtcars %>% my_group_by(c(am, cyl, vs))

When embracing in tidyselect context or usingacross() is not possible, you might have to implement tidyselect behaviour manually withtidyselect::eval_select().

Using external defusal

To implement an argument with tidyselect behaviour, it is necessary todefuse the argument. However defusing an argument which had historically behaved like a regular argument is a rather disruptive breaking change. This is why we could not implement tidy selections in ggplot2 facetting functions likefacet_grid() andfacet_wrap().

An alternative is to use external defusal of arguments. This is what formula interfaces do for instance. A modelling function takes a formula in a regular argument and the formula defuses the user code:

my_lm <- function(data, f, ...) {  lm(f, data, ...)}mtcars %>% my_lm(disp ~ drat)

Once created, the defused expressions contained in the formula are passed around like a normal argument. A similar approach was taken to updatefacet_ functions to tidy eval. Thevars() function (a simple alias toquos()) is provided so that users can defuse their arguments externally.

ggplot2::facet_grid(  ggplot2::vars(cyl),  ggplot2::vars(am, vs))

You can implement this approach by simply taking a list of defused expressions as argument. This list can be passed the usual way to other functions taking such lists:

my_facet_grid <- function(rows, cols, ...) {  ggplot2::facet_grid(rows, cols, ...)}

Or it can be spliced with!!!:

my_group_by <- function(data, vars) {  stopifnot(is_quosures(vars))  data %>% dplyr::group_by(!!!vars)}mtcars %>% my_group_by(dplyr::vars(cyl, am))

A non-approach: Parsing lists

Intuitively, many programmers who want to take a list of expressions in a single argument try to defuse an argument and parse it. The user is expected to supply multiple arguments within alist() expression. When such a call is detected, the arguments are retrieved and spliced with⁠!!!⁠. Otherwise, the user is assumed to have supplied a single argument which is injected with⁠!!⁠. An implementation along these lines might look like this:

my_group_by <- function(data, vars) {  vars <- enquo(vars)  if (quo_is_call(vars, "list")) {    expr <- quo_get_expr(vars)    env <- quo_get_env(vars)    args <- as_quosures(call_args(expr), env = env)    data %>% dplyr::group_by(!!!args)  } else {    data %>% dplyr::group_by(!!vars)  }}

This does work in simple cases:

mtcars %>% my_group_by(cyl) %>% dplyr::group_vars()#> [1] "cyl"mtcars %>% my_group_by(list(cyl, am)) %>% dplyr::group_vars()#> [1] "cyl" "am"

However this parsing approach quickly shows limits:

mtcars %>% my_group_by(list2(cyl, am))#> Error in `group_by()`: Can't add columns.#> i `..1 = list2(cyl, am)`.#> i `..1` must be size 32 or 1, not 2.

Also, it would be better for overall consistency of interfaces to use the tidyselect syntaxc() for passing multiple columns. In general, we recommend to use either the tidyselect or the external defusal approaches.


What are quosures and when are they needed?

Description

A quosure is a special type ofdefused expression that keeps track of the original context the expression was written in. The tracking capabilities of quosures is important when interfacingdata-masking functions together because the functions might come from two unrelated environments, like two different packages.

Blending environments

Let's take an example where the R user calls the functionsummarise_bmi() from the foo package to summarise a data frame with statistics of a BMI value. Because theheight variable of their data frame is not in metres, they use a custom functiondiv100() to rescale the column.

# Global environment of userdiv100 <- function(x) {  x / 100}dplyr::starwars %>%  foo::summarise_bmi(mass, div100(height))

Thesummarise_bmi() function is a data-masking function defined in the namespace of the foo package which looks like this:

# Namespace of package foobmi <- function(mass, height) {  mass / height^2}summarise_bmi <- function(data, mass, height) {  data %>%    bar::summarise_stats(bmi({{ mass }}, {{ height }}))}

The foo package uses the custom functionbmi() to perform a computation on two vectors. It interfaces withsummarise_stats() defined in bar, another package whose namespace looks like this:

# Namespace of package barcheck_numeric <- function(x) {  stopifnot(is.numeric(x))  x}summarise_stats <- function(data, var) {  data %>%    dplyr::transmute(      var = check_numeric({{ var }})    ) %>%    dplyr::summarise(      mean = mean(var, na.rm = TRUE),      sd = sd(var, na.rm = TRUE)    )}

Again the package bar uses a custom function,check_numeric(), to validate its input. It also interfaces with data-masking functions from dplyr (using thedefine-a-constant trick to avoid issues of double evaluation).

There are three data-masking functions simultaneously interfacing in this snippet:

There is a fourth context, the global environment wheresummarise_bmi() is called with two columns defined in a data frame, one of which is transformed on the fly with the user functiondiv100().

All of these contexts (except to some extent the global environment) contain functions that are private and invisible to foreign functions. Yet, the final expanded data-masked expression that is evaluated down the line looks like this (with caret characters indicating the quosure boundaries):

dplyr::transmute(  var = ^check_numeric(^bmi(^mass, ^div100(height))))

The role of quosures is to let R know thatcheck_numeric() should be found in the bar package,bmi() in the foo package, anddiv100() in the global environment.

When should I create quosures?

As a tidyverse user you generally don't need to worry about quosures because⁠{{⁠ and... will create them for you. Introductory texts likeProgramming with dplyr or thestandard data-mask programming patterns don't even mention the term. In more complex cases you might need to create quosures withenquo() orenquos() (even though you generally don't need to know or care that these functions return quosures). In this section, we explore when quosures are necessary in these more advanced applications.

Foreign and local expressions

As a rule of thumb, quosures are only needed for arguments defused withenquo() orenquos() (or with{{ which callsenquo() implicitly):

my_function <- function(var) {  var <- enquo(var)  their_function(!!var)}# Equivalentlymy_function <- function(var) {  their_function({{ var }})}

Wrapping defused arguments in quosures is needed because expressions supplied as argument comes from a different environment, the environment of your user. For local expressions created in your function, you generally don't need to create quosures:

my_mean <- function(data, var) {  # `expr()` is sufficient, no need for `quo()`  expr <- expr(mean({{ var }}))  dplyr::summarise(data, !!expr)}my_mean(mtcars, cyl)#> # A tibble: 1 x 1#>   `mean(cyl)`#>         <dbl>#> 1        6.19

Usingquo() instead ofexpr() would have worked too but it is superfluous becausedplyr::summarise(), which usesenquos(), is already in charge of wrapping your expression within a quosure scoped in your environment.

The same applies if you evaluate manually. By default,eval() andeval_tidy() capture your environment:

my_mean <- function(data, var) {  expr <- expr(mean({{ var }}))  eval_tidy(expr, data)}my_mean(mtcars, cyl)#> [1] 6.1875

External defusing

An exception to this rule of thumb (wrap foreign expressions in quosures, not your own expressions) arises when your function takes multiple expressions in a list instead of.... The preferred approach in that case is to take a tidy selection so that users can combine multiple columns usingc(). If that is not possible, you can take a list of externally defused expressions:

my_group_by <- function(data, vars) {  stopifnot(is_quosures(vars))  data %>% dplyr::group_by(!!!vars)}mtcars %>% my_group_by(dplyr::vars(cyl, am))

In this pattern,dplyr::vars() defuses expressions externally. It creates a list of quosures because the expressions are passed around from function to function like regular arguments. In fact,dplyr::vars() andggplot2::vars() are simple aliases ofquos().

dplyr::vars(cyl, am)#> <list_of<quosure>>#> #> [[1]]#> <quosure>#> expr: ^cyl#> env:  global#> #> [[2]]#> <quosure>#> expr: ^am#> env:  global

For more information about external defusing, seeTaking multiple columns without ....

Technical description of quosures

A quosure carries two things:

And implements these behaviours:

There are similarities between promises (the ones R uses to implement lazy evaluation, not the async expressions from the promises package) and quosures. One important difference is that promises are only evaluated once and cache the result for subsequent evaluation. Quosures behave more like calls and can be evaluated repeatedly, potentially in a different data mask. This property is useful to implement split-apply-combine evaluations.

See also


Capture a backtrace

Description

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.

Usage

trace_back(top = NULL, bottom = NULL)trace_length(trace)

Arguments

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 calltrace_back()indirectly or from a larger context, for example in tests orinside an RMarkdown document where you don't want all of theknitr evaluation mechanisms to appear in the backtrace.

If not supplied, therlang_trace_top_env global option isconsulted. This makes it possible to trim the embedding contextfor all backtraces created while the option is set. If knitr isin progress, the default value for this option isknitr::knit_global() so that the knitr context is trimmed outof backtraces.

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 tocaller_env().

trace

A backtrace created bytrace_back().

Examples

# 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 an expression with condition handlers

Description

[Experimental]

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:

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.

Usage

try_fetch(expr, ...)

Arguments

expr

An R expression.

...

<dynamic-dots> Named conditionhandlers. The names specify the condition class for which ahandler will be called.

Stack overflows

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.

Comparison withtryCatch()

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() than⁠tryCatch().⁠


Type predicates

Description

These type predicates aim to make type testing in R moreconsistent. They are wrappers aroundbase::typeof(), so operateat a level beneath S3/S4 etc.

Usage

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)

Arguments

x

Object to be tested.

n

Expected length of a vector.

finite

Whether all values of the vector are finite. Thenon-finite values areNA,Inf,-Inf andNaN. Setting thisto something other thanNULL can be expensive because the wholevector needs to be traversed and checked.

Details

Compared to base R functions:

See Also

bare-type-predicatesscalar-type-predicates


Base type of an object

Description

[Soft-deprecated][Experimental]

This is equivalent tobase::typeof() with a few differences thatmake dispatching easier:

Usage

type_of(x)

Arguments

x

An R object.

Examples

type_of(10L)# Quosures are treated as a new base type but not formulas:type_of(quo(10L))type_of(~10L)# Compare to base::typeof():typeof(quo(10L))# Strings are treated as a new base type:type_of(letters)type_of(letters[[1]])# This is a bit inconsistent with the core language tenet that data# types are vectors. However, treating strings as a different# scalar type is quite helpful for switching on function inputs# since so many arguments expect strings:switch_type("foo", character = abort("vector!"), string = "result")# Special and builtin primitives are both treated as primitives.# That's because it is often irrelevant which type of primitive an# input is:typeof(list)typeof(`$`)type_of(list)type_of(`$`)

Poke values into a vector

Description

[Experimental]

These tools are for R experts only. They copy elements fromyintox by mutation. You should only do this if you ownx,i.e. if you have created it or if you are certain that it doesn'texist in any other context. Otherwise you might create unintendedside effects that have undefined consequences.

Usage

vec_poke_n(x, start, y, from = 1L, n = length(y))vec_poke_range(x, start, y, from = 1L, to = length(y) - from + 1L)

Arguments

x

The destination vector.

start

The index indicating where to start modifyingx.

y

The source vector.

from

The index indicating where to start copying fromy.

n

How many elements should be copied fromy tox.

to

The index indicating the end of the range to copy fromy.


Coerce an object to a base type

Description

[Deprecated]

These are equivalent to the base functions (e.g.as.logical(),as.list(), etc), but perform coercion rather than conversion.This means they are not generic and will not call S3 conversionmethods. They only attempt to coerce the base type of theirinput. In addition, they have stricter implicit coercion rules andwill never attempt any kind of parsing. E.g. they will not try tofigure out if a character vector represents integers or booleans.Finally, they treat attributes consistently, unlike the base Rfunctions: all attributes except names are removed.

Usage

as_logical(x)as_integer(x)as_double(x)as_complex(x)as_character(x, encoding = NULL)as_list(x)

Arguments

x

An object to coerce to a base type.

encoding

If non-null, set an encoding mark. This is onlydeclarative, no encoding conversion is performed.

Lifecycle

These functions are deprecated in favour ofvctrs::vec_cast().

Coercion to logical and numeric atomic vectors

Coercion to character vectors

as_character() andas_string() have an optionalencodingargument to specify the encoding. R uses this information forinternal handling of strings and character vectors. Note that thisis only declarative, no encoding conversion is attempted.

Note that onlyas_string() can coerce symbols to a scalarcharacter vector. This makes the code more explicit and adds anextra type check.

Coercion to lists

as_list() only coerces vector and dictionary types (environmentsare an example of dictionary type). Unlikebase::as.list(),as_list() removes all attributes except names.

Effects of removing attributes

A technical side-effect of removing the attributes of the input isthat the underlying objects has to be copied. This has noperformance implications in the case of lists because this is ashallow copy: only the list structure is copied, not the contents(seeduplicate()). However, be aware that atomic vectorscontaining large amounts of data will have to be copied.

In general, any attribute modification creates a copy, which is whyit is better to avoid using attributes with heavy atomic vectors.Uncopyable objects like environments and symbols are an exceptionto this rule: in this case, attributes modification happens inplace and has side-effects.

Examples

# Coercing atomic vectors removes attributes with both base R and rlang:x <- structure(TRUE, class = "foo", bar = "baz")as.logical(x)# But coercing lists preserves attributes in base R but not rlang:l <- structure(list(TRUE), class = "foo", bar = "baz")as.list(l)as_list(l)# Implicit conversions are performed in base R but not rlang:as.logical(l)## Not run: as_logical(l)## End(Not run)# Conversion methods are bypassed, making the result of the# coercion more predictable:as.list.foo <- function(x) "wrong"as.list(l)as_list(l)# The input is never parsed. E.g. character vectors of numbers are# not converted to numeric types:as.integer("33")## Not run: as_integer("33")## End(Not run)# With base R tools there is no way to convert an environment to a# list without either triggering method dispatch, or changing the# original environment. as_list() makes it easy:x <- structure(as_environment(mtcars[1:2]), class = "foobar")as.list.foobar <- function(x) abort("dont call me")as_list(x)

Create vectors

Description

[Questioning]

The atomic vector constructors are equivalent toc() but:

Usage

lgl(...)int(...)dbl(...)cpl(...)chr(...)bytes(...)

Arguments

...

Components of the new vector. Bare lists and explicitlyspliced lists are spliced.

Life cycle

Examples

# 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))

Evaluate an expression within a given environment

Description

[Deprecated]

These functions evaluateexpr within a given environment (envforwith_env(), or the child of the current environment forlocally). They rely oneval_bare() which features a lighterevaluation mechanism than base Rbase::eval(), and which also hassome subtle implications when evaluting stack sensitive functions(see help foreval_bare()).

locally() is equivalent to the base functionbase::local() but it produces a much cleanerevaluation stack, and has stack-consistent semantics. It is thusmore suited for experimenting with the R language.

Usage

with_env(env, expr)locally(expr)

Arguments

env

An environment within which to evaluateexpr. Can bean object with aget_env() method.

expr

An expression to evaluate.

Examples

# with_env() is handy to create formulas with a given environment:env <- child_env("rlang")f <- with_env(env, ~new_formula())identical(f_env(f), env)# Or functions with a given enclosure:fn <- with_env(env, function() NULL)identical(get_env(fn), env)# Unlike eval() it doesn't create duplicates on the evaluation# stack. You can thus use it e.g. to create non-local returns:fn <- function() {  g(current_env())  "normal return"}g <- function(env) {  with_env(env, return("early return"))}fn()# Since env is passed to as_environment(), it can be any object with an# as_environment() method. For strings, the pkg_env() is returned:with_env("base", ~mtcars)# This can be handy to put dictionaries in scope:with_env(mtcars, cyl)

Establish handlers on the stack

Description

[Deprecated]

As of rlang 1.0.0,with_handlers() is deprecated. Use the basefunctions or the experimentaltry_fetch() function instead.

Usage

with_handlers(.expr, ...)calling(handler)exiting(handler)

Arguments

.expr,...,handler

[Deprecated]


Get key/value from a weak reference object

Description

Get key/value from a weak reference object

Usage

wref_key(x)wref_value(x)

Arguments

x

A weak reference object.

See Also

is_weakref() andnew_weakref().


Create zap objects

Description

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.

Usage

zap()is_zap(x)

Arguments

x

An object to test.

Examples

# Create one zap object:zap()# Create a list of zaps:rep(list(zap()), 3)rep_named(c("foo", "bar"), list(zap()))

Zap source references

Description

There are a number of situations where R creates source references:

These source references take up space and might cause a number ofissues.zap_srcref() recursively walks through expressions andfunctions to remove all source references.

Usage

zap_srcref(x)

Arguments

x

An R object. Functions and calls are walked recursively.


[8]ページ先頭

©2009-2025 Movatter.jp