Skip to content

Commit 3aae6ef

Browse files
hadleyDavisVaughanmaelle
authored
More advice about documenting S3/S7 generics/methods (#1851)
Fixes #1513 --------- Co-authored-by: Davis Vaughan <davis@posit.co> Co-authored-by: Maëlle Salmon <maelle.salmon@yahoo.se>
1 parent 5e66827 commit 3aae6ef

2 files changed

Lines changed: 220 additions & 44 deletions

File tree

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# roxygen2 (development version)
2+
* `vignette("rd-other")` now includes improved advice for documenting S3 generics, classes, and methods, including how to use the new [doclisting](https://doclisting.r-lib.org/) package to automatically list methods for a generic (#1513).
23
* Added initial support for S7 classes, generics, and methods (#1484).
34
* S7 generics are documented like regular functions.
45
* S7 classes are documented like regular functions, but you can use `@prop` to document additional properties that are not constructor parameters. If multiple classes share one page, use `@prop ClassName@prop_name description` to group properties by class.

vignettes/rd-other.Rmd

Lines changed: 219 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,135 @@ Some notes:
8787

8888
## S3
8989

90-
- S3 **generics** are regular functions, so document them as such.
91-
If necessary, include a section that provides additional details for developers implementing methods.
90+
Documenting [S3 code](https://adv-r.hadley.nz/s3.html) well requires thinking about three things:
9291

93-
- S3 **classes** have no formal definition, so document the [constructor](https://adv-r.hadley.nz/s3.html#s3-constructor).
92+
- **Generics**: mention that the function is a generic and list the
93+
available methods.
94+
- **Methods**: link back to the generic; only document individually
95+
when the method has unique behavior or arguments.
96+
- **Classes**: document the constructor.
9497

95-
- It is your choice whether or not to document S3 **methods**.
96-
Generally, it's not necessary to document straightforward methods for common generics like `print()`.
97-
(You should, however, always `@export` S3 methods, even internal ones).
98+
### Generics
9899

99-
If your method is more complicated, you should document it by setting `@rdname` or `@describeIn`.
100-
For complicated methods, you might document in their own file (i.e. `@name generic.class`; for simpler methods you might document with the generic (i.e. `@rdname generic)`.
101-
Learn more about these tags in `vignette("reuse")`.
100+
S3 **generics** are regular functions, so document them as such.
101+
Export a generic if you want users to call it or other developers to
102+
write methods for it. If the generic is internal, you don't need to
103+
document it.
102104

103-
- Generally, roxygen2 will automatically figure out the generic that the method belongs to, and you should only need to use `@method` if there is ambiguity.
104-
For example, is `all.equal.data.frame()` the `equal.data.frame` method for `all()`, or the `data.frame` method for `all.equal()`?.
105-
If this happens to you, disambiguate with (e.g.) `@method all.equal data.frame`.
105+
The documentation should mention that the function is a generic, because this
106+
tells the reader that the behavior may vary depending on the input and that
107+
they can write their own methods. For simple generics, you can do this
108+
in the description:
109+
110+
```{r}
111+
#| eval: false
112+
113+
#' Frobnpolicate an object
114+
#'
115+
#' @description
116+
#' `frobnpolicate()` is an S3 generic that ..., with methods available for
117+
#' the following classes:
118+
#'
119+
#' `r doclisting::methods_inline("frobnpolicate")`
120+
```
121+
122+
For more complicated generics, you can use a `# Methods` section to
123+
provide more detail:
124+
125+
```{r}
126+
#| eval: false
127+
128+
#' Frobnpolicate an object
129+
#'
130+
#' @description
131+
#' `frobnpolicate()` does ...
132+
#'
133+
#' # Methods
134+
#' `frobnpolicate()` is an S3 generic with methods available for the following
135+
#' classes:
136+
#'
137+
#' `r doclisting::methods_list("frobnpolicate")`
138+
```
139+
140+
You might also want to include a section that provides additional details for developers implementing their own methods.
141+
142+
Both examples above use the [doclisting](https://doclisting.r-lib.org/)
143+
package to automatically generate a list of methods, with links to their
144+
help topics. These examples use [inline R code](reuse.html#inline-code) (`` `r ` ``), which generates the list at documentation time (i.e. when you run
145+
`devtools::document()`). This only requires including doclisting in `Suggests`.
146+
147+
If you want the list to dynamically reflect all methods that are currently registered
148+
(including methods registered by other packages), use an [inline Rd code](
149+
reuse.html#inline-rd-code) block
150+
(`` `Rd ` ``) instead:
151+
152+
```{r}
153+
#| eval: false
154+
155+
#' `Rd doclisting::methods_list("frobnpolicate")`
156+
```
157+
158+
Using `` `Rd ` `` requires doclisting in `Imports`, and you'll need a dummy call to eliminate the `R CMD check` NOTE about unused imports:
159+
160+
```{r}
161+
#| eval: false
162+
163+
ignore_unused_imports <- function() {
164+
doclisting::methods_list
165+
}
166+
```
167+
168+
169+
### Classes
170+
171+
S3 **classes** have no formal definition, so document the [constructor](https://adv-r.hadley.nz/s3.html#s3-constructor).
172+
Export the constructor if you want users to create instances of your
173+
class or other developers to extend it (e.g. by creating subclasses).
174+
Internal constructors don't need documentation.
175+
176+
Note that you don't need to list methods for a class: in S3, methods
177+
belong to generics, not to classes. Users should look at the generic's
178+
help page to learn about available methods.
179+
180+
### Methods
181+
182+
It is your choice whether or not to document S3 **methods**.
183+
Generally, it's not necessary to document straightforward methods for
184+
common generics like `print()`. (You should, however, always `@export`
185+
S3 methods, even internal ones.)
186+
187+
It's good practice, however, to document methods that have unique behavior or arguments. For example, the clock package documents the methods for
188+
[`date_group()`](https://clock.r-lib.org/reference/date_group.html) on
189+
their own pages ([Date](https://clock.r-lib.org/reference/date-group.html),
190+
[POSIXt](https://clock.r-lib.org/reference/posixt-group.html)) because the
191+
methods accept different `precision` values and have type-specific return
192+
value semantics and examples. The generic page serves only as a signpost
193+
linking to the individual method pages.
194+
195+
When documenting a method, always include a link back to the generic
196+
using `[generic_name()]` so the reader can easily find the full
197+
documentation and other methods.
198+
199+
### Method disambiguation
200+
201+
Generally, roxygen2 will automatically figure out the generic that the
202+
method belongs to, and you should only need to use `@method` if there
203+
is ambiguity. For example, is `all.equal.data.frame()` the
204+
`equal.data.frame` method for `all()`, or the `data.frame` method for
205+
`all.equal()`? If this happens to you, disambiguate with (e.g.)
206+
`@method all.equal data.frame`.
106207

107208
## S4
108209

109210
S4 **generics** are also functions, so document them as such.
211+
Export a generic if you want users to call it or other developers to
212+
write methods for it. If the generic is internal, you don't need to
213+
document it.
110214

111215
Document **S4 classes** by adding a roxygen block before `setClass()`.
216+
Export a class if you want users to create instances or other developers
217+
to extend it (e.g. by creating subclasses). Internal classes don't need
218+
documentation.
112219
Use `@slot` to document the slots of the class.
113220
Here's a simple example:
114221

@@ -124,7 +231,7 @@ Account <- setClass("Account",
124231
```
125232

126233
S4 **methods** are a little more complicated.
127-
Unlike S3 methods, all S4 methods must be documented.
234+
Unlike S3 and S7 methods, all S4 methods must be documented.
128235
You can document them in three places:
129236

130237
- In the class.
@@ -134,16 +241,98 @@ You can document them in three places:
134241
Most appropriate if the generic uses multiple dispatches and you control it.
135242

136243
- In its own file.
137-
Most appropriate if the method is complex.
138-
or the either two options don't apply.
244+
Most appropriate if the method is complex or the either two options don't apply.
139245

140-
Use either `@rdname` or `@describeIn` to control where method documentation goes.
141-
See the next section for more details.
246+
Use either `@rdname` or `@describeIn` to control where method documentation goes. See `vignette("reuse")` for more details.
142247

143248
## S7
144249

250+
Documenting S7 code well requires thinking about three things:
251+
252+
- **Generics**: mention that the function is a generic and list the
253+
available methods.
254+
- **Methods**: link back to the generic; only document individually
255+
when the method has unique behavior or arguments.
256+
- **Classes**: document the constructor.
257+
258+
### Generics
259+
260+
S7 **generics** are functions, so document them as such.
261+
Export a generic if you want users to call it or other developers to
262+
write methods for it. If the generic is internal, you don't need to
263+
document it.
264+
265+
The documentation should mention that the function is a generic, because
266+
this tells the reader that the behavior may vary depending on the input
267+
and that they can write their own methods. For simple generics, you can
268+
do this in the description:
269+
270+
```{r}
271+
#| eval: false
272+
273+
#' Size of an object
274+
#'
275+
#' @description
276+
#' `size()` is an S7 generic that determines the size of an object,
277+
#' with methods available for the following classes:
278+
#'
279+
#' `r doclisting::methods_inline("size")`
280+
#'
281+
#' @param x An object.
282+
#' @param ... Not used.
283+
#' @returns A single number.
284+
#' @export
285+
size <- new_generic("size", "x")
286+
```
287+
288+
For more complicated generics, you can use a `# Methods` section to
289+
provide more detail:
290+
291+
```{r}
292+
#| eval: false
293+
294+
#' Size of an object
295+
#'
296+
#' @description
297+
#' `size()` determines the size of an object.
298+
#'
299+
#' # Methods
300+
#' `size()` is an S7 generic with methods available for the following
301+
#' classes:
302+
#'
303+
#' `r doclisting::methods_list("size")`
304+
#'
305+
#' @param x An object.
306+
#' @param ... Not used.
307+
#' @returns A single number.
308+
#' @export
309+
size <- new_generic("size", "x")
310+
```
311+
312+
See the [S3 Generics section](#generics) above for more about using the
313+
[doclisting](https://doclisting.r-lib.org/) package to automatically
314+
generate method lists.
315+
316+
It's good practice to document the default method alongside the generic
317+
using `@rdname`:
318+
319+
```{r}
320+
#| eval: false
321+
322+
#' @rdname size
323+
method(size, class_any) <- function(x, ...) {
324+
length(x)
325+
}
326+
```
327+
328+
### Classes
329+
145330
S7 **classes** are constructor functions, so document them much like
146-
you'd document any other function. Use `@param` to document the
331+
you'd document any other function. Export a class if you want users to
332+
create instances or other developers to extend it (e.g. by creating
333+
subclasses). Internal classes don't need documentation.
334+
335+
Use `@param` to document the
147336
constructor arguments (which correspond to class properties), and
148337
`@returns` to describe the object that is returned. If the class has
149338
additional properties that are not part of the constructor (e.g.
@@ -178,38 +367,24 @@ If multiple classes share one Rd page (via `@rdname`), you can prefix
178367
the property name with the class name to group properties by class,
179368
e.g. `@prop ClassName@prop_name description`.
180369

181-
S7 **generics** are also functions, so document them as such.
182-
It's good practice to document the default method alongside the generic using `@rdname`:
370+
### Methods
183371

184-
```{r}
185-
#| eval: false
186-
187-
#' Size of an object
188-
#'
189-
#' An S7 generic for determining the size of an object. The default method
190-
#' uses [length()], but other classes may have more specific implementations.
191-
#'
192-
#' @param x An object.
193-
#' @param ... Not used.
194-
#' @returns A single number.
195-
#' @export
196-
size <- new_generic("size", "x")
197-
198-
#' @rdname size
199-
method(size, class_any) <- function(x, ...) {
200-
length(x)
201-
}
202-
```
372+
It is your choice whether or not to document S7 **methods**.
373+
S7 methods are registered with `method(generic, class) <- fn`.
374+
Generally, it's not necessary to document straightforward methods.
203375

204-
S7 **methods** are registered with `method(generic, class) <- fn`. You don't need to document methods, but you _may_ want to do so for particularly complicated implementations or methods with extra arguments.
205-
If you do decided to document a method, give it its own roxygen block:
376+
It's good practice, however, to document methods that have unique
377+
behavior or arguments. If you do document a method, give it its own
378+
roxygen block. When documenting a method, always include a link back to
379+
the generic using `[generic_name()]` so the reader can easily find the
380+
full documentation and other methods.
206381

207382
```{r}
208383
#| eval: false
209384
210385
#' Size of a range
211386
#'
212-
#' The size of a range is its length.
387+
#' The size of a range is its [size()], i.e. its length.
213388
#'
214389
#' @param x A `Range` object.
215390
#' @param ... Not used.
@@ -219,9 +394,9 @@ method(size, Range) <- function(x, ...) {
219394
}
220395
```
221396

222-
Note that S7 methods are registered at load time via
397+
S7 methods are registered at load time via
223398
`S7::methods_register()` in your `.onLoad()` function, not through
224-
`NAMESPACE` directives. See `vignette("packages", package = "S7")` for
399+
`NAMESPACE` directives, so you never need to `@export` them. See `vignette("packages", package = "S7")` for
225400
details.
226401

227402
## R6

0 commit comments

Comments
 (0)