forked from hadley/r-pkgs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dependencies-in-practice.Rmd
782 lines (586 loc) · 35 KB
/
dependencies-in-practice.Rmd
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
# Dependencies: In Practice {#sec-dependencies-in-practice}
```{r, echo = FALSE}
source("common.R")
```
This chapter presents the practical details of working with your dependencies inside your package.
If you need a refresher on any of the background:
- @sec-description covers the DESCRIPTION file.
Listing a dependency in that file, such as in `Imports`, is a necessary first step when taking a dependency.
- @sec-dependencies-pros-cons provides a decision-making framework for dependencies.
- The technical details of package namespaces, the search path, and attaching vs. loading are laid out in @sec-dependencies-namespace, @sec-dependencies-search, and @sec-dependencies-attach-vs-load.
We're finally ready to talk about how to use different types of dependencies within the different parts of your package:
- in your functions, below `R/`
- in your tests, below `tests/testthat/`
- in your examples, in the help topics, below `man/`
- in your vignettes and articles, below `vignettes/`
## Confusion about `Imports`
Let's make this crystal clear:
> Listing a package in `Imports` in `DESCRIPTION` does not "import" that package.
It is natural to assume that listing a package in `Imports` actually "imports" the package, but this is just an unfortunate choice of name for the `Imports` field.
The `Imports` field makes sure that the packages listed there are installed when your package is installed.
It does not make those functions available to you, e.g. below `R/`, or to your user.
It is neither automatic nor necessarily advisable that a package listed in `Imports` also appears in `NAMESPACE` via `imports()` or `importFrom()`.
It is common for a package to be listed in `Imports` in `DESCRIPTION`, but not in `NAMESPACE`.
The converse is not true.
Every package mentioned in `NAMESPACE` must also be present in the `Imports` or `Depends` fields.
## Conventions for this chapter
Sometimes our examples can feature real functions from real packages.
But if we need to talk about a generic package or function, here are the conventions we use below:
- pkg: the name of your hypothetical package
- aaapkg or bbbpkg: the name of a hypothetical package your package depends on
- `aaa_fun()`: the name of a function exported by aaapkg
## `NAMESPACE` Workflow {#sec-dependencies-NAMESPACE-workflow}
In the sections below, we give practical instructions on how (and when) to import functions from another package into yours and how to export functions from your package.
The file that keeps track of all this is the `NAMESPACE` file (more details in @sec-dependencies-NAMESPACE-file).
In the devtools workflow and this book, we generate the `NAMESPACE` file from special comments in the `R/*.R` files.
Since the package that ultimately does this work is roxygen2, these are called "roxygen comments".
These roxygen comments are also the basis for your package's help topics, which is covered in @sec-man-workflow.
The `NAMESPACE` file starts out with a single commented-out line explaining the situation (and hopefully discouraging any manual edits):
```
# Generated by roxygen2: do not edit by hand
```
As you incorporate roxygen tags to export and import functions, you need to re-generate the `NAMESPACE` file periodically.
Here is the general workflow for regenerating `NAMESPACE` (and your documentation):
1. Add namespace-related tags to the roxygen comments in your `R/*.R` files.
This is an artificial example, but it gives you the basic idea:
```{r}
#| eval: false
#' @importFrom aaapkg aaa_fun
#' @import bbbpkg
#' @export
foo <- function(x, y, z) {
...
}
```
2. Run `devtools::document()` (or press Ctrl/Cmd + Shift + D in RStudio) to "document" your package.
By default, two things happen:
- The help topics in the `man/*.Rd` files are updated (covered in @sec-man).
- The `NAMESPACE` file is re-generated.
In our example, the `NAMESPACE` file would look like:
```
# Generated by roxygen2: do not edit by hand
export(foo)
import(bbbpkg)
importFrom(aaapkg,aaa_fun)
```
Roxygen2 is quite smart and will insert the appropriate directive in `NAMESPACE`, i.e. it can usually determine whether to use `export()` or `S3method()`.
::: callout-tip
## RStudio
Press Ctrl/Cmd + Shift + D, to generate your package's `NAMESPACE` (and `man/*.Rd` files).
This is also available via *Document* in the *Build* menu and pane.
:::
## Package is listed in `Imports` {#sec-dependencies-in-imports}
Consider a dependency that is listed in `DESCRIPTION` in `Imports`:
``` yaml
Imports:
aaapkg
```
The code inside your package can assume that aaapkg is installed whenever pkg is installed.
### In code below R/ {#sec-dependencies-in-imports-r-code}
Our recommended default is to call external functions using the `package::function()` syntax:
```{r}
#| eval: false
somefunction <- function(...) {
...
x <- aaapkg::aaa_fun(...)
...
}
```
Specifically, we recommend that you default to *not* importing anything from aaapkg into your namespace.
This makes it very easy to identify which functions live outside of your package, which is especially useful when you read your code in the future.
This also eliminates any concerns about name conflicts between aaapkg and your package.
Of course there are reasons to make exceptions to this rule and to import something from another package into yours:
- An operator: You can't call an operator from another package via `::`, so you must import it.
Examples: the null-coalescing operator `%||%` from rlang or the original pipe `%>%` from magrittr.
- A function that you use *a lot*: If importing a function makes your code much more readable, that's a good enough reason to import it.
This literally reduces the number of characters required to call the external function.
This can be especially handy when generating user-facing messages, because it makes it more likely that lines in the source correspond to lines in the output.
- A function that you call in a tight loop: There is a minor performance penalty associated with `::`.
It's on the order of 100ns, so it will only matter if you call the function millions of times.
A handy function for your interactive workflow is `usethis::use_import_from()`:
```{r}
#| eval: false
usethis::use_import_from("glue", "glue_collapse")
```
The call above writes this roxygen tag into the source code of your package:
```{r}
#| eval: false
#' @importFrom glue glue_collapse
```
Where should this roxygen tag go?
There are two reasonable locations:
- As close as possible to the usage of the external function.
With this mindset, you would place `@importFrom` in the roxygen comment above the function in your package where you use the external function.
If this is your style, you'll have to do it by hand.
We have found that this feels natural at first, but starts to break down as you use more external functions in more places.
- In a central location.
This approach keeps all `@importFrom` tags together, in a dedicated section of the package-level documentation file (which can be created with `usethis::use_package_doc()`, @sec-man-package-doc).
This is what `use_import_from()` implements.
So, in `R/pkg-package.R`, you'll end up with something like this:
```{r}
#| eval: false
# The following block is used by usethis to automatically manage
# roxygen namespace tags. Modify with care!
## usethis namespace: start
#' @importFrom glue glue_collapse
## usethis namespace: end
NULL
```
Recall that `devtools::document()` processes your roxygen comments (@sec-dependencies-NAMESPACE-workflow), which writes help topics to `man/*.Rd` and, relevant to our current goal, generates the `NAMESPACE` file.
If you use `use_import_from()`, it does this for you and also calls `load_all()`, making the newly imported function available in your current session.
The roxygen tag above causes this directive to appear in the `NAMESPACE` file:
```
importFrom(glue, glue_collapse)
```
Now you can use the imported function directly in your code:
```{r}
somefunction <- function(...) {
...
x <- glue_collapse(...)
...
}
```
Sometimes you make such heavy use of so many functions from another package that you want to import its entire namespace.
This should be relatively rare.
In the tidyverse, the package we most commonly treat this way is rlang, which functions almost like a base package for us.
Here is the roxygen tag that imports all of rlang.
This should appear somewhere in `R/*.R`, such as the dedicated space described above for collecting all of your namespace import tags.
```{r}
#| eval: false
#' @import rlang
```
After calling `devtools::document()`, this roxygen tag causes this directive to appear in the `NAMESPACE` file:
```
import(rlang)
```
This is the least recommended solution because it can make your code harder to read (you can't tell where a function is coming from), and if you `@import` many packages, it increases the chance of function name conflicts.
Save this for very special situations.
#### How to *not* use a package in Imports
Sometimes you have a package listed in `Imports`, but you don't actually use it inside your package or, at least, R doesn't think you use it.
That leads to a `NOTE` from `R CMD check`:
```
* checking dependencies in R code ... NOTE
Namespace in Imports field not imported from: ‘aaapkg’
All declared Imports should be used.
```
This can happen if you need to list an indirect dependency in `Imports`, perhaps to state a minimum version for it.
The tidyverse meta-package has this problem on a large scale, since it exists mostly to install a bundle of packages at specific versions.
Another scenario is when your package uses a dependency in such a way that requires another package that is only suggested by the direct dependency[^dependencies-in-practice-1].
There are various situations where it's not obvious that your package truly needs every package listed in `Imports`, but in fact it does.
[^dependencies-in-practice-1]: For example, if your package needs to call `ggplot2::geom_hex()`, you might choose to list hexbin in `Imports`, since ggplot2 only lists it in `Suggests`.
How can you get rid of this `NOTE`?
Our recommendation is to put a namespace-qualified reference (not a call) to an object in aaapkg in some file below `R/`, such as a `.R` file associated with package-wide setup:
```{r}
#| eval: false
ignore_unused_imports <- function() {
aaapkg::aaa_fun
}
```
You don't need to call `ignore_unused_imports()` anywhere.
You shouldn't export it.
You don't have to actually exercise `aaapkg::aaa_fun()`.
What's important is to access something in aaapkg's namespace with `::`.
An alternative approach you might be tempted to use is to import `aaapkg::aaa_fun()` into your package's namespace, probably with the roxygen tag `@importFrom aaapkg aaa_fun`.
This does suppress the `NOTE`, but it also does more.
This causes aaapkg to be loaded whenever your package is loaded.
In contrast, if you use the approach we recommend, the aaapkg will only be loaded if your user does something that actually requires it.
This rarely matters in practice, but it's always nice to minimize or delay the loading of additional packages.
### In test code {#sec-dependencies-in-imports-in-tests}
Refer to external functions in your tests just as you refer to them in the code below `R/`.
Usually this means you should use `aaapkg::aaa_fun()`.
But if you have imported a particular function, either specifically or as part of an entire namespace, you can just call it directly in your test code.
It's generally a bad idea to use `library(aaapkg)` to attach one of your dependencies somewhere in your tests, because it makes the search path in your tests different from how your package actually works.
This is covered in more detail in @sec-testing-design-tension.
### In examples and vignettes
If you use a package that appears in `Imports` in one of your examples or vignettes, you'll need to either attach the package with `library(aaapkg)` or use a `aaapkg::aaa_fun()`-style call.
You can assume that aaapkg is available, because that's what `Imports` guarantees.
Read more in @sec-man-examples-dependencies-conditional-execution and @sec-vignettes-eval-option.
## Package is listed in `Suggests` {#sec-dependencies-in-suggests}
Consider a dependency that is listed in `DESCRIPTION` in `Suggests`:
``` yaml
Suggests:
aaapkg
```
You can NOT assume that every user has installed aaapkg (but you can assume that a developer has).
Whether a user has aaapkg will depend on how they installed your package.
Most of the functions that are used to install packages support a `dependencies` argument that controls whether to install just the hard dependencies or to take a more expansive approach, which includes suggested packages:
```{r}
#| eval: false
install.packages(dependencies =)
remotes::install_github(dependencies =)
pak::pkg_install(dependencies =)
```
Broadly speaking, the default is to not install packages in `Suggests`.
### In code below `R/` {#sec-dependencies-in-suggests-r-code}
Inside a function in your own package, check for the availability of a suggested package with `requireNamespace("aaapkg", quietly = TRUE)`.
There are two basic scenarios: the dependency is absolutely required or your package offers some sort of fallback behaviour.
```{r}
# the suggested package is required
my_fun <- function(a, b) {
if (!requireNamespace("aaapkg", quietly = TRUE)) {
stop(
"Package \"aaapkg\" must be installed to use this function.",
call. = FALSE
)
}
# code that includes calls such as aaapkg::aaa_fun()
}
# the suggested package is optional; a fallback method is available
my_fun <- function(a, b) {
if (requireNamespace("aaapkg", quietly = TRUE)) {
aaapkg::aaa_fun()
} else {
g()
}
}
```
The rlang package has some useful functions for checking package availability: `rlang::check_installed()` and `rlang::is_installed()`.
Here's how the checks around a suggested package could look if you use rlang:
```{r}
# the suggested package is required
my_fun <- function(a, b) {
rlang::check_installed("aaapkg", reason = "to use `aaa_fun()`")
# code that includes calls such as aaapkg::aaa_fun()
}
# the suggested package is optional; a fallback method is available
my_fun <- function(a, b) {
if (rlang::is_installed("aaapkg")) {
aaapkg::aaa_fun()
} else {
g()
}
}
```
These rlang functions have handy features for programming, such as vectorization over `pkg`, classed errors with a data payload, and, for `check_installed()`, an offer to install the needed package in an interactive session.
### In test code {#sec-dependencies-in-suggests-in-tests}
The tidyverse team generally writes tests as if all suggested packages are available.
That is, we use them unconditionally in the tests.
The motivation for this posture is self-consistency and pragmatism.
The key package needed to run tests is testthat and it appears in `Suggests`, not in `Imports` or `Depends`.
Therefore, if the tests are actually executing, that implies that an expansive notion of package dependencies has been applied.
Also, empirically, in every important scenario of running `R CMD check`, the suggested packages are installed.
This is generally true for CRAN and we ensure that it's true in our own automated checks.
However, it's important to note that other package maintainers take a different stance and choose to protect all usage of suggested packages in their tests and vignettes.
Sometimes even we make an exception and guard the use of a suggested package in a test.
Here's a test from ggplot2, which uses `testthat::skip_if_not_installed()` to skip execution if the suggested sf package is not available.
```{r}
#| eval: false
test_that("basic plot builds without error", {
skip_if_not_installed("sf")
nc_tiny_coords <- matrix(
c(-81.473, -81.741, -81.67, -81.345, -81.266, -81.24, -81.473,
36.234, 36.392, 36.59, 36.573, 36.437, 36.365, 36.234),
ncol = 2
)
nc <- sf::st_as_sf(
data_frame(
NAME = "ashe",
geometry = sf::st_sfc(sf::st_polygon(list(nc_tiny_coords)), crs = 4326)
)
)
expect_doppelganger("sf-polygons", ggplot(nc) + geom_sf() + coord_sf())
})
```
What might justify the use of `skip_if_not_installed()`?
In this case, the sf package can be nontrivial to install and it is conceivable that a contributor would want to run the remaining tests, even if sf is not available.
Finally, note that `testthat::skip_if_not_installed(pkg, minimum_version = "x.y.z")` can be used to conditionally skip a test based on the version of the other package.
### In examples and vignettes {#sec-dependencies-in-suggests-in-examples-and-vignettes}
Another common place to use a suggested package is in an example and here we often guard with `require()` or `requireNamespace()`.
This example is from `ggplot2::coord_map()`.
ggplot2 lists the maps package in `Suggests`.
```{r}
#| eval: false
#' @examples
#' if (require("maps")) {
#' nz <- map_data("nz")
#' # Prepare a map of NZ
#' nzmap <- ggplot(nz, aes(x = long, y = lat, group = group)) +
#' geom_polygon(fill = "white", colour = "black")
#'
#' # Plot it in cartesian coordinates
#' nzmap
#' }
```
An example is basically the only place where we would use `require()` inside a package.
Read more in @sec-dependencies-attach-vs-load.
Our stance regarding the use of suggested packages in vignettes is similar to that for tests.
The key packages needed to build vignettes (rmarkdown and knitr) are listed in `Suggests`.
Therefore, if the vignettes are being built, it's reasonable to assume that all of the suggested packages are available.
We typically use suggested packages unconditionally inside vignettes.
But if you choose to use suggested packages conditionally in your vignettes, the knitr chunk option `eval` is very useful for achieving this.
See @sec-vignettes-eval-option for more.
## Package is listed in `Depends` {#sec-dependencies-in-depends}
Consider a dependency that is listed in `DESCRIPTION` in `Depends`:
``` yaml
Depends:
aaapkg
```
This situation has a lot in common with a package listed in `Imports`.
The code inside your package can assume that aaapkg is installed on the system.
The only difference is that aaapkg will be attached whenever your package is.
### In code below `R/` and in test code
Your options are exactly the same as using functions from a package listed in `Imports`:
- Use the `aaapkg::aaa_fun()` syntax.
- Import an individual function with the `@importFrom aaapkg aaa_fun` roxygen tag and call `aaa_fun()` directly.
- Import the entire aaapkg namespace with the `@import aaapkg` roxygen tag and call any function directly.
The main difference between this situation and a dependency listed in `Imports` is that it's much more common to import the entire namespace of a package listed in `Depends`.
This often makes sense, due to the special dependency relationship that motivated listing it in `Depends` in the first place.
### In examples and vignettes
This is the most obvious difference with a dependency in `Depends` versus `Imports`.
Since your package is attached when your examples are executed, so is the package listed in `Depends`.
You don't have to attach it explicitly with `library(aaapkg)`.
The ggforce package `Depends` on ggplot2 and the examples for `ggforce::geom_mark_rect()` use functions like `ggplot2::ggplot()` and `ggplot2::geom_point()` without any explicit call to `library(ggplot2)`:
```{r}
#| eval: false
ggplot(iris, aes(Petal.Length, Petal.Width)) +
geom_mark_rect(aes(fill = Species, filter = Species != 'versicolor')) +
geom_point()
# example code continues ...
```
The first line of code executed in one of your vignettes is probably `library(pkg)`, which attaches your package and, as a side effect, attaches any dependency listed in `Depends`.
You do not need to explicitly attach the dependency before using it.
The censored package `Depends` on the survival package and the code in `vignette("examples", package = "censored")` starts out like so:
```{r}
#| eval: false
library(tidymodels)
library(censored)
#> Loading required package: survival
# vignette code continues ...
```
## Package is a nonstandard dependency {#sec-dependencies-nonstandard}
In packages developed with devtools, you may see `DESCRIPTION` files that use a couple other nonstandard fields for package dependencies specific to development tasks.
### Depending on the development version of a package
The `Remotes` field can be used when you need to install a dependency from a nonstandard place, i.e. from somewhere besides CRAN or Bioconductor.
One common example of this is when you're developing against a development version of one of your dependencies.
During this time, you'll want to install the dependency from its development repository, which is often GitHub.
The way to specify various remote sources is described in a [devtools vignette](https://devtools.r-lib.org/articles/dependencies.html) and in a [pak help topic](https://pak.r-lib.org/reference/pak_package_sources.html).
The dependency and any minimum version requirement still need to be declared in the normal way in, e.g., `Imports`.
`usethis::use_dev_package()` helps to make the necessary changes in `DESCRIPTION`.
If your package temporarily relies on a development version of aaapkg, the affected `DESCRIPTION` fields might evolve like this:
<!-- This is unlovely, but I just wanted to get the content down "on paper". It's easier to convey with a concrete example. -->
```
Stable --> Dev --> Stable again
---------------------- --------------------------- ----------------------
Package: pkg Package: pkg Package: pkg
Version: 1.0.0 Version: 1.0.0.9000 Version: 1.1.0
Imports: Imports: Imports:
aaapkg (>= 2.1.3) aaapkg (>= 2.1.3.9000) aaapkg (>= 2.2.0)
Remotes:
jane/aaapkg
```
::: callout-warning
## CRAN
It's important to note that you should not submit your package to CRAN in the intermediate state, meaning with a `Remotes` field and with a dependency required at a version that's not available from CRAN or Bioconductor.
For CRAN packages, this can only be a temporary development state, eventually resolved when the dependency updates on CRAN and you can bump your minimum version accordingly.
:::
### `Config/Needs/*` field {#sec-dependencies-nonstandard-config-needs}
You may also see devtools-developed packages with packages listed in `DESCRIPTION` fields in the form of `Config/Needs/*`, which we described in @sec-description-custom-fields.
```{=html}
<!--
https://github.com/wch/r-source/blob/de49776d9fe54cb4580fbbd04906b40fe2f6117e/src/library/tools/R/QC.R#L7133
https://github.com/wch/r-source/blob/efacf56dcf2f880b9db8eafa28d49a08d56e861e/src/library/tools/R/utils.R#L1316-L1389
-->
```
The use of `Config/Needs/*` is not directly related to devtools.
It's more accurate to say that it's associated with continuous integration workflows made available to the community at <https://github.com/r-lib/actions/> and exposed via functions such as `usethis::use_github_actions()`.
A `Config/Needs/*` field tells the [`setup-r-dependencies`](https://github.com/r-lib/actions/tree/HEAD/setup-r-dependencies#readme) GitHub Action about extra packages that need to be installed.
`Config/Needs/website` is the most common and it provides a place to specify packages that aren't a formal dependency, but that must be present in order to build the package's website (@sec-website).
The readxl package is a good example.
It has a [non-vignette article on workflows](https://readxl.tidyverse.org/articles/readxl-workflows.html) that shows readxl working in concert with other tidyverse packages, such as readr and purrr.
But it doesn't make sense for readxl to have a formal dependency on readr or purrr or (even worse) the tidyverse!
On the left is what readxl has in the `Config/Needs/website` field of `DESCRIPTION` to indicate that the tidyverse is needed in order to build the website, which is also formatted with styling that lives in the `tidyverse/template` GitHub repo.
On the right is the corresponding excerpt from the configuration of the workflow that builds and deploys the website.
```
in DESCRIPTION in .github/workflows/pkgdown.yaml
-------------------------- ---------------------------------
Config/Needs/website: - uses: r-lib/actions/setup-r-dependencies@v2
tidyverse, with:
tidyverse/tidytemplate extra-packages: pkgdown
needs: website
```
Package websites and continuous integration are discussed more in @sec-website and @sec-sw-dev-practices-ci, respectively.
The `Config/Needs/*` convention is handy because it allows a developer to use `DESCRIPTION` as their definitive record of package dependencies, while maintaining a clean distinction between true runtime dependencies versus those that are only needed for specialized development tasks.
## Exports
For a function to be usable outside of your package, you must **export** it.
When you create a new package with `usethis::create_package()`, nothing is exported at first, even once you add some functions.
You can still experiment interactively with `load_all()`, since that loads all functions, not just those that are exported.
But if you install and attach the package with `library(pkg)` in a fresh R session, you'll notice that no functions are available.
### What to export
Export functions that you want other people to use.
Exported functions must be documented, and you must be cautious when changing their interface --- other people are using them!
Generally, it's better to export too little than too much.
It's easy to start exporting something that you previously did not; it's hard to stop exporting a function because it might break existing code.
Always err on the side of caution, and simplicity.
It's easier to give people more functionality than it is to take away stuff they're used to.
We believe that packages that have a wide audience should strive to do one thing and do it well.
All functions in a package should be related to a single problem (or a set of closely related problems).
Any functions not related to that purpose should not be exported.
For example, most of our packages have a `utils.R` file (@sec-code-organising) that contains small utility functions that are useful internally, but aren't part of the core purpose of those packages.
We don't export such functions.
There are at least two reasons for this:
- Freedom to be less robust and less general.
A utility for internal use doesn't have to be implemented in the same way as a function used by others.
You just need to cover your own use case.
- Regrettable reverse dependencies.
You don't want people depending on your package for functionality and functions that are unrelated to its core purpose.
That said, if you're creating a package for yourself, it's far less important to be this disciplined.
Because you know what's in your package, it's fine to have a local "miscellany" package that contains a hodgepodge of functions that you find useful.
But it is probably not a good idea to release such a package for wider use.
Sometimes your package has a function that could be of interest to other developers extending your package, but not to typical users.
In this case, you want to export the function, but also to give it a very low profile in terms of public documentation.
This can be achieved by combining the roxygen tags `@export` and `@keywords internal`.
The `internal` keyword keeps the function from appearing in the package index, but the associated help topic still exists and the function still appears among those exported in the `NAMESPACE` file.
### Re-exporting
Sometimes you want to make something available to users of your package that is actually provided by one of your dependencies.
When devtools was split into several smaller packages (@sec-setup-usage), many of the user-facing functions moved elsewhere.
For usethis, the chosen solution was to list it in `Depends` (@sec-dependencies-imports-vs-depends), but that is not a good general solution.
Instead, devtools now re-exports certain functions that actually live in a different package.
Here is a blueprint for re-exporting an object from another package, using the `session_info()` function as our example:
1. List the package that hosts the re-exported object in `Imports` in `DESCRIPTION`.[^dependencies-in-practice-2]
In this case, the `session_info()` function is exported by the sessioninfo package.
``` yaml
Imports:
sessioninfo
```
2. In one of your `R/*.R` files, have a reference to the target function, preceded by roxygen tags for both importing and exporting.
```{r}
#| eval: false
#' @export
#' @importFrom sessioninfo session_info
sessioninfo::session_info
```
[^dependencies-in-practice-2]: Remember `usethis::use_package()` is helpful for adding dependencies to `DESCRIPTION`.
That's it!
Next time you re-generate NAMESPACE, these two lines will be there (typically interspersed with other exports and imports):
```
...
export(session_info)
...
importFrom(sessioninfo,session_info)
...
```
And this explains how `library(devtools)` makes `session_info()` available in the current session.
This will also lead to the creation of the `man/reexports.Rd` file, which finesses the requirement that your package must document all of its exported functions.
This help topic lists all re-exported objects and links to their primary documentation.
## Imports and exports related to S3
R has multiple object-oriented programming (OOP) systems:
- S3 is currently the most important for us and is what's addressed in this book.
The [S3 chapter of Advanced R](https://adv-r.hadley.nz/s3.html) is a good place to learn more about S3 conceptually and the [vctrs package](https://vctrs.r-lib.org) is worth studying for practical knowledge.
- S4 is very important within certain R communities, most notably within the Bioconductor project.
We only use S4 when it's necessary for compatibility with other packages.
If you want to learn more, the [S4 chapter of Advanced R](https://adv-r.hadley.nz/s4.html) is a good starting point and has recommendations for additional resources.
- R6 is used in many tidyverse packages (broadly defined), but is out of scope for this book.
Good places to learn more include the [R6 package website](https://r6.r-lib.org), the [R6 chapter of Advanced R](https://adv-r.hadley.nz/r6.html), and the [roxygen2 documentation related to R6](https://roxygen2.r-lib.org/articles/rd-other.html#r6).
In terms of namespace issues around S3 classes, the main things to consider are generic functions and their class-specific implementations known as methods.
If your package "owns" an S3 class, it makes sense to export a user-friendly constructor function.
This is often just a regular function and there is no special S3 angle.
If your package "owns" an S3 generic and you want others to be able to use it, you should export the generic.
For example, the dplyr package exports the generic function `dplyr::count()` and also implements and exports a specific method, `count.data.frame()`:
```{r}
#| eval: false
#' ... all the usual documentation for count() ...
#' @export
count <- function(x, ..., wt = NULL, sort = FALSE, name = NULL) {
UseMethod("count")
}
#' @export
count.data.frame <- function(
x,
...,
wt = NULL,
sort = FALSE,
name = NULL,
.drop = group_by_drop_default(x)) { ... }
```
The corresponding lines in dplyr's `NAMESPACE` file look like this:
```
...
S3method(count,data.frame)
...
export(count)
...
```
Now imagine that your package implements a method for `count()` for a class you "own" (not `data.frame`).
A good example is the dbplyr package, which implements `count()` for the `tbl_lazy` class.
An add-on package that implements an S3 generic for a new class should list the generic-providing package in `Imports`, import the generic into its namespace, and export its S3 method.
Here's part of dbplyr's `DESCRIPTION` file:
```
Imports:
...,
dplyr,
...
```
In `dbplyr/R/verb-count.R`, we have:
```{r}
#| eval: false
#' @importFrom dplyr count
#' @export
count.tbl_lazy <- function(x, ..., wt = NULL, sort = FALSE, name = NULL) { ... }
```
In `NAMESPACE`, we have:
```
S3method(count,tbl_lazy)
...
importFrom(dplyr,count)
```
Dbplyr also provides methods for various generics provided by the base package, such as `dim()` and `names()`.
In this case, there is no need to import those generics, but it's still necessary to export the methods.
In `dbplyr/R/tbl_lazy.R`, we have:
```{r}
#| eval: false
#' @export
dim.tbl_lazy <- function(x) {
c(NA, length(op_vars(x$lazy_query)))
}
#' @export
names.tbl_lazy <- function(x) {
colnames(x)
}
```
In `NAMESPACE`, this produces:
```
S3method(dim,tbl_lazy)
...
S3method(names,tbl_lazy)
```
The last and trickiest case is when your package offers a method for a generic "owned" by a package you've listed in `Suggests`.
The basic idea is that you want to register the availability of your S3 method conditionally, when your package is being loaded.
If the suggested package is present, your S3 method should be registered, but otherwise it should not.
We'll illustrate this with an example.
Within the tidyverse, the glue package is managed as a low-level package that should have minimal dependencies (@sec-dependencies-tidyverse).
Glue functions generally return a character vector that also has the `"glue"` S3 class.
```{r}
library(glue)
name <- "Betty"
(ret <- glue('My name is {name}.'))
class(ret)
```
The motivation for this is that it allows glue to offer special methods for `print()`, the `+` operator, and subsetting via `[` and `[[`.
One downside, though, is that this class attribute complicates string comparisons:
```{r}
identical(ret, "My name is Betty.")
all.equal(ret, "My name is Betty.")
```
Therefore, for testing, it is helpful if glue offers a method for `testthat::compare()`, which explains why this expectation succeeds:
```{r}
testthat::expect_equal(ret, "My name is Betty.")
```
But glue can't list testthat in `Imports`!
It must go in `Suggests`.
The solution is to register the method conditionally when glue is loaded.
Here is a redacted version of glue's `.onLoad()` function, where you'll see that it conditionally registers some other methods as well:
```{r}
#| eval: false
.onLoad <- function(...) {
s3_register("testthat::compare", "glue")
s3_register("waldo::compare_proxy", "glue")
s3_register("vctrs::vec_ptype2", "glue.glue")
...
invisible()
}
```
The `s3_register()` function comes from the vctrs package.
If you don't have an organic need to depend on vctrs, it is common (and encouraged) to simply inline [the `s3_register()` source](https://github.com/r-lib/vctrs/blob/main/R/register-s3.R) into your own package.
You can't always copy code from other people's packages and paste it into yours, but you can in this case.
This usage is specifically allowed by the license of the source code of `s3_register()`.
This provides a great segue into @sec-license, which is all about licensing.