Like any parse tree, R
expressions are structured as trees of nodes. Each node has two
components: the head and the tail (though technically there is
actually a third component for argument names, see details). Due to
R's lisp roots, the
head of a node (or cons cell) is called the CAR and the tail is
called the CDR (pronounced car and cou-der). While R's ordinary
subsetting operators have builtin support for indexing into these
trees and replacing elements, it is sometimes useful to manipulate
the nodes more directly. This is the purpose of functions like
mut_node_car(). They are particularly useful to
prototype algorithms for your C-level functions.
mut_node_car() access or change the head of a node.
mut_node_cdr() access or change the tail of a node.
mut_node_cdar() deal with the
CAR of the CAR of a node or the CDR of the CAR of a node
respectively. The letters in the middle indicate the type (CAR or
CDR) and order of access.
mut_node_tag() access or change the tag of a
node. This is meant for argument names and should only contain
symbols (not strings).
node() creates a new node from two components.
node(newcar, newcdr) node_car(x) node_cdr(x) node_caar(x) node_cadr(x) node_cdar(x) node_cddr(x) mut_node_car(x, newcar) mut_node_cdr(x, newcdr) mut_node_caar(x) mut_node_cadr(x, newcar) mut_node_cdar(x) mut_node_cddr(x, newcdr) node_tag(x) mut_node_tag(x, newtag)
The new CAR or CDR for the node. These can be any R objects.
A language or pairlist node. Note that these functions are barebones and do not perform any type checking.
The new tag for the node. This should be a symbol.
R has two types of nodes to represent parse trees: language nodes, which represent function calls, and pairlist nodes, which represent arguments in a function call. These are the exact same data structures with a different name. This distinction is helpful for parsing the tree: the top-level node of a function call always has language type while its arguments have pairlist type.
Note that it is risky to manipulate calls at the node level. First,
the calls are changed inplace. This is unlike base R operators
which create a new copy of the language tree for each modification.
To make sure modifying a language object does not produce
side-effects, rlang exports the
duplicate() function to create
deep copy (or optionally a shallow copy, i.e. only the top-level
node is copied). The second danger is that R expects language trees
to be structured as a
NULL-terminated list. The CAR of a node is
a data slot and can contain anything, including another node (which
is how you form trees, as opposed to mere linked lists). On the
other hand, the CDR has to be either another node, or
NULL. If it
is terminated by anything other than the
NULL object, many R
commands will crash, including functions like
str(). It is up to
you to ensure that the language list you have modified is
Finally, all nodes can contain metadata in the TAG slot. This is meant for argument names and R expects tags to contain a symbol (not a string).
duplicate() for creating copy-safe objects,
lang_tail() as slightly higher level
alternatives that check their input, and
an easier way of creating a linked list of nodes.
# Changing a node component happens in place and can have side # effects. Let's create a language object and a copy of it: lang <- quote(foo(bar)) copy <- lang # Using R's builtin operators to change the language tree does not # create side effects: copy[] <- quote(baz) copy#> foo(baz)lang#> foo(bar)# On the other hand, the CAR and CDR operators operate in-place. Let's # create new objects since the previous examples triggered a copy: lang <- quote(foo(bar)) copy <- lang # Now we change the argument pairlist of `copy`, making sure the new # arguments are NULL-terminated: mut_node_cdr(copy, node(quote(BAZ), NULL))#> foo(BAZ)# Or equivalently: mut_node_cdr(copy, pairlist(quote(BAZ)))#> foo(BAZ)copy#> foo(BAZ)# The original object has been changed in place: lang#> foo(BAZ)