call_match: Match patterns to a call

Description Usage Arguments Value Pattern rules call_match() versus node_match() Named patterns Evaluated bindings Examples

Description

node_match() provides a flexible way of checking whether a quoted call conforms to a pattern. It compares the call to a set of pattern ~ expression pairs. If the LHS of a formula (e.g. a pattern) matches, the RHS of the formula is evaluated; otherwise the next formula is checked. Patterns support named and positional arguments, and can involve wildcards that match anything.

If the wildcards are named, the part of the call that matched the wildcard will be assigned under that name in the environment where the RHS is evaluated. This technique makes it easy to provide specific responses to different inputs.

Usage

1
2
3
call_match(.x, ..., .env = caller_env())

node_match(.x, ..., .env = caller_env())

Arguments

.x

An expression to match.

...

Two sided formulas with patterns on the LHS and expressions on the RHS. If the LHS matches .x, the RHS is evaluated.

.env

An environment in which to evaluate the RHS. By default the current environment.

Value

The result of evaluating the RHS of a matching pattern. NULL if no pattern has matched .x

Pattern rules

A pattern typically involves one call (e.g. fn(1, 2)), and possibly subcalls (as arguments of an outer call, e.g. fn(other(), 2)). The arguments of a call in the pattern are checked using the following rules:

In addition, patterns can contain wildcards that will match anything:

Patterns are supplied as formulas with the pattern on the left-hand side and an expression of your choosing on the right-hand side. The expression is evaluated only if the pattern match. It can evaluate to a sentinel value (so you know which expression matched) or to some checking code. If no pattern matches, the return value of is NULL. For example the following returns NULL:

1
2
3
4
5
expr <- quote(call(foo, bar))
node_match(expr,
  call(baz) ~ 1,
  call(.) ~ 2
)

While this returns 2:

1
2
3
4
node_match(expr,
  call(foo, baz) ~ 1,
  call(foo, .) ~ 2
)

call_match() versus node_match()

call_match() is just like node_match() except it standardises the .x call and pattern calls with rlang::call_standardise(). Standardisation ensures that arguments are matched by position rather than by name. Note that only function arguments positioned before ... are normalised.

Named patterns

Argument names are parsed to R code. This makes it easy to supply binding wildcards as names, e.g.

1
call(`.(arg_name)` = .)

On the other hand it makes it more difficult to supply non-syntactic names as you have to double quote. Either one of the following types of double-quotes will work:

1
2
call("`non-syntactic`" = .)
call(`\`non-syntactic\`` = .)

Evaluated bindings

It is often useful to check the value of an argument rather than its symbolic form. The eval-bind wildcard ..() makes it easy because it evaluates the matched argument and binds it to a symbol. You can then refer to that symbol in the right-hand side:

1
call(. = ..(foo)) ~ is.numeric(foo)

However you need to be a bit careful when evaluating R code this way.

Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# Let's create a call to dplyr::mutate(). We'll try to match this code
# with various patterns:
call <- quote(mutate(df, weight_sq = weight^2))

# A pattern is an expression supplied as a formula. The pattern is on
# the LHS and the expression on the RHS is evaluated only if the LHS
# matches. Here the second call matches and the RHS evaluates to `2`:
node_match(call,
  mutate(df)                       ~ 1,
  mutate(df, weight_sq = weight^2) ~ 2
)

# If further arguments do not matter, use `...` in the pattern:
node_match(call,
  mutate(df)      ~ 1,
  mutate(df, ...) ~ 2
)

# The following patterns do not match because the data frame goes by
# another name. Instead of returning the RHS of a matching pattern,
# node_match() returns `NULL`:
node_match(call,
  mutate(x, ...)             ~  1,
  mutate(my_data_frame, ...) ~  2
)

# Let's use a wildcard so the data frame doesn't count. Since we have
# a match, node_match() returns the pattern RHS, `2`:
node_match(call,
  mutate(x, ...) ~ 1,
  mutate(., ...) ~ 2
)

# Wildcards also apply to names:
node_match(call,
  mutate(., weight^2)         ~ 1,
  mutate(., wrong = weight^2) ~ 2,
  mutate(., . = weight^2)     ~ 3
)

# If you want to match an argument regardless of whether it has a
# name, use the ellipsis wildcard instead:
node_match(quote(call(arg)),
  call(. = arg)   ~ 1,
  call(... = arg) ~ 2
)

# The RHS is a handy way of providing custom error messages:
fail_unnamed <- function() {
  stop("You should provide a named argument")
}
check_sq_suffix <- function(nm) {
  if (!grepl(".*_sq$", nm)) {
    stop("The new variable must end with `_sq`")
  }
  message("Alright!")
}
node_match(call,
  mutate(., .)             ~ fail_unnamed(),
  mutate(., `.(nm)` = .^2) ~ check_sq_suffix(nm),
  .                        ~ message("Try again")
)

lionel-/redpen documentation built on May 30, 2019, 4:37 p.m.