Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documenting R6 classes with and without roxygen2 #3

Closed
gaborcsardi opened this issue Aug 1, 2014 · 22 comments
Closed

Documenting R6 classes with and without roxygen2 #3

gaborcsardi opened this issue Aug 1, 2014 · 22 comments

Comments

@gaborcsardi
Copy link
Member

Somewhat related to #2. It would be nice to have an example of the best practices for documenting R6 classes with and/or without roxygen2.

@gaborcsardi
Copy link
Member Author

Well, I guess eventually roxygen2 will support R6. It has already has some methods to parse docstrings from reference classes, these can be handy to do the same for R6 classes.

For now the only option is to write the man page(s) by hand.

You can close this for now if you want, or just leave it here as a reminder. I'll also open an issue klutometis/roxygen at some point.

@fkgruber
Copy link

Hi
Has there been any progress regarding ROxygen support for R6 classes?

thanks

@oscardelama
Copy link
Contributor

I have managed a workaround to be able to use ROXygen for the documentation of a package using R6 classes.

I have a doc_myClass.R in the R folder with a RC class with a prototype (fields and empty methods) mimicking the real R6 Class and its public methods, only for documentation sake:

#--------------------------------------------------------------------------
#'  Analysis of the half variance of delta images versus mean
#'
#' The \code{hvdvm} class contains tools for the preparations, analysis and
#' plotting of the half variance of delta images for the study of noise in raw
#' image files.
#----------------------------------------------
xyz <- setRefClass("xyz",
 fields = list(
   channel.labels = "character",
   green.channels = "numeric"
 ),
 methods = list(

   new = function(channel.labels=NA, green.channels=NA, avg.green.channel.label=NA) {
     "Create a new instance of the hvdvm class"
     NULL
   },

   digest = function(photo.conds.file.name, crop.files.path='./') {
     "Collect statistics from the raw images files"
     NULL
   }
))

## ==> Remove/destroy the RC class <===
rm(xyz)

Each RC method must contain a docstring that will be used by ROxygen2 for the class documentation. The class is named xyz or whatever else you want, but not the R6 class name you will really export. Then, open the generated "xyz-class.Rd" text file and replace xyz with your real R6 class name; do the same with the file name. Now convert the ROxygen comments in your 'xyz' class to normal comments to avoid the generation of documentation for the fake xyz class.

I know this is ugly, but it is just for the class documentation as whole. For the documentation of your R6 Class methods. I am using the following schema:

Create a (temporal) list and add to it functions with the signature of your real R6 class public methods and document it with regular ROxygen2. Use the @name tag to name the documented function as your real R6 class$method. In my example this is hvdvm$new.

This way, when the final user executes help('hvdvm$new') the corresponding help content appears correctly.

Unfortunately if the user runs ?hvdvm$new R will throw this error:

Error in .helpForCall(topicExpr, parent.frame()) :  no documentation for function ‘$’ and signature ‘x = "R6ClassGenerator"’
In addition: Warning message: 
In .helpForCall(topicExpr, parent.frame()) :  no method defined for function ‘$’ and signature ‘x = "R6ClassGenerator"’

@wch: It seems like R is interacting with your R6 class generator trying to handle the '$' function in the search for the corresponding help text. Is it possible to add some feature to the R6ClassGenerator in order to avoid this error?

# Create a list
hvdvm_doc <- list()

#----------------------------------------------
#' Create a new instance of the hvdvm class
#'
#' Creates and initializes a new hvdvm class instance.
#'
#' With the \code{channel.labels} argument you can specify the labels
#' corresponding to the Bayer color filters, starting at ....
#'
#' @param channel.labels A vector of characters labeling of each one of the
#'   Bayer color filters in the (raw) image samples.
#'  ....
#'
#' @name hvdvm$new
#----------------------------------------------
hvdvm_doc$new<- function(channel.labels=NA, green.channels=NA, is.RGGB=FALSE, avg.green.channel.label=NA) {
  NULL
}
:
# ==> Destroy the list <==
rm(hvdvm_doc)

@fkgruber
Copy link

thanks for the workaround

@jranke
Copy link
Contributor

jranke commented Dec 18, 2014

I really like R6 classes. I have found that using something like

#' An example R6 class
#' 
#' @docType class
#' @importFrom R6 R6Class
#' @export
#' @format An \code{\link{R6Class}} generator object
#' @keywords data

myR6Class <- R6Class("myR6Class",
  ...
)

works well. Specifying @formatand @keywords overrides roxygen default values that are less appropriate. It can be extended using @field and methods can be described using

#' @section Methods:
#' \describe{
#'   \item{\code{example_method(parameter_1 = 3)}}{This method uses \code{parameter_1} to ...}
#' }

Just a pity that R CMD check will not warn if the method arguments are not properly documented.

@gaborcsardi
Copy link
Member Author

@jranke This is actually quite good, although I haven't looked at the output yet.

Btw. is @exportClass really needed? Isn't that for S4 only?

@jranke
Copy link
Contributor

jranke commented Dec 18, 2014

@gaborcsardi Yes, you are right. I removed it from my comment above - maybe my approach is a bit experimental. But it produces nice Rd output and the generator objects get exported. Just didn't publish the package yet where I am using this approach.

@imanuelcostigan
Copy link

I think this is a useful placeholder, but can I suggest future discussion happen over at r-lib/roxygen2#306 ?

@petermeissner
Copy link

For those stumbling here looking for a way / example to document their R6 class, I found this to be a nice template:

https://github.com/Ermlab/lightning-rstat/blob/41ba35d647d0f6953055eb61c87d950c6b21cc97/LightningR/R/lightning.R

#' Class providing object with methods for communication with lightning-viz server
#'
#' @docType class
#' @importFrom R6 R6Class
#' @importFrom RCurl postForm
#' @importFrom RJSONIO fromJSON toJSON
#' @importFrom httr POST
#' @export
#' @keywords data
#' @return Object of \code{\link{R6Class}} with methods for communication with lightning-viz server.
#' @format \code{\link{R6Class}} object.
#' @examples
#' Lightning$new("http://localhost:3000/")
#' Lightning$new("http://your-lightning.herokuapp.com/")
#' @field serveraddress Stores address of your lightning server.
#' @field sessionid Stores id of your current session on the server.
#' @field url Stores url of the last visualization created by this object.
#' @field autoopen Checks if the server is automatically opening the visualizations.
#' @field notebook Checks if the server is in the jupyter notebook mode.
#' #' @section Methods:
#' \describe{
#'   \item{Documentation}{For full documentation of each method go to https://github.com/lightning-viz/lightining-r/}
#'   \item{\code{new(serveraddress)}}{This method is used to create object of this class with \code{serveraddress} as address of the server object is connecting to.}
#'
#'   \item{\code{sethost(serveraddress)}}{This method changes server that you are contacting with to \code{serveraddress}.}
#'   \item{\code{createsession(sessionname = "")}}{This method creates new session on the server with optionally given name in \code{sessionname}.}
#'   \item{\code{usesession(sessionid)}}{This method changes currently used session on the server to the one with id given in \code{sessionid} parameter.}
#'   \item{\code{openviz(vizid = NA)}}{This method by default opens most recently created by this object visualization. If \code{vizid} parameter is given, it opens a visualization with given id instead.}
#'   \item{\code{enableautoopening()}}{This method enables auto opening of every visualisation that you create since that moment. Disabled by default.}
#'   \item{\code{disableautoopening()}}{This method disables auto opening of every visualisation that you create since that moment. Disabled by default.}
#'   \item{\code{line(series, index = NA, color = NA, label = NA, size = NA, xaxis = NA, yaxis = NA, logScaleX = "false", logScaleY = "false")}}{This method creates a line visualization for vector/matrix with each row representing a line, given in \code{series}.}
#'   \item{\code{scatter(x, y, color = NA, label = NA, size = NA, alpha = NA, xaxis = NA, yaxis = NA)}}{This method creates a scatterplot for points with coordinates given in vectors \code{x, y}.}
#'   \item{\code{linestacked(series, color = NA, label = NA, size = NA)}}{This method creates a plot of multiple lines given in matrix \code{series}, with an ability to hide and show every one of them.}
#'   \item{\code{force(matrix, color = NA, label = NA, size = NA)}}{This method creates a force plot for matrix given in \code{matrix}.}
#'   \item{\code{graph(x, y, matrix, color = NA, label = NA, size = NA)}}{This method creates a graph of points with coordinates given in \code{x, y} vectors, with connection given in \code{matrix} connectivity matrix.}
#'   \item{\code{map(regions, weights, colormap)}}{This method creates a world (or USA) map, marking regions given as a vector of abbreviations (3-char for countries, 2-char for states) in \code{regions} with weights given in \code{weights} vector and with \code{colormap} color (string from colorbrewer).}
#'   \item{\code{graphbundled(x, y, matrix, color = NA, label = NA, size = NA)}}{This method creates a bundled graph of points with coordinates given in \code{x, y} vectors, with connection given in \code{matrix} connectivity matrix. Lines on this graph are stacked a bit more than in the \code{graph} function.}
#'   \item{\code{matrix(matrix, colormap)}}{This method creates a visualization of matrix given in \code{matrix} parameter, with its contents used as weights for the colormap given in \code{colormap} (string from colorbrewer).}
#'   \item{\code{adjacency(matrix, label = NA)}}{This method creates a visualization for adjacency matrix given in \code{matrix} parameter.}
#'   \item{\code{scatterline(x, y, t, color = NA, label = NA, size = NA)}}{This method creates a scatterplot for coordinates in vectors \code{x, y} and assignes a line plot to every point on that plot. Each line is given as a row in \code{t} matrix.}
#'   \item{\code{scatter3(x, y, z, color = NA, label = NA, size = NA, alpha = NA)}}{This method creates a 3D scatterplot for coordinates given in vectors \code{x, y, z}.}
#'   \item{\code{image(imgpath)}}{This method uploads image from file \code{imgpath} to the server and creates a visualisation of it.}
#'   \item{\code{gallery(imgpathvector)}}{This method uploads images from vector of file paths \code{imgpathvector} to the server and creates a gallery of these images.}}


Lightning <- R6Class("Lightning",
...
)

@maelle
Copy link

maelle commented Feb 17, 2016

Thanks @petermeissner ! I was "stumbling here looking for a way / example to document their R6 class" 😄

@richfitz
Copy link

I have some documented R6 classes in storr - Rd here and rendered here. I think this is actually quite a hard thing to get right in general, but definitely doable for someone sufficiently motivated.

@maelle
Copy link

maelle commented Feb 17, 2016

Thanks @richfitz 👍

For now I have no methods to document, only slots, because even initializing happens in a non-exported function so that's easy but I can see it getting more complicated later!

@hhoeflin
Copy link

@richfitz

thanks for providing the documentation example. The .Rd says that it was created using roxygen from R/storr.R, but looking at the storr.R code, I couldn't find the documentation there that lets one create this. Is there some trick to it?

Thanks!

@richfitz
Copy link

Yes, sorry, there is a trick (not one I'd recommend - native roxygen support would be really nice).

  • actual data is here
  • that's run through this revolting hack which checks that everything is documented appropriately given R CMD check is not going to do that for me...
  • to create this roxygen file
  • which in turn is included in the main roxygen source here which is why it says it's come from storr.R

@petermeissner
Copy link

Girls and Guys no thanks anymore - please. It clutters my mail and I really do not want to un-follow this issue. If you feel thankful, go and fix a typo, or bug, or simply be nice to whoever you meet next.

@hhoeflin
Copy link

I have put up a pull-request with a suggestion for documenting R6 classes.

r-lib/roxygen2#465

Essentially, it is close to how rc-classes are documented, i.e. with a docstring. But instead of a single string, it allows several strings, using @param to describe parameters and sets a usage statement inside every class method (as longer methods aren't appropriately set in the first part of a \item due to line-breaks not being possible there, I think).

Would be great to get feedback.

Thanks

@maelle
Copy link

maelle commented Apr 11, 2016

@hhoeflin cool! Have you put an example of a R6 class documentation somewhere (source and Rd)? I've looked at the PR but it'd be easier to see an example before giving feedback.

@hhoeflin
Copy link

An example is attached at the pull request

@maelle
Copy link

maelle commented Apr 13, 2016

@hhoeflin I'm sorry I had only looked at the code 😀 I think it'd be nice to document methods in methods as you do in this suggestion.

How would you document fields?

@hhoeflin
Copy link

Fields get documented the same way as in rc classes. At the top of the R6class with a @field tag.

@hhoeflin
Copy link

One thing that I currently don't like though: I stuck to the convention of using "docstrings", i.e. strings at the beginning of a function. That is not so nice as then it is necessary to escape all slashes that are used for rd-commands.

An alternative would be to document it as before using comments, parse the functions keeping the source there and then extract the comments from the function source.

@gaborcsardi
Copy link
Member Author

Here is another example:
https://github.com/gaborcsardi/processx/blob/bc7483237b0fbe723390cbb74951221968fdb963/R/process.R#L2

The tricks:

  • we document on NULL, to have more flexibility, and provide the name of the class in @name
  • the sections are specified manually, e.g. @section Usage:

The rendered docs looks very much like regular R documentation, and you can write whatever you like in Usage, which is good I think.

Anyway, I think we have enough examples now, so I can close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants