Skip to content

Commit

Permalink
Fix crosslinks; use R4DS styles (tidyverse#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
hadley authored Jul 20, 2023
1 parent 2ee0505 commit 2b51fc9
Show file tree
Hide file tree
Showing 31 changed files with 141 additions and 81 deletions.
4 changes: 3 additions & 1 deletion _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ book:

format:
html:
theme: cosmo
theme:
- cosmo
- r4ds.scss
code-link: true

author-meta: "Hadley Wickham"
Expand Down
12 changes: 6 additions & 6 deletions args-data-details.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Data, descriptors, details {#args-data-details}
# Data, descriptors, details {#sec-args-data-details}

```{r}
#| include = FALSE
Expand All @@ -23,12 +23,12 @@ A standard argument order makes it easier to understand a function at a glance,
Related patterns:

- `...` can play the role of the data argument (i.e. when there are an arbitrary number of inputs), as in `paste()`.
This pattern is best using sparingly, and is described in more detail in Chapter \@ref(dots-data).
This pattern is best using sparingly, and is described in more detail in @sec-dots-data.

- `...` can also be used to capture details arguments and pass them on to other functions.
See Chapters \@ref(dots-position) and \@ref(dots-inspect) to how to use `...` as safely as possible in this situation.
See @sec-dots-position and @sec-dots-inspect to how to use `...` as safely as possible in this situation.

- If the descriptor has a default value, I think you should inform the user about it, as in Chapter \@ref(def-inform).
- If the descriptor has a default value, I think you should inform the user about it, as in @sec-def-inform.

## What are some examples?

Expand Down Expand Up @@ -88,7 +88,7 @@ These families of functions represent transformations that preserve the shape wh
When combined with the pipe, this leads to code that focusses on the transformations, not the objects being transformed.
These argument types as also affect how you *call* a function.
As discussed in Chapter \@ref(call-data-details), you should never name data arguments, and always name details arguments.
As discussed in @sec-call-data-details, you should never name data arguments, and always name details arguments.
This convention balances concision with readability.
## How do I avoid the problem?
Expand All @@ -106,7 +106,7 @@ There are a couple of heuristics that you can also check for:
(Note that this ordering isn't strict: sometimes it's more important to organise related arguments together than to precisely order by importance.)
- Do any arguments with defaults come before any arguments without defaults?
This may be a sign that the argument order is wrong, or that you've assigned a default value to an required argument (See Chapter \@ref(#def-required) for more details.)
This may be a sign that the argument order is wrong, or that you've assigned a default value to an required argument (See @sec-def-required for more details.)
## How do I remediate past mistakes?
Expand Down
8 changes: 4 additions & 4 deletions args-hidden.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Avoid hidden arguments {#args-hidden}
# Avoid hidden arguments {#sec-args-hidden}

```{r}
#| include = FALSE
Expand All @@ -18,7 +18,7 @@ Related:
## What are some examples?

One common source of hidden arguments is the use of global options.
These can be useful to control display but, as discussed in Chapter \@ref(def-user)), should not affect computation:
These can be useful to control display but, as discussed in @sec-def-user), should not affect computation:

- The result of `data.frame(x = "a")$x` depends on the value of the global `stringsAsFactors` option: if it's `TRUE` (the default) you get a factor; if it's false, you get a character vector.

Expand Down Expand Up @@ -86,8 +86,8 @@ However, when they do cause problems they will take a long time to track down: y
Generally, hidden arguments are easy to avoid when creating new functions: simply avoid depending on environment variables (like the locale), or global options (like `stringsAsFactors`).
The easiest way for problems to creep in is for you to not realise a function has hidden inputs; make sure to consult the list of common offenders provided above.
If you must depend on an environment variable or option, make sure it's an explicit argument, as in Chapter \@ref(def-user).
Such arguments generally should not affect computation (only side-effects like printed output or status messages); if they do affect results, follow Chapter \@ref(def-inform) to make sure to inform the user what's happening.
If you must depend on an environment variable or option, make sure it's an explicit argument, as in @sec-def-user.
Such arguments generally should not affect computation (only side-effects like printed output or status messages); if they do affect results, follow @sec-def-inform to make sure to inform the user what's happening.
If you have an existing function with a hidden input, you'll need to take both steps above.
First make sure the input is an explicit option, and then make sure it's printed.
Expand Down
12 changes: 6 additions & 6 deletions args-independence.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Avoid dependencies between arguments {#args-independence}
# Avoid dependencies between arguments {#sec-args-independence}

```{r}
#| include = FALSE
Expand All @@ -20,7 +20,7 @@ Dependencies between arguments makes functions harder to use because you have to
rep(1:3, times = 1:3, each = 2)
```
Learn more in Chapter \@ref(cs-rep).
Learn more in Chapter @sec-cs-rep.
- In `var()`, `na.rm` is only used if `use` is not set.
If you supply both `use` and `na.rm`, `na.rm` is silently ignored.
Expand Down Expand Up @@ -59,7 +59,7 @@ Dependencies between arguments makes functions harder to use because you have to
- In `readr::locale()` there's a complex dependency between `decimal_mark` and `grouping_mark` because they can't be the same value, and the US and Europe use different standards.
See Sections \@ref(args-mutually-exclusive) and \@ref(args-compound) for a two exceptions where the dependency is via specific patterns of missing arguments.
See @sec-args-mutually-exclusive) and @sec-args-compound for a two exceptions where the dependency is via specific patterns of missing arguments.
## Why is this important?
Expand Down Expand Up @@ -89,12 +89,12 @@ There are two common outcomes which are illustrated in the case studies below:
- Encapulsating related details arguments into a single object.
See also larger case study in Chapter \@ref(cs-rep) where this problem is tangled up with other problems.
See also larger case study in @sec-cs-rep where this problem is tangled up with other problems.
If these changes to the interface occur to exported functions in a package, you'll need to consider how to preserve the interface with deprecation warnings.
For important functions, it is worth generating an message that includes new code to copy and paste.
### Case study: `fct_lump()` {#cs-fct-lump}
### Case study: `fct_lump()` {#sec-cs-fct-lump}
There are many different ways to decide how to lump uncommon factor levels together, and initially we attempted to encode these through arguments to `fct_lump()`.
However, over time as the number of arguments increased, it gets harder and harder to tell what the options are.
Expand Down Expand Up @@ -139,7 +139,7 @@ x <- grepl("a", letters, fixed = TRUE, perl = TRUE)
```

Part of this problem could be resolved by making it more clear that one important choice is the matching engine to use: POSIX 1003.2 extended regular expressions (the default), Perl-style regular expressions (`perl = TRUE`) or fixed matching (`fixed = TRUE`).
A better approach would be to use the pattern in Chapter \@ref(def-enum), and create a new argument called something like `engine = c("POSIX", "perl", "fixed")`.
A better approach would be to use the pattern in @sec-def-enum and create a new argument called something like `engine = c("POSIX", "perl", "fixed")`.

The other problem is that `ignore.case` can only affect two of the three engines: POSIX and perl.
This is hard to remedy without creating a completely new matching engine.
Expand Down
2 changes: 1 addition & 1 deletion call-data-details.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Name details arguments {#call-data-details}
# Name details arguments {#sec-call-data-details}

```{r}
#| include = FALSE
Expand Down
2 changes: 1 addition & 1 deletion changes-multivers.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Work with multiple dependency versions {#changes-multivers}
# Work with multiple dependency versions {#sec-changes-multivers}

```{r}
#| include = FALSE
Expand Down
4 changes: 2 additions & 2 deletions cs-mapply-pmap.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Case study: `mapply()` vs `pmap()` {#cs-mapply-pmap}
# Case study: `mapply()` vs `pmap()` {#sec-cs-mapply-pmap}

```{r}
#| include = FALSE
Expand Down Expand Up @@ -55,4 +55,4 @@ purrr::pmap_chr(list(pattern, replacement, x), gsub, fixed = TRUE)
There's a subtle difference here that doesn't matter in most cases - in the `mapply()` `fixed` is recycled to the same length as `pattern` whereas it is not `pmap()`.
TODO: figure out example where that's more clear.

(Also note that `pmap()` uses the `.` prefix to avoid the problem described in Chapter \@ref(dots-prefix).)
(Also note that `pmap()` uses the `.` prefix to avoid the problem described in Chapter @sec-dots-prefix.)
2 changes: 1 addition & 1 deletion cs-rep.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ depends_on:
- args-independence
---

# Case study: `rep()` {#cs-rep}
# Case study: `rep()` {#sec-cs-rep}

```{r}
#| include = FALSE
Expand Down
2 changes: 1 addition & 1 deletion cs-rgb.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Case study: `rgb()` {#cs-rgb}
# Case study: `rgb()` {#sec-cs-rgb}

```{r}
#| include = FALSE
Expand Down
2 changes: 1 addition & 1 deletion cs-setNames.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ It was defined this way to make it possible to name a character vector with itse
setNames(nm = c("apple", "banana", "cake"))
```

But that decision leads to a function signature that violates one of the principles of Chapter \@ref(args-data-details): a required argument comes after an optional argument.
But that decision leads to a function signature that violates one of the principles of @sec-args-data-details: a required argument comes after an optional argument.
Fortunately, we can fix this easily and still preserve the useful ability to name a vector with itself:

```{r}
Expand Down
4 changes: 2 additions & 2 deletions def-enum.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Enumerate possible options {#def-enum}
# Enumerate possible options {#sec-def-enum}

```{r}
#| include = FALSE
Expand Down Expand Up @@ -115,7 +115,7 @@ rank2(x, ties.method = "r")

This technique is a best used when the set of possible values is short.
You can see that it's already getting unwieldy in `rank()`.
If you have a long list of possibilities, there are two options that you could use from Chapter \@ref(def-short).
If you have a long list of possibilities, there are two options that you could use from @sec-def-short.
Unfortunately both approaches have major downsides:

- Set a single default and supply the possible values to `match.arg()`:
Expand Down
6 changes: 3 additions & 3 deletions def-inform.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Explain important defaults {#def-inform}
# Explain important defaults {#sec-def-inform}

```{r}
#| include = FALSE
Expand All @@ -10,7 +10,7 @@ library(dplyr, warn.conflicts = FALSE)

If a default value is important, and the computation is non-trivial, inform the user what value was used.
This is particularly important when the default value is an educated guess, and you want the user to change it.
It is also important when descriptor arguments (Chapter \@ref(args-data-details)) have defaults.
It is also important when descriptor arguments (@sec-args-data-details)) have defaults.

## What are some examples?

Expand Down Expand Up @@ -126,7 +126,7 @@ common_by(flights, planes)
The technique you use to generate the code will vary from function to function.
`rlang::expr_text()` is useful here because it automatically creates the code you'd use to build the character vector.

To avoid creating a magical default (Chapter \@ref(def-magical)), either export and document the function, or use the technique of Section \@ref(arg-short-null):
To avoid creating a magical default (@sec-def-magical), either export and document the function, or use the technique of @sec-arg-short-null:

```{r}
left_join <- function(x, y, by = NULL) {
Expand Down
8 changes: 4 additions & 4 deletions def-magical.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Avoid magical defaults {#def-magical}
# Avoid magical defaults {#sec-def-magical}

```{r}
#| include = FALSE,
Expand Down Expand Up @@ -148,9 +148,9 @@ This problem is generally easy to avoid for new functions:
- Don't use unexported functions.
[^def-magical-1]: The only exceptions are described in Sections \@ref(args-mutually-exclusive) and \@ref(args-compound).
[^def-magical-1]: The only exceptions are described in @sec-args-mutually-exclusive) and @sec-args-compound).
If you have a made a mistake in an older function you can remediate it by using a `NULL` default, as described in Chapter \@ref(def-short).
If you have a made a mistake in an older function you can remediate it by using a `NULL` default, as described in @sec-def-short).
If the problem is caused by an unexported function, you can also choose to document and export it.
```{r}
Expand All @@ -172,7 +172,7 @@ f2_better <- function(x = NULL) {

This modification should not break existing code, because expands the function interface: all previous code will continue to work, and the function will also work if the argument is passed `NULL` input (which probably didn't previously).

For functions like `data.frame()` where `NULL` is already a permissible value, you'll need to use a sentinel object, as described in Section \@ref(args-default-sentinel).
For functions like `data.frame()` where `NULL` is already a permissible value, you'll need to use a sentinel object, as described in @sec-args-default-sentinel.

```{r}
sentinel <- function() structure(list(), class = "sentinel")
Expand Down
10 changes: 5 additions & 5 deletions def-required.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Required args shouldn't have defaults {#def-required}
# Required args shouldn't have defaults {#sec-def-required}

```{r}
#| include = FALSE
Expand Down Expand Up @@ -63,7 +63,7 @@ There are two exceptions to this rule:
In both cases, I believe the benefits outweigh the costs of violating a standard pattern.
### Pair of mututally exclusive arguments {#args-mutually-exclusive}
### Pair of mututally exclusive arguments {#sec-args-mutually-exclusive}
A number of functions that allow you to supply exactly one of two possible arguments:
Expand Down Expand Up @@ -96,9 +96,9 @@ And in the documentation, make it clear that only one of the pair can be supplie

This technique should only be used for are exactly two possible arguments.
If there are more than two , that is generally a sign you should create more functions.
See case studies in Chapter \@ref(cs-rep) and Section \@ref(cs-fct-lump) for examples.
See case studies in @sec-cs-rep and @sec-cs-fct-lump for examples.

### One compound argument vs multiple simple arguments {#args-compound}
### One compound argument vs multiple simple arguments {#sec-args-compound}

A related, if less generally useful, form is to allow the user to supply either a single complex argument or several smaller arguments.
For example:
Expand All @@ -107,7 +107,7 @@ For example:

- `stringr::str_replace_all(x, c(pattern = replacement))` is equivalent to `stringr(x, pattern, replacement)`.

- `rgb(cbind(r, g, b))` is equivalent to `rgb(r, g, b)` (See Chapter \@ref(cs-rgb) for more details).
- `rgb(cbind(r, g, b))` is equivalent to `rgb(r, g, b)` (See @sec-cs-rgb for more details).

- `options(list(a = 1, b = 2))` is equivalent to `options(a = 1, b = 2)`.

Expand Down
8 changes: 4 additions & 4 deletions def-short.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Keep defaults short and sweet {#def-short}
# Keep defaults short and sweet {#sec-def-short}

```{r}
#| include = FALSE
Expand Down Expand Up @@ -52,13 +52,13 @@ The following examples, drawn from base R, illustrate some functions that don't
There are three approaches:
- Set the default value to `NULL` and calculate the default only when the argument is `NULL`.
Providing a default of `NULL` signals that the argument is optional (Chapter \@ref(def-required)) but that the default requires some calculation.
Providing a default of `NULL` signals that the argument is optional (@sec-def-required) but that the default requires some calculation.
- If the calculation is complex, and the user might find it useful in other scenarios, compute it with an exported function that documents exactly what happens.
- If `NULL` is meaningful, so you can't use the first approach, use a "sentinel" object instead.
### `NULL` default {#arg-short-null}
### `NULL` default {#sec-arg-short-null}
The most common approach is to use `NULL` as a sentinel value that indicates that the argument is optional, but non-trivial.
This pattern is made substantially more elegant with the infix `%||%` operator.
Expand Down Expand Up @@ -111,7 +111,7 @@ For more complicated cases, you'll probably want to pull the code that computes
A good example of this pattern is `readr::show_progress()`: it's used in every `read_` function in readr and it's sufficiently complicated that you don't want to copy and paste it between functions.
It's also nice to document it in its own file, rather than cluttering up file reading functions with incidental details.

### Sentinel value {#args-default-sentinel}
### Sentinel value {#sec-args-default-sentinel}

Sometimes a default argument has a complex calculation that you don't want to include in arguments list.
You'd normally use `NULL` to indicate that it's calculated by default, but `NULL` is a meaningful option.
Expand Down
4 changes: 2 additions & 2 deletions def-user.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# User settable defaults {#def-user}
# User settable defaults {#sec-def-user}

```{r}
#| include = FALSE
Expand All @@ -15,7 +15,7 @@ The two primary uses are for controlling the appearance of output, particularly

Related patterns:

- If a global option affects the results of the computation (not just its side-effects), you have an example of Chapter \@ref(args-hidden).
- If a global option affects the results of the computation (not just its side-effects), you have an example of @sec-args-hidden.

## What are some examples?

Expand Down
4 changes: 2 additions & 2 deletions dots-data.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Making data with ... {#dots-data}
# Making data with ... {#sec-dots-data}

```{r}
#| include = FALSE
Expand Down Expand Up @@ -39,7 +39,7 @@ In general, I think it is best to avoid using `...` for this purpose because it
mean(1, 2, 3)
```
(See Chapter \@ref(dots-position) to learn why this doesn't give an error message.)
(See Chapter @sec-dots-position to learn why this doesn't give an error message.)
- It makes it harder to adapt the function for new uses.
For example, `fct_relevel()` can also be called with a function:
Expand Down
4 changes: 2 additions & 2 deletions dots-inspect.Rmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Inspect the dots {#dots-inspect}
# Inspect the dots {#sec-dots-inspect}

```{r}
#| include = FALSE
Expand Down Expand Up @@ -90,7 +90,7 @@ str_sort(x, numeric = TRUE)
This is wrapper is useful because it decouples `str_sort()` from the `stri_opts_collator()` meaning that if `stri_opts_collator()` gains new arguments users of `str_sort()` can take advantage of them immediately.
But most of the arguments in `stri_opts_collator()` are sufficiently arcane that they don't need to be exposed directly in stringr, which is designed to minimise the cognitive load of the user, by hiding some of the full complexity of string handling.

(The importance of the `locale` argument comes up in "hidden inputs", Chapter \@ref(args-hidden).)
(The importance of the `locale` argument comes up in "hidden inputs", @sec-args-hidden.)

However, `stri_opts_collator()` deliberately ignores any arguments in `...`.
This means that misspellings are silently ignored:
Expand Down
Loading

0 comments on commit 2b51fc9

Please sign in to comment.