generated from r4ds/bookclub-template
-
Notifications
You must be signed in to change notification settings - Fork 18
/
11-dependencies-in-practice.Rmd
300 lines (179 loc) · 10.8 KB
/
11-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
# Dependencies: In Practice
**Learning objectives:**
- Practical details of working with dependencies
- How to use different types of dependencies in your
- functions
- tests
- examples
- vignettes and articles
## Confusion about `Imports`
<!-- The `DESCRIPTION` file lists the packages that your package needs to work, but does **not** import the package. -->
Reminder `Imports` makes sure the packages is installed, but does not make functions available to you or your user.
Packages in listed in `Imports` do not need to appear in `NAMESPACE`, but every package in `NAMESPACE` must appear in `Imports` or `Depends`.
## `NAMESPACE` Workflow
The `NAMESPACE` file
- keeps track of how and when to import functions from another package and export function from your package
- generated from comments in `R/*.R` files
- starts with comment explaining roxygen2 generation
- needs to be regenerated periodically
### General Workflow
1. Add namespace-related tags to roxygen comments in `R/*.R` files.
2. Run `devtools::document()` which updates help topics and regenerates `NAMESPACE`
## Package is listed in `Imports`
Recall from [Chapter 10](https://r-pkgs.org/description.html#sec-description-imports-suggests):
> `Imports` are needed by your users at runtime and will be installed (or potentially updated) when users install your package via `install.packages()`
Your package can assume a package listed in `Imports` is installed whenever itself is installed.
### In code below R/
Recommendation: use the syntax `package::function()` to:
- avoid importing `package` to your namespace
- easily identify what functions live outside your package
- eliminate name conflicts
Exceptions:
- An operator can't be called with `::`. ex: magrittr pipe
- A function you use a **lot**: makes code more readable
- A function used in a tight loop: `::` has minor performance penalty on order of 100ns
If making an exception, generate roxygen tag with `usethis::use_import_from()` and place either:
- as close as possible to usage of external function
- in a central location
`usethis::use_import_from()` updates documentation and calls `load_all()`
Importing entire package namespace should be rare and is least recommended.
- example is rlang in tidyverse
- generally only done when you rely heavily on functions from another package
- makes code harder to read
- increases risk of function name conflicts
### How to *not* use a package in Imports
Sometimes you have a package listed in Imports but R thinks you don't use it
e.g., you only use aaapkg to call `aaapkg::aaa_fun()` in the default for a function argument in your package
This leads to `R CMD check` giving the following note:
> `checking dependencies in R code ... NOTE Namespace in Imports field not imported from: 'aaapkg' All declared Imports should be used.`
To get around this, create a function in a file below `R/` directory:
- This function doesn't need to be called.
- It shouldn't be exported
For example:
```{r eval=FALSE}
ignore_unused_imports <- function() {
aaapkg::aaa_fun()
}
```
This is preferable to importing the `aaapkg::aaa_fun()` into your namespace as this will cause aaapkg to be loaded whenever your package is loaded. It is good practice to minimise the loading of packages until it is actually needed.
### In test code
Generally, external functions in a test are referred to in the same way as code below `R/`
- Call `aaapkg::aaa_fun()`
Using `library(aaapkg)` in your tests is usually a bad idea:
- It makes the search paths for your tests different to how your package works (more in [Chapter 15](https://r-pkgs.org/testing-design.html#sec-testing-design-tension))
### In examples and vignettes
If you use a package listed in `Imports` in your example/vignette either:
- attach the package with library(aaapkg), or,
- use a `aaapkg::aaa_fun()` call
## Package is listed in `Suggests`
Recall from [Chapter 10](https://r-pkgs.org/description.html#sec-description-imports-suggests):
> Packages listed in `Suggests` are either needed for development tasks or might unlock optional functionality for your users.
You can NOT assume that every user has installed aaapkg (but you can assume that a developer has).
They could be installed through:
- `install.packages("pkg", dependencies = TRUE)`
### In code below `R/`
You can check for the availability of a suggested package using `requireNamespace("aaapkg", quietly = TRUE)`
Two scenarios:
1) The dependency is required and the function wont work without it
```{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()
}
```
2) You have a fallback in the function/package if the user does not have the required package
```{r}
# 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()
}
}
```
`rlang` has useful functions for checking package availability, offering to install the package for the user and programming (such as vectorization over `pkg`).
### In test code
Including packages from `Suggests` when writing tests depends on the maintainer/developer.
The tidyverse team *generally* writes tests as if all suggested packages are available.
- `testthat` package is in the `Suggests` field so we can assume all suggested packages are available
- They will skip sometimes e.g. if package is cumbersome to install and not already installed
### In examples and vignettes
An example is a common place to use a suggested package and is one of the few places we would use `require()` or `requireNamespace()` in a package.
A vignette needs packages contained in the `Suggests` field.
- Similar to tests, we can assume all suggested packages are available and use suggested packages unconditionally.
- *If* you want to use suggested packages conditionally, the knitr chunk option `eval` can be used (more in a later chapter).
## Package is listed in `Depends`
Recall from [Chapter 10](https://r-pkgs.org/description.html#depends-and-linkingto):
> Prior to the roll-out of namespaces in R 2.14.0 in 2011, `Depends` was the only way to "depend" on another package... The most legitimate current use of Depends is to state a minimum version for R itself, e.g. Depends: R (\>= 4.0.0).
`Depends` shouldn't be used for packages.. but if you do use it will be similar to `Imports` e.g. your package can assume that `aaapkg` will be installed but unlike `Imports`, `aaapkg` will be attached as well.
### In code below `R/` and in test code
Same as using functions from a package listed in `Imports`
- use aaapkg::aaa_fun()
- Import an individual function using an `@importFrom` roxygen tag
- Import the entire namespace using `@import` roxygen tag
### In examples and vignettes
Because your package is attached when the examples are executed you wont need to use `library()`.
Similarly, if your vignette starts with `library(pkg)`, your package will be loaded and attached along with any packages listed in the `Depends` field.
## Package is a nonstandard dependency
A package developed using [Devtools](https://devtools.r-lib.org/articles/dependencies.html) may have non-standard fields in the `DESCRIPTION` file. The `Remotes` field can be used to install packages from non standard locations e.g. GitHUb.
- Still need to declare the dependency in the correct `DESCRIPTION` field e.g. `Imports`
### `Config/Needs/*` field
- Not directly related to devtools
- Used for continuous integration workflows e.g. GitHub Actions
- Allows to differentiate between true runtime dependencies and those only needed for specialised development tasks
## Export
You need to export a function to make it usable outside of your package. When you first create a package, nothing will be exported, even when you have added some functions.
### What to export
- Export functions that you want others to use.
- Too little is better than too much
- It is hard to remove/change a function once others are using it
### What not to export
- All functions not related to the problem your package addresses should not be exported
- Functions that are internally useful can be placed into a `utils.R` file
## Re-exporting
Making available to users of your package a function/object that is actually provided by one of your dependencies.
To do this you would:
1) List the package that host the re-exported object in the `imports` field in `DESCRIPTION`
2) In one of the `R/*.R` have a reference to the target function and include the import and export roxygen tags
3) Regenerate the `NAMESPACE`
This will also lead to the generation of `man/reexports.Rd` which is where you keep a record of all of your re-exported objects with links to their documentation.
## Imports and exports related to S3
R has multiple object-oriented programming (OOP) systems e.g. S3, S4, R6
If your package owns an S3 class, it makes sense to export a user-friendly constructor function
## Meeting Videos
### Cohort 1
`r knitr::include_url("https://www.youtube.com/embed/C_H1oQZD7m8")`
### Cohort 2
`r knitr::include_url("https://www.youtube.com/embed/8PU_KT5IpWg")`
### Cohort 3
`r knitr::include_url("https://www.youtube.com/embed/2-baO_E9p1s")`
<details>
<summary>Meeting chat log</summary>
00:05:44 Ryan Metcalf: https://www.rstudio.com/conference/
00:08:38 Brendan Lam: https://stackoverflow.com/questions/70097126/how-to-edit-namespace-with-roxygen2-when-creating-r-packages
00:08:51 Brendan Lam: https://stackoverflow.com/questions/11990589/r-namespace-access-and-match-fun
00:44:59 Rex Parsons: https://github.com/gentrywhite/DSSP/blob/b3d7f0d10374968a43a634a1ae3f65e9231fceb0/R/plot.R#L43
00:48:01 Rex Parsons: https://stackoverflow.com/questions/5595512/what-is-the-difference-between-require-and-library
00:58:13 Rex Parsons: gotta run guys - thanks Brendan and team!
00:58:29 collinberke: https://stackoverflow.com/questions/64737686/why-library-or-require-should-not-be-used-in-a-r-package
</details>
### Cohort 4
`r knitr::include_url("https://www.youtube.com/embed/vlHK-D24wd0")`
<details>
<summary> Meeting chat log </summary>
```
00:31:39 Olivier Leroy: https://stackoverflow.com/questions/13401977/what-does-the-autoloads-environment-do#13419964
00:31:57 Olivier Leroy: apparently an envt that store function that could be called letter
00:32:28 Olivier Leroy: latter*
00:32:55 Olivier Leroy: Pointer maybe
00:54:34 Oluwafemi Oyedele: I have to go now, thank you very much for the presentation @Toryn, I will catch up with the rest of the discussion, see you all next week!!!
```
</details>
`r knitr::include_url("https://www.youtube.com/embed/vlHKrK7NRXBN18A")`