Skip to content

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 @export the method and roxygen2 will generate the correct S3method() directive.

  • Imported packages: You have two options. Firstly, import the generic with @importFrom and @export the 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.