There are three things that you might document for S3:
- Generics: mention that the function is a generic and list the available methods.
- Methods: link back to the generic; only document individually when the method has unique behavior or arguments.
- Classes: document the constructor.
Generics
S3 generics are regular functions, so document them
as such. @export a generic if you want users to call it or
other developers to write methods for it. If the generic is internal,
you don’t need to export or document it.
The documentation should mention that the function is a generic, because this tells the reader that the behavior may vary depending on the input and that they can write their own methods. For simple generics, you can do this in the description:
#' Frobnpolicate an object
#'
#' @description
#' `frobnpolicate()` is an S3 generic that ..., with methods available for
#' the following classes:
#'
#' `r doclisting::methods_list("frobnpolicate")`For more complicated generics, you can use a # Methods
section to provide more detail:
#' Frobnpolicate an object
#'
#' @description
#' `frobnpolicate()` does ...
#'
#' # Methods
#' `frobnpolicate()` is an S3 generic with methods available for the following
#' classes:
#'
#' `r doclisting::methods_list("frobnpolicate")`You might also want to include a section that provides additional details for developers implementing their own methods.
Both examples above use the doclisting package to
automatically generate a list of methods, with links to their help
topics. These examples use inline R
code (`r `), which generates the list at documentation
time (i.e. when you run devtools::document()). This only
requires including doclisting in Suggests.
If you want the list to dynamically reflect all methods that are
currently registered (including methods registered by other packages),
use an inline Rd code block
(`Rd `) instead:
#' `Rd doclisting::methods_list("frobnpolicate")`Using `Rd ` requires doclisting in Imports,
and you’ll need a dummy call to eliminate the R CMD check
NOTE about unused imports:
ignore_unused_imports <- function() {
doclisting::methods_list
}Classes
S3 classes have no formal definition, so document
the constructor.
@export the constructor if you want users to create
instances of your class or other developers to extend it (e.g. by
creating subclasses). Internal constructors don’t need
documentation.
Note that you don’t need to list methods for a class: in S3, methods belong to generics, not to classes. Users should look at the generic’s help page to learn about available methods.
Methods
It is your choice whether or not to document S3
methods. Generally, it’s not necessary to document
straightforward methods for common generics like
print().
You must, however, always @export S3 methods, even for
internal generics. This registers the method so that
the generic can find it; a user can’t directly access the method
definition by typing its name. roxygen2 will warn you if you have
forgotten.
#' @export
bizarro.character <- function(x, ...) {
letters <- strsplit(x, "")
letters_rev <- lapply(letters, rev)
vapply(letters_rev, paste, collapse = "", FUN.VALUE = character(1))
}If you are exporting a method in some other way, you can use
@exportS3Method NULL to suppress the warning.
It’s good practice to document methods that have unique behavior or
arguments. For example, the clock package documents the methods for date_group()
on their own pages (Date, POSIXt)
because the methods accept different precision values and
have type-specific return value semantics and examples. The generic page
serves only as a signpost linking to the individual method pages.
When documenting a method, always include a link back to the generic
using [generic_name()] so the reader can easily find the
full documentation and other methods.
Methods for generics in other packages
It’s common to write methods for generics defined in other packages. There are three cases you need to be aware of:
Base packages: you don’t need to do anything special: just
@exportthe method and roxygen2 will generate the correctS3method()directive.-
Imported packages: You have two options. Firstly, import the generic with
@importFromand@exportthe method:.#' @importFrom pkg generic #' @export generic.foo <- function(x, ...) {}Alternatively, use
@exportS3Method pkg::generic:#' @exportS3Method pkg::generic generic.foo <- function(x, ...) {} Suggested packages: you can’t import the generic for a suggested package, so you must use
@exportS3Method pkg::generic. This uses delayed registration, so the method is only be registered when the suggested package is loaded.
Generally, roxygen2 can automatically figure out which generic the
method belongs to. But there is occasionally ambiguity if the generic
name contains .. You can avoid this problem in your own
packages by not using . in generic names. But you might
encounter it when writing methods for generics in other packages. For
example, is all.equal.data.frame() the
equal.data.frame method for all(), or the
data.frame method for all.equal()? If this
happens to you, disambiguate with @method:
#' @method all.equal data.frame
#' @export
all.equal.data.frame <- function(target, current, ...) {
# ...
}roxygen2 does now automatically handle the all.equal
case for you, so this should happen very very rarely. And, again it can
be avoided by not using . in generic or class names.
