18.6 Specialised data structures
There are two data structures and one special symbol that we need to cover for the sake of completeness. They are not usually important in practice.
Pairlists are a remnant of R’s past and have been replaced by lists almost everywhere. The only place you are likely to see pairlists in R66 is when working with calls to the
function function, as the formal arguments to a function are stored in a pairlist:
expr(function(x, y = 10) x + y) f <- f[] args <- args#> $x #> #> #> $y #>  10 typeof(args) #>  "pairlist"
Fortunately, whenever you encounter a pairlist, you can treat it just like a regular list:
pairlist(x = 1, y = 2) pl <-length(pl) #>  2 $x pl#>  1
Behind the scenes pairlists are implemented using a different data structure, a linked list instead of an array. That makes subsetting a pairlist much slower than subsetting a list, but this has little practical impact.
18.6.2 Missing arguments
The special symbol that needs a little extra discussion is the empty symbol, which is used to represent missing arguments (not missing values!). You only need to care about the missing symbol if you’re programmatically creating functions with missing arguments; we’ll come back to that in Section 19.4.3.
You can make an empty symbol with
missing_arg() typeof(missing_arg()) #>  "symbol"
An empty symbol doesn’t print anything, so you can check if you have one with
is_missing(missing_arg()) #>  TRUE
You’ll find them in the wild in function formals:
expr(function(x, y = 10) x + y) f <- f[] args <-is_missing(args[]) #>  TRUE
This is particularly important for
... which is always associated with an empty symbol:
expr(function(...) list(...)) f <- f[] args <-is_missing(args[]) #>  TRUE
The empty symbol has a peculiar property: if you bind it to a variable, then access that variable, you will get an error:
missing_arg() m <- m#> Error in eval(expr, envir, enclos): argument "m" is missing, with no default
But you won’t if you store it inside another data structure!
list(missing_arg(), missing_arg()) ms <-1]]ms[[
If you need to preserve the missingness of a variable,
rlang::maybe_missing() is often helpful. It allows you to refer to a potentially missing variable without triggering the error. See the documentation for use cases and more details.
18.6.3 Expression vectors
Finally, we need to briefly discuss the expression vector. Expression vectors are only produced by two base functions:
parse(text = c(" exp1 <-x <- 4 x ")) expression(x <- 4, x) exp2 <- typeof(exp1) #>  "expression" typeof(exp2) #>  "expression" exp1#> expression(x <- 4, x) exp2#> expression(x <- 4, x)
Like calls and pairlists, expression vectors behave like lists:
length(exp1) #>  2 1]] exp1[[#> x <- 4
Conceptually, an expression vector is just a list of expressions. The only difference is that calling
eval() on an expression evaluates each individual expression. I don’t believe this advantage merits introducing a new data structure, so instead of expression vectors I just use lists of expressions.
If you’re working in C, you’ll encounter pairlists more often. For example, call objects are also implemented using pairlists.↩︎