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

R6 class support #922

Merged
merged 30 commits into from Oct 3, 2019
Merged

R6 class support #922

merged 30 commits into from Oct 3, 2019

Conversation

@gaborcsardi
Copy link
Collaborator

@gaborcsardi gaborcsardi commented Sep 26, 2019

This is far from being ready, I am opening it for discussion. Feedback is welcome.

Still coming:

  • consistent naming of the initialize() / new() method
  • support for fields, right now they have to be documented manually, via @field.
  • support for active bindings, they are manual for now, via @field as well.
  • add inheritance information, i.e. link to superclasses
  • get rid of the compulsory @details tag, in the main part and also before a method (maybe later)
  • support @description as well
  • change method docs order: 1. Description 2. Usage 3. Arguments 4. Details.
  • check that all public methods are documented
  • check that docs and names(formals()) of method arguments match
  • allow inheritParams() from methods (maybe later)
  • allow inheritSection() from methods, maybe (maybe later)
  • allow @inheritDotParams() from methods (maybe later)
  • allow documenting a common argument up front in the class, and then automatically use this in the documentation of the methods
  • A test(that) helper to run the code in the examples, because these do not
    run in R CMD check.
  • Dump all the methods examples into a big \examples{} block at the end, so
    they are tested.
  • should we used \describe{} for the list of arguments? Probably....
  • actually arguments are in a table, so we should do that instead...
  • support methods added via set(). (maybe layer)
  • support user defined clone() methods. (maybe later)
  • support documenting private methods, fields, active bindings? (maybe later)
  • test cases
  • documentation
  • a vignette? (maybe later)
  • check what CSS or other updates pkgdown needs to format the reference pages well.
  • Fix paragraphs in argument blocks, because they are not wrapped currently.
  • Format warnings the usual roxygen way.

This is the current syntax:

#' R6 class for a person
#' 
#' Description of the class.
#' 
#' @details
#' Details of the class.
#' 
#' @section Public fields:
#' * `name`: first name.
#' * `hair`: hair color.

Person <- R6Class("Person",
  public = list(
    name = NULL,
    hair = NULL,

    #' @details
    #' Create a new `Person` object.
    #' 
    #' @param name First name.
    #' @param hair Hair color, if known.
    #' @return A person object.
    #' 
    #' @examples
    #' Person$new("Ann", "black")

    initialize = function(name = NA, hair = NA) {
      self$name <- name
      self$hair <- hair
      self$greet()
    },

    #' @details
    #' Set new hair color.
    #' 
    #' @param val The new hair color.
    #' @return The new color, invisibly.
    #' 
    #' @examples
    #' ann <- Person$new("Ann", "black")
    #' ann$hair
    #' ann$set_hair("red")
    #' ann$hair

    set_hair = function(val) {
      self$hair <- val
    },

    #' @details
    #' Make the person greet.
    #' 
    #' @examples
    #' ann <- Person$new("Ann", "black")
    #' ann$greet()

    greet = function() {
      cat(paste0("Hello, my name is ", self$name, ".\n"))
    }
  )
)

This will generate the following:

Person               package:pkgdepends                R Documentation

R6 class for a person

Description:

     Description of the class.

Details:

     Details of the class.

Public fields:

        • ‘name’: first name.

        • ‘hair’: hair color.

Methods:

  Public methods:

         • ‘Person$new()’

         • ‘Person$set_hair()’

         • ‘Person$greet()’

  Method 'new()':

       Person$new(name = NA, hair = NA)

     Create a new ‘Person’ object.

    Arguments:

           • ‘name’: First name.

           • ‘hair’: Hair color, if known.


    Returns:

         A person object.


    Examples:

         Person$new("Ann", "black")


  Method 'set_hair()':

       Person$set_hair(val)

     Set new hair color.

    Arguments:

           • ‘val’: The new hair color.


    Returns:

         The new color, invisibly.


    Examples:

         ann <- Person$new("Ann", "black")
         ann$hair
         ann$set_hair("red")
         ann$hair


  Method 'greet()':

       Person$greet()

     Make the person greet.

    Examples:

         ann <- Person$new("Ann", "black")
         ann$greet()

This is how it looks in RStudio:

Screen Shot 2019-09-26 at 10 36 57 AM

It could look, nicer, e.g. with some extra CSS:

Screen Shot 2019-09-26 at 10 38 09 AM

Closes #388.

@gaborcsardi
Copy link
Collaborator Author

@gaborcsardi gaborcsardi commented Sep 26, 2019

Apart from the "usual" @inheritParams, it seems like it would be useful to be able to document the same parameter for several methods up front. I'll add this to the list.

gaborcsardi added a commit to r-lib/pkgdepends that referenced this issue Sep 26, 2019
The package now needs the R6 support PR from roxygen:
r-lib/roxygen2#922
@sckott
Copy link

@sckott sckott commented Sep 30, 2019

What about examples? Seems like maybe \examples in man files is needed so that cmd check & devtools::run_examples will run examples? Maybe devtools::run_examples could find and run the \subsection{Examples}{...} sections created by the doc sections in R6 public methods?

@gaborcsardi
Copy link
Collaborator Author

@gaborcsardi gaborcsardi commented Sep 30, 2019

@sckott You can have @examples in the main block, e.g. to showcase all/most of the methods, and that'll be \examples{} in the man page, and CRAN will be happy as well.

@sckott
Copy link

@sckott sckott commented Sep 30, 2019

right, that's what I currently do. Should best practice be then to not have examples in public method blocks since they could not be working examples?

@gaborcsardi
Copy link
Collaborator Author

@gaborcsardi gaborcsardi commented Sep 30, 2019

Should best practice be then to not have examples in public method blocks since they could not be working examples?

IDK, I think I want to have examples for the methods sometimes, and then have a helper among the test cases to make sure that they run.

You still cannot run them with example(), but that seems tricky to fix.

@gaborcsardi gaborcsardi force-pushed the feature/r6 branch 2 times, most recently from 15d701e to b4b88a3 Oct 1, 2019
@gaborcsardi
Copy link
Collaborator Author

@gaborcsardi gaborcsardi commented Oct 2, 2019

@sckott Now we also dump all the method examples into a large \examples{} block at the end, so it is tested by R CMD check and no helpers etc. are needed.

@gaborcsardi gaborcsardi force-pushed the feature/r6 branch 3 times, most recently from 5772f73 to e094c65 Oct 3, 2019
@gaborcsardi
Copy link
Collaborator Author

@gaborcsardi gaborcsardi commented Oct 3, 2019

@hadley All TODO list items are done.

gaborcsardi added 12 commits Oct 3, 2019
We create subclass based on object types. This
will let us format the docs differently for
different types of objects.
We put this information into the `.r6data` tag.
It is stored in a data frame with columns:
- `type`: this is always `"method"` now, because we
  only deal with methods. We will add fields,
  active bindings and inheritance information later.
- `name`: the name of the method, etc.
- `file`: name of the file, from the source refs.
  In theory methods can be defined in different
  files, but this is not tested yet.
- `line`: line number for the start of the method,
  from the source refs.
- `formals`: the `formals()` of the method.
This will let us define a custom page formatter
for R6 classes.
It writes out the entries for the main
part (i.e. the part that does not belong to
methods) first, e.g. description, details,
main sections, etc. All tags that are not
`@details`, `@param`, `@return` and `@examples`
automatically got into the main part. These
four tags got into the 'Methods' section, if
they appear in the inline comments, i.e. the
comments within the class definition.

The 'Methods' section has the two parts. First
there is a list of all public methods, with
in-page links. Then there is one subsection
for each method. These appear in the same order
as in the source file.

For each method we have:
- An HTML anchor, so the method can be linked
  via `[link text](#method-mymethod)`
  (from the same page), and with
  `[link text][myclass#method-mymethod]` from other
  pages.
- A "usage" part (not a subsection), with
  the formals of the method.
- Details of the method, the `@details` tags for the
  method.
- An 'Arguments' subsection if the method takes
  arguments.
- A 'Returns' subsection if the method had a
  `@return` tag. (Max one is allowed.)
- An 'Examples' subsection if the method had
  any `@examples` tags.
Because \return is implicitly a \describe in
\arguments. So we should do the same, because then
the custom CSS will apply for both the non-R6 arguments
and the R6 methods arguments.
This is what R does with the `\arguments()` blocks.
Not sure how this looks in the PDF, though.
Look better and this avoids the double rule at the end of
the page.
1) Lists the superclasses, e.g.
   ‘processx::process’ -> ‘callr::r_process’ ->
   ‘pkgdepends::r_untar_process’
   Where each class name is a link.
2) Lists the inherited methods, in HTML.
   Currently not in text and PDF, because it might be a
   long list. Seems OK in HTML. Each method name is a link.
Rd2tex does some magic to make this work, but only in
a \value{} section, so that does not help us.
The prefix (i.e. _not_ inline) docs may contain @param tags,
that the methods automatically inherit, if needed. The methods
can also override these arguments, in which case they won't be
inherited. These @param tags will be left out from the man page
of the class otherwise.

If you want to document a function on the same page as the class,
e.g. a factory function, then include its arguments in a
separate block, the block of the function.

This is experimental, and might still change. One drawback is that
we cannot inherit currently from or into the method docs, but
we can probably fix this later.
gaborcsardi added 17 commits Oct 3, 2019
* Add 'Usage' and 'Details' headers.
* Make sure usage is formatted with the right width of
  the class$method name.
* Omit 'Details' section if no @details tags.
Also change the order of subsections in a method doc to:
1. description
2. usage
3. details
4. examples
Because \tabular does not wrap the text in text and TeX/PDF output.
* Remove row names from data frames.
* Save inherited fields and active bindings.
* Save superclass information, even if nothing is inherited.
New function: `roxy_warning()`, `roxy_tag_warning()` is
a special case of this now.
* Do not link our class, that does not make much sense.
* Use the class names, not the block alias.
* Fix shadowed method detection in method list.
* Fix class order in hierarchy.
* Order methods according to class hierarchy in
  method list.
@hadley hadley merged commit 52b4bba into master Oct 3, 2019
10 checks passed
@hadley hadley deleted the feature/r6 branch Oct 3, 2019
@hadley
Copy link
Member

@hadley hadley commented Oct 3, 2019

Thanks @gaborcsardi!!

@wch
Copy link
Member

@wch wch commented Oct 3, 2019

Awesome!

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

Successfully merging this pull request may close these issues.

4 participants