Skip to content

rlang 0.3.0

Compare
Choose a tag to compare
@lionel- lionel- released this 23 Oct 13:51

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() and quo_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 than quo_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 to quos(x, y).

  • The sentinel for removing arguments in call_modify() has been
    changed from NULL to zap(). This breaking change is motivated
    by the ambiguity of NULL 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 with abort() 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 with trace_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 a parent 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() or rlang::with_handlers() and rethrow them with
    a high-level message. Call abort() with the intercepted error as
    the parent argument. When the user prints rlang::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 with abort(). When that's not the case, you can call the
    low-level function within with_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 that var 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
    calls as.list() on S3 objects and as(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 a parent 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 with rlang::last_error(). The error prints with a
    simplified backtrace. Call summary(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 the abort(), 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 function Rf_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() and inform() 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 to tryCatch()). 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 with list2()
    and in quoting functions. !!! accepts these types:

    • Lists, pairlists, and atomic vectors. If they have a class, they
      are converted with base::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() and enquos() 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() and enquos() now support .ignore_empty = "all"
    with named arguments as well (#414).

  • dots_list() gains a .preserve_empty argument. When TRUE, empty
    arguments are stored as missing arguments (see ?missing_arg).

  • dots_list(), enexprs() and enquos() gain a .check_assign
    argument. When TRUE, 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 evaluates foo (#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 roundtrip parse_expr(expr_deparse(x)) is not
    lossy.

  • new_data_mask() now throws an error when bottom is not a child
    of top (#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() and as_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() and current_fn() functions to retrieve the
    current environment or the function being evaluated. They are
    equivalent to base::environment() and base::sys.function()
    called without argument.

  • env_get() and env_get_list() gain a default 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 companion env_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() and env_tail() now stop at the global environment
    by default. This can be changed with the last 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() and env_binding_unlock() allows to lock and
    unlock multiple bindings. The predicate env_binding_are_locked()
    tests if bindings are locked.

  • env_lock() and env_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() and env_bind_active() now support quosures.

  • env_bind_exprs() and env_bind_fns() are soft-deprecated and
    renamed to env_bind_lazy() and env_bind_active() for clarity
    and consistency.

  • env_bind(), env_bind_exprs(), and env_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 and env_bind(env, foo = zap()) removes the foo
    binding.

  • The inherit argument of env_get() and env_get_list() has
    changed position. It now comes after default.

  • scoped_bindings() and with_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() calls get_env() on its argument, making it easier to
    see the environment of closures and quosures (#567).

  • env_get() now supports retrieving missing arguments when inherit
    is FALSE.

Calls

  • is_call() now accepts multiple namespaces. For instance
    is_call(x, "list", ns = c("", "base")) will match if x is
    list() or if it's base::list():

  • call_modify() has better support for ... and now treats it like
    a named argument. call_modify(call, ... = ) adds ... to the call
    and call_modify(call, ... = NULL) removes it.

  • call_modify() now preserves empty arguments. It is no longer
    necessary to use missing_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 as call_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
    like env_bind() or call_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 to invoke()
    (#536). invoke() has been soft-deprecated.

  • Lambda functions created from formulas with as_function() are now
    classed. Use is_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 instance fn_body(function(x) do()) returns
    quote({ do() }).

  • is_string() now returns FALSE for NA_character_.

  • The vector predicates have been rewritten in C for performance.

  • The finite argument of is_integerish() is now NULL by
    default. Missing values are now considered as non-finite for
    consistency with base::is.finite().

  • is_bare_integerish() and is_scalar_integerish() gain a finite
    argument for consistency with is_integerish().

  • flatten_if() and squash_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() and parse_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 the quosures 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 that group_by(df, .data[["name"]]) and group_by(df, name) produce the same column
    name.

  • Automatic naming of expressions now uses a new deparser (still
    unexported) instead of quo_text(). Following this change,
    automatic naming is now compatible with all object types (via
    pillar::type_sum() if available), prevents multi-line names, and
    ensures name 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() and enquos() 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() and length() methods for data pronouns are deprecated.
    It is no longer valid to write names(.data) or length(.data).

  • Using as.character() on quosures is soft-deprecated (#523).

Miscellaneous

  • Using get_env() without supplying an environment is now
    soft-deprecated. Please use current_env() to retrieve the current
    environment.

  • The frame and stack API is soft-deprecated. Some of the
    functionality has been replaced by trace_back().

  • The new_vector_along() family is soft-deprecated because these
    functions are longer to type than the equivalent rep_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() and env_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() and UQS() 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() and UQS() can still be used for now.

  • The call argument of abort() and condition constructors is now
    deprecated in favour of storing full backtraces.

  • The .standardise argument of call_modify() is deprecated. Please
    use call_standardise() beforehand.

  • The sentinel argument of env_tail() has been deprecated and
    renamed to last.

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 to with_handlers() to muffle specific conditions (such as
    specific subclasses of warnings).

  • cnd_inform(), cnd_warn() and cnd_abort() are retired and
    defunct. The old cnd_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 with cnd_signal() is soft-deprecated, please
    use the new function [signal()] instead.

  • inplace() has been renamed to calling() 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() and modify() 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.