rlang 0.3.0
Breaking changes
The rlang API is still maturing. In this section, you'll find hard
breaking changes. See the life cycle section below for an exhaustive
list of API changes.
-
quo_text()
now deparses non-syntactic symbols with backticks:quo_text(sym("foo+")) #> [1] "`foo+`"
This caused a number of issues in reverse dependencies as
quo_text()
tends to be used for converting symbols to strings.
quo_text()
andquo_name()
should not be used for this purpose
because they are general purpose deparsers. These functions should
generally only be used for printing outputs or creating default
labels. If you need to convert symbols to strings, please use
as_string()
rather thanquo_text()
.We have extended the documentation of
?quo_text
and?quo_name
to
make these points clearer. -
exprs()
no longer flattens quosures.exprs(!!!quos(x, y))
is now
equivalent toquos(x, y)
. -
The sentinel for removing arguments in
call_modify()
has been
changed fromNULL
tozap()
. This breaking change is motivated
by the ambiguity ofNULL
with valid argument values.call_modify(call, arg = NULL) # Add `arg = NULL` to the call call_modify(call, arg = zap()) # Remove the `arg` argument from the call
-
The
%@%
operator now quotes its input and supports S4 objects.
This makes it directly equivalent to@
except that it extracts
attributes for non-S4 objects (#207). -
Taking the
env_parent()
of the empty environment is now an error.
Summary
The changes for this version are organised around three main themes:
error reporting, tidy eval, and tidy dots.
-
abort()
now records backtraces automatically in the error object.
Errors thrown withabort()
invite users to call
rlang::last_error()
to see a backtrace and help identifying where
and why the error occurred. The backtraces created by rlang (you can
create one manually withtrace_back()
) are printed in a simplified
form by default that removes implementation details from the
backtrace. To see the full backtrace, call
summary(rlang::last_error())
.abort()
also gains aparent
argument. This is meant for
situations where you're calling a low level API (to download a file,
parse a JSON file, etc) and would like to intercept errors with
base::tryCatch()
orrlang::with_handlers()
and rethrow them with
a high-level message. Callabort()
with the intercepted error as
theparent
argument. When the user printsrlang::last_error()
,
the backtrace will be shown in two sections corresponding to the
high-level and low-level contexts.In order to get segmented backtraces, the low-level error has to be
thrown withabort()
. When that's not the case, you can call the
low-level function withinwith_abort()
to automatically promote
all errors to rlang errors. -
The tidy eval changes are mostly for developers of data masking
APIs. The main user-facing change is that.data[[
is now an
unquote operator so thatvar
in.data[[var]]
is never masked by
data frame columns and always picked from the environment. This
makes the pronoun safe for programming in functions. -
The
!!!
operator now supports all classed objects like factors. It
callsas.list()
on S3 objects andas(x, "list")
on S4 objects. -
dots_list()
gains several arguments to control how dots are
collected. You can control the selection of arguments with the same
name with.homonyms
(keep first, last, all, or abort). You can
also elect to preserve empty arguments with.preserve_empty
.
Conditions and errors
-
New
trace_back()
captures a backtrace. Compared to the base R
traceback, it contains additional structure about the relationship
between frames. It comes with tools for automatically restricting to
frames after a certain environment on the stack, and to simplify
when printing. These backtraces are now recorded in errors thrown by
abort()
(see below). -
abort()
gains aparent
argument to specify a parent error. This
is meant for situations where a low-level error is expected
(e.g. download or parsing failed) and you'd like to throw an error
with higher level information. Specifying the low-level error as
parent makes it possible to partition the backtraces based on
ancestry. -
Errors thrown with
abort()
now embed a backtrace in the condition
object. It is no longer necessary to record a trace with a calling
handler for such errors. -
with_abort()
runs expressions in a context where all errors are
promoted to rlang errors and gain a backtrace. -
Unhandled errors thrown by
abort()
are now automatically saved and
can be retrieved withrlang::last_error()
. The error prints with a
simplified backtrace. Callsummary(last_error())
to see the full
backtrace. -
New experimental option
rlang__backtrace_on_error
to display
backtraces alongside error messages. See?rlang::abort
for
supported options. -
The new
signal()
function completes theabort()
,warn()
and
inform()
family. It creates and signals a bare condition. -
New
interrupt()
function to simulate an user interrupt from R
code. -
cnd_signal()
now dispatches messages, warnings, errors and
interrupts to the relevant signalling functions (message()
,
warning()
,stop()
and the C functionRf_onintr()
). This makes
it a good choice to resignal a captured condition. -
New
cnd_type()
helper to determine the type of a condition
("condition"
,"message"
,"warning"
,"error"
or"interrupt"
). -
abort()
,warn()
andinform()
now accepts metadata with...
.
The data are stored in the condition and can be examined by user
handlers.Consequently all arguments have been renamed and prefixed with a dot
(to limit naming conflicts between arguments and metadata names). -
with_handlers()
treats bare functions as exiting handlers
(equivalent to handlers supplied totryCatch()
). It also supports
the formula shortcut for lambda functions (as in purrr). -
with_handlers()
now produces a cleaner stack trace.
Tidy dots
-
The input types of
!!!
have been standardised.!!!
is generally
defined on vectors: it takes a vector (typically, a list) and
unquotes each element as a separate argument. The standardisation
makes!!!
behave the same in functions taking dots withlist2()
and in quoting functions.!!!
accepts these types:-
Lists, pairlists, and atomic vectors. If they have a class, they
are converted withbase::as.list()
to allow S3 dispatch.
Following this change, objects like factors can now be spliced
without data loss. -
S4 objects. These are converted with
as(obj, "list")
before
splicing. -
Quoted blocks of expressions, i.e.
{ }
calls
!!!
disallows:- Any other objects like functions or environments, but also
language objects like formula, symbols, or quosures.
Quoting functions used to automatically wrap language objects in
lists to make them spliceable. This behaviour is now soft-deprecated
and it is no longer valid to write!!!enquo(x)
. Please unquote
scalar objects with!!
instead. -
-
dots_list()
,enexprs()
andenquos()
gain a.homonyms
argument to control how to treat arguments with the same name.
The default is to keep them. Set it to"first"
or"last"
to keep
only the first or last occurrences. Set it to"error"
to raise an
informative error about the arguments with duplicated names. -
enexprs()
andenquos()
now support.ignore_empty = "all"
with named arguments as well (#414). -
dots_list()
gains a.preserve_empty
argument. WhenTRUE
, empty
arguments are stored as missing arguments (see?missing_arg
). -
dots_list()
,enexprs()
andenquos()
gain a.check_assign
argument. WhenTRUE
, a warning is issued when a<-
call is
detected in...
. No warning is issued if the assignment is wrapped
in brackets like{ a <- 1 }
. The warning lets users know about a
possible typo in their code (assigning instead of matching a
function parameter) and requires them to be explicit that they
really want to assign to a variable by wrapping in parentheses. -
lapply(list(quote(foo)), list2)
no longer evaluatesfoo
(#580).
Tidy eval
-
You can now unquote quosured symbols as LHS of
:=
. The symbol is
automatically unwrapped from the quosure. -
Quosure methods have been defined for common operations like
==
. These methods fail with an informative error message
suggesting to unquote the quosure (#478, #tidyverse/dplyr#3476). -
as_data_pronoun()
now accepts data masks. If the mask has multiple
environments, all of these are looked up when subsetting the pronoun.
Function objects stored in the mask are bypassed. -
It is now possible to unquote strings in function position. This is
consistent with how the R parser coerces strings to symbols. These
two expressions are now equivalent:expr("foo"())
and
expr((!!"foo")())
. -
Quosures converted to functions with
as_function()
now support
nested quosures. -
expr_deparse()
(used to print quosures at the console) now escapes
special characters. For instance, newlines now print as"\n"
(#484).
This ensures that the roundtripparse_expr(expr_deparse(x))
is not
lossy. -
new_data_mask()
now throws an error whenbottom
is not a child
oftop
(#551). -
Formulas are now evaluated in the correct environment within
eval_tidy()
. This fixes issues in dplyr and other tidy-evaluation
interfaces. -
New functions
new_quosures()
andas_quosures()
to create or
coerce to a list of quosures. This is a small S3 class that ensures
two invariants on subsetting and concatenation: that each element is
a quosure and that the list is always named even if only with a
vector of empty strings.
Environments
-
env()
now treats a single unnamed argument as the parent of the
new environment. Consequently,child_env()
is now superfluous and
is now in questioning life cycle. -
New
current_env()
andcurrent_fn()
functions to retrieve the
current environment or the function being evaluated. They are
equivalent tobase::environment()
andbase::sys.function()
called without argument. -
env_get()
andenv_get_list()
gain adefault
argument to
provide a default value for non-existing bindings. -
env_poke()
now returns the old value invisibly rather than the
input environment. -
The new function
env_name()
returns the name of an environment.
It always adds the "namespace:" prefix to namespace names. It
returns "global" instead of ".GlobalEnv" or "R_GlobalEnv", "empty"
instead of "R_EmptyEnv". The companionenv_label()
is like
env_name()
but returns the memory address for anonymous
environments. -
env_parents()
now returns a named list. The names are taken with
env_name()
. -
env_parents()
andenv_tail()
now stop at the global environment
by default. This can be changed with thelast
argument. The empty
environment is always a stopping condition so you can take the
parents or the tail of an environment on the search path without
changing the default. -
New predicates
env_binding_are_active()
and
env_binding_are_lazy()
detect the kind of bindings in an
environment. -
env_binding_lock()
andenv_binding_unlock()
allows to lock and
unlock multiple bindings. The predicateenv_binding_are_locked()
tests if bindings are locked. -
env_lock()
andenv_is_locked()
lock an environment or test if
an environment is locked. -
env_print()
pretty-prints environments. It shows the contents (up
to 20 elements) and the properties of the environment. -
is_scoped()
has been soft-deprecated and renamed to
is_attached()
. It now supports environments in addition to search
names. -
env_bind_lazy()
andenv_bind_active()
now support quosures. -
env_bind_exprs()
andenv_bind_fns()
are soft-deprecated and
renamed toenv_bind_lazy()
andenv_bind_active()
for clarity
and consistency. -
env_bind()
,env_bind_exprs()
, andenv_bind_fns()
now return
the list of old binding values (or missing arguments when there is
no old value). This makes it easy to restore the original
environment state:old <- env_bind(env, foo = "foo", bar = "bar") env_bind(env, !!!old)
-
env_bind()
now supports binding missing arguments and removing
bindings with zap sentinels.env_bind(env, foo = )
binds a missing
argument andenv_bind(env, foo = zap())
removes thefoo
binding. -
The
inherit
argument ofenv_get()
andenv_get_list()
has
changed position. It now comes afterdefault
. -
scoped_bindings()
andwith_bindings()
can now be called without
bindings. -
env_clone()
now recreates active bindings correctly. -
env_get()
now evaluates promises and active bindings since these are
internal objects which should not be exposed at the R level (#554) -
env_print()
callsget_env()
on its argument, making it easier to
see the environment of closures and quosures (#567). -
env_get()
now supports retrieving missing arguments wheninherit
isFALSE
.
Calls
-
is_call()
now accepts multiple namespaces. For instance
is_call(x, "list", ns = c("", "base"))
will match ifx
is
list()
or if it'sbase::list()
: -
call_modify()
has better support for...
and now treats it like
a named argument.call_modify(call, ... = )
adds...
to the call
andcall_modify(call, ... = NULL)
removes it. -
call_modify()
now preserves empty arguments. It is no longer
necessary to usemissing_arg()
to add a missing argument to a
call. This is possible thanks to the new.preserve_empty
option of
dots_list()
. -
call_modify()
now supports removing unexisting arguments (#393)
and passing multiple arguments with the same name (#398). The new
.homonyms
argument controls how to treat these arguments. -
call_standardise()
now handles primitive functions like~
properly (#473). -
call_print_type()
indicates how a call is deparsed and printed at
the console by R: prefix, infix, and special form. -
The
call_
functions such ascall_modify()
now correctly check
that their input is the right type (#187).
Other improvements and fixes
-
New function
zap()
returns a sentinel that instructs functions
likeenv_bind()
orcall_modify()
that objects are to be removed. -
New function
rep_named()
repeats value along a character vector of
names. -
New function
exec()
is a simpler replacement toinvoke()
(#536).invoke()
has been soft-deprecated. -
Lambda functions created from formulas with
as_function()
are now
classed. Useis_lambda()
to check a function was created with the
formula shorthand. -
is_integerish()
now supports large double values (#578). -
are_na()
now requires atomic vectors (#558). -
The operator
%@%
has now a replacement version to update
attributes of an object (#207). -
fn_body()
always returns a{
block, even if the function has a
single expression. For instancefn_body(function(x) do())
returns
quote({ do() })
. -
is_string()
now returnsFALSE
forNA_character_
. -
The vector predicates have been rewritten in C for performance.
-
The
finite
argument ofis_integerish()
is nowNULL
by
default. Missing values are now considered as non-finite for
consistency withbase::is.finite()
. -
is_bare_integerish()
andis_scalar_integerish()
gain afinite
argument for consistency withis_integerish()
. -
flatten_if()
andsquash_if()
now handle primitive functions like
base::is.list()
as predicates. -
is_symbol()
now accepts a character vector of names to mach the
symbol against. -
parse_exprs()
andparse_quos()
now support character vectors.
Note that the output may be longer than the input as each string may
yield multiple expressions (such as"foo; bar"
). -
parse_quos()
now adds thequosures
class to its output.
Lifecycle
Soft-deprecated functions and arguments
rlang 0.3.0 introduces a new warning mechanism for soft-deprecated
functions and arguments. A warning is issued, but only under one of
these circumstances:
- rlang has been attached with a
library()
call. - The deprecated function has been called from the global environment.
In addition, deprecation warnings appear only once per session in
order to not be disruptive.
Deprecation warnings shouldn't make R CMD check fail for packages
using testthat. However, expect_silent()
can transform the warning
to a hard failure.
tidyeval
-
.data[[foo]]
is now an unquote operator. This guarantees that
foo
is evaluated in the context rather than the data mask and
makes it easier to treat.data[["bar"]]
the same way as a
symbol. For instance, this will help ensuring thatgroup_by(df, .data[["name"]])
andgroup_by(df, name)
produce the same column
name. -
Automatic naming of expressions now uses a new deparser (still
unexported) instead ofquo_text()
. Following this change,
automatic naming is now compatible with all object types (via
pillar::type_sum()
if available), prevents multi-line names, and
ensuresname
and.data[["name"]]
are given the same default
name. -
Supplying a name with
!!!
calls is soft-deprecated. This name is
ignored because only the names of the spliced vector are applied. -
Quosure lists returned by
quos()
andenquos()
now have "list-of"
behaviour: the types of new elements are checked when adding objects
to the list. Consequently, assigning non-quosure objects to quosure
lists is now soft-deprecated. Please coerce to a bare list with
as.list()
beforehand. -
as_quosure()
now requires an explicit environment for symbols and
calls. This should typically be the environment in which the
expression was created. -
names()
andlength()
methods for data pronouns are deprecated.
It is no longer valid to writenames(.data)
orlength(.data)
. -
Using
as.character()
on quosures is soft-deprecated (#523).
Miscellaneous
-
Using
get_env()
without supplying an environment is now
soft-deprecated. Please usecurrent_env()
to retrieve the current
environment. -
The frame and stack API is soft-deprecated. Some of the
functionality has been replaced bytrace_back()
. -
The
new_vector_along()
family is soft-deprecated because these
functions are longer to type than the equivalentrep_along()
or
rep_named()
calls without added clarity. -
Passing environment wrappers like formulas or functions to
env_
functions is now soft-deprecated. This internal genericity was
causing confusion (see issue #427). You should now extract the
environment separately before calling these functions.This change concerns
env_depth()
,env_poke_parent()
,
env_parent<-
,env_tail()
,set_env()
,env_clone()
,
env_inherits()
,env_bind()
,scoped_bindings()
,
with_bindings()
,env_poke()
,env_has()
,env_get()
,
env_names()
,env_bind_exprs()
andenv_bind_fns()
. -
cnd_signal()
now always installs a muffling restart for
non-critical conditions. Consequently the.mufflable
argument has
been soft-deprecated and no longer has any effect.
Deprecated functions and arguments
Deprecated functions and arguments issue a warning inconditionally,
but only once per session.
-
Calling
UQ()
andUQS()
with the rlang namespace qualifier is
deprecated as of rlang 0.3.0. Just use the unqualified forms
instead:# Bad rlang::expr(mean(rlang::UQ(var) * 100)) # Ok rlang::expr(mean(UQ(var) * 100)) # Good rlang::expr(mean(!!var * 100))
Although soft-deprecated since rlang 0.2.0,
UQ()
andUQS()
can still be used for now. -
The
call
argument ofabort()
and condition constructors is now
deprecated in favour of storing full backtraces. -
The
.standardise
argument ofcall_modify()
is deprecated. Please
usecall_standardise()
beforehand. -
The
sentinel
argument ofenv_tail()
has been deprecated and
renamed tolast
.
Defunct functions and arguments
Defunct functions and arguments throw an error when used.
-
as_dictionary()
is now defunct. -
The experimental function
rst_muffle()
is now defunct. Please use
cnd_muffle()
instead. Unlike its predecessor,cnd_muffle()
is not
generic. It is marked as a calling handler and thus can be passed
directly towith_handlers()
to muffle specific conditions (such as
specific subclasses of warnings). -
cnd_inform()
,cnd_warn()
andcnd_abort()
are retired and
defunct. The oldcnd_message()
,cnd_warning()
,cnd_error()
and
new_cnd()
constructors deprecated in rlang 0.2.0 are now defunct. -
Modifying a condition with
cnd_signal()
is defunct. In addition,
creating a condition withcnd_signal()
is soft-deprecated, please
use the new function [signal()] instead. -
inplace()
has been renamed tocalling()
to follow base R
terminology more closely.
Functions and arguments in the questioning stage
We are no longer convinced these functions are the right approach but
we do not have a precise alternative yet.
-
The functions from the restart API are now in the questioning
lifecycle stage. It is not clear yet whether we want to recommend
restarts as a style of programming in R. -
prepend()
andmodify()
are in the questioning stage, as well as
as_logical()
,as_character()
, etc. We are still figuring out
what vector tools belong in rlang. -
flatten()
,squash()
and their atomic variants are now in the
questioning lifecycle stage. They have slightly different semantics
than the flattening functions in purrr and we are currently
rethinking our approach to flattening with the new typing facilities
of the vctrs package.