Skip to content

Commit

Permalink
Continuing to work on meta
Browse files Browse the repository at this point in the history
  • Loading branch information
hadley committed Oct 23, 2017
1 parent 2bc10d8 commit 2c41886
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 175 deletions.
109 changes: 0 additions & 109 deletions Expressions.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -326,39 +326,6 @@ x

But there are some important caveats which we'll come back to after discussion how to construct a call from scratch.


### Constructing calls

In base R, you can construct a call using the `call()` function. We are going to use the similar function `rlang::lang()`. The chief difference is that `lang()` supports quasiquotation. This makes it considerably easier to generate certain types of call.

The basics of `lang()` are simple. You create a call giving the name of a function, followed by the arguments:

```{r}
lang("+", 1, 2)
lang("foo", x = 1, y = 2)
```

Here we've used a convenient shortcut: we've given it the name of the fuction as a string not a call. In most cases a string is easier to type and directly equivalent to the `quote()`d equivalent:

```{r}
lang(expr(f), 1, 2)
lang("f", 1, 2)
```

However, this will not work if the function is generated by a function call. Note the subtle difference in these two calls:

```{r}
lang(quote(f()), 1, 2)
lang("f()", 1, 2)
```

The first uses the function generated by calling `f()`, the second calls a function with the confusing name `f()`:

```{r}
`f()` <- function(x) x + 1
`f()`(1)
```

## Parsing and deparsing {#parsing-and-deparsing}

Sometimes code is represented as a string, rather than as an expression. You can convert a string to an expression with `parse()`. `parse()` is the opposite of `deparse()`: it takes a character vector and returns an expression object. The primary use of `parse()` is parsing files of code to disk, so the first argument is a file path. Note that if you have code in a character vector, you need to use the `text` argument: \indexc{parse()}
Expand Down Expand Up @@ -430,82 +397,6 @@ The real `source()` is considerably more complicated because it can `echo` input
successful, when you look at the function, you'll see the comment and
not just the source code.


### Inlining and the deparser

If you construct ASTs by hand, it's possible to construct things that you could not construct by parsing code. For example, if you forget to quote the first argument to `lang` it will literally inline the funtion call:

```{r}
lang(sum, quote(x))
```

It's also possible to inline objects that are not constants, symbols, or calls. This is useful in a handful of places (beyond the scope of the book, but typically useful in overscoping). The main thing to be aware of is that the the printed representation does not always accurately reflect the underlying tree. Trust `ast()` over what the console will print.

R will print parentheses that don't exist in the call tree:

```{r}
x1 <- lang("+", 1, lang("+", 2, 3))
x1
lobstr::ast(!!x1)
```

It will also display integer sequences as if they were generated with `:`.

```{r}
x2 <- lang("f", c(1L, 2L, 3L, 4L, 5L))
x2
lobstr::ast(!!x2)
```

If you inline more complex objects, their attributes are not printed which might lead to confusing output:

```{r}
x3 <- lang("class", data.frame(x = 10))
x3
eval(x3)
lobstr::ast(!!x3)
```

In general, if you're ever confused, remember to check the object with `ast()`!

### Exercises

1. The following two calls look the same, but are actually different:

```{r}
(a <- call("mean", 1:10))
(b <- call("mean", quote(1:10)))
identical(a, b)
```
What's the difference? Which one should you prefer?
1. `standardise_call()` doesn't work so well for the following calls.
Why?
```{r}
lang_standardise(quote(mean(1:10, na.rm = TRUE)))
lang_standardise(quote(mean(n = T, 1:10)))
lang_standardise(quote(mean(x = 1:10, , TRUE)))
```
1. Read the documentation for `rlang::lang_modify()`. How do you think
it works? Read the source code.
1. One important feature of `deparse()` to be aware of when programming is that
it can return multiple strings if the input is too long. For example, the
following call produces a vector of length two:
```{r, eval = FALSE}
g(a + b + c + d + e + f + g + h + i + j + k + l + m +
n + o + p + q + r + s + t + u + v + w + x + y + z)
```
Why does this happen? Carefully read the documentation for `?deparse`. Can you write a
wrapper around `deparse()` so that it always returns a single string?
## Exercises that need a home

1. You can use `formals()` to both get and set the arguments of a function.
Expand Down
Loading

0 comments on commit 2c41886

Please sign in to comment.