user1981275
user1981275

Reputation: 13372

Documenting R6 class methods with Roxygen2

I'm writing a package with an R6 class that has several methods. I would like to be able to generate documentation for both, class and methods. For the below example, I would like to be able to access the docs with ?Person for the class and ?set_hair, for the method. Here my example class:

#' This is my Person class
#' @title Person Class
#' @docType class
#' @description Person class description
#' @field name Name of the person
#' @field hair Hair colour
#'
#' @section Methods:
#' \describe{
#' \item{set_hair Set the hair color}
#' }
#' 
#' @examples
#' Person$new(name="Bill", hair="Blond")
#' @export
Person <- R6::R6Class("Person",
  public = list(
    name = NULL,
    hair = NULL,
    initialize = function(name = NA, hair = NA) {
      self$name <- name
      self$hair <- hair
  },    

    # '@name set_hair
    # '@param val: hair colour
    set_hair = function(val) {
      self$hair <- val
  },
  )
)

Running roxygenise(), the annotations above the method bodies are not rendered at all, so the only information I specify in @section Methods is in the docs.

Since I have over 50 class methods, it would be much nicer if I can access the method docs with ?methodname sepeartly. I found some some posts on this (Documenting R6 classes and methods within R package in RStudio, https://github.com/klutometis/roxygen/issues/306), but it seems to me that this is not supported for R6 classes.

What would be the best way to document my class methods separately?

Upvotes: 5

Views: 3417

Answers (2)

yuskam
yuskam

Reputation: 400

This is an old post and you might have solved your question long ago. But it's not added here so in case someone need the solution, it will be:

#' This is my Person class
#' @description Person class description
#' @field name Name of the person
#' @field hair Hair colour
#' 
#' @examples
#' Person$new(name="Bill", hair="Blond")
#' @export
Person <- R6::R6Class("Person",
  public = list(
    name = NULL,
    hair = NULL,

    #' @description
    #' Create a person
    #' @param name Name of the person
    #' @param hair Hair colour
    initialize = function(name = NA, hair = NA) {
      self$name <- name
      self$hair <- hair
  },    

    #' @description Set hair
    #' @param val Hair colour
    set_hair = function(val) {
      self$hair <- val
  },
  )
)

Upvotes: 6

Alex Firsov
Alex Firsov

Reputation: 168

The discussion on the github-raised issue linked in user2554330's comment above indicates that separated out documentation is not something on the to-do list for roxygen, as it does not match traditional style for method documentation. That said, I've still found it useful, and have been using a workaround.

One partial solution is to create functional wrappers. This is a semi-manual process that can be cumbersome given lots of methods (as in your case), but it does enable clear and semi-automated documentation for R6 methods in separate docs. Using the person example, here is the would-be implementation of a document-able wrapper in roxygen2:

#` Method for setting hair
#` 
#` @param person a person class object
#` @param val hair color
#` 
#` @return nothing; modifies \code{person}
#` @export
#` 
#` @examples
#` bill <- Person$new(name="Bill", hair="Blond")
#` bill$set_hair("InspiredRed")
#` bill$hair
#` set_hair(bill, "MetalBlack")
#` bill$hair
set_hair <- function(person, val){
  person$set_hair(val)
  invisible()
}

The result would be two separate .Rd files, one for the person class, and one for the set_hair method, both accessible with ?.

The result has an added advantage in that a call closer to functional form may be preferred by most R users, since this is closer to how most R syntax is set up. Both person$set_hair(val) and set_hair(person, val) will produce the same result without any need for explicit assignment, maintaining R6's advantages while adding minimal overhead.


EDIT:

After bringing this up with a few people, I've anecdotally found more preference for having actual functional form wrappers--ones which sacrifice the reference component in favor of a functional approach, as it is nearer to R's "norm." In this case, the approach still provides the same documentation advantages, while referential method calls are still available via $. However, additional emphasis should be made on the existence of the referential method approach when writing the wrapper documentation.

#` Clones person and changes hair
#` 
#` @param person a person class object
#` @param val hair color
#` 
#` @return nothing; modifies \code{person}
#` @export
#` 
#` @details This creates a new person with the same characteristics as the \code{person}
#' provided, except with new hair. To update the original person's hair by reference,
#' use \code{person$set_hair()}.
#` 
#` @examples
#` bill <- Person$new(name="Bill", hair="Blond")
#` bill$set_hair("InspiredRed")
#` bill$hair
#` set_hair(bill, "MetalBlack")
#` bill$hair
set_hair <- function(person, val){
  personNew <- person.clone()
  personNew$set_hair(val)
  invisible(personNew) # invisible assuming no print method, but you probably want one
}

Of course, which route to take with these wrappers depends on your specific application's speed and memory requirements. The functional approach may create enough of a hindrance to not be viable.

Upvotes: 2

Related Questions