how to extend a reference class defined in an R package? - r

I want to allow users to extend a reference class I define in my package. Here is a toy example:
# my_package/R/Main.R
#' My Main class
#' #export
Main <- setRefClass("Main")
After loading this package, I get a warning when I try to extend it:
library(my_package)
Child <- setRefClass("Child", contains = "Main")
# Warning message:
# Class "Main" is defined (with package slot ‘my_package’) but no metadata object found to revise subclass information---not exported? Making a copy in package ‘.GlobalEnv’
How do I get rid of this warning?

Remember to export the class definition from your package, in the my_package/NAMESPACE file add
exportClasses("Main")

Related

Access package-wide variable from within an R6 class

I am currently packaging up an API wrapper in R and found myself in a scenario in which I need to access a package-wide variable from within an R6 class (as it'll be used for a simple string concatenation). Below you can find an exemplification of my class:
#' A class that will need a package-wide variable
#'
#' #export
MyClass <- R6::R6Class(
"MyClass",
public = list(
# This var is a concat between a package-wide variable and a str
base_url = paste0(.base_url, "me/", "endpoint"),
)
)
.base_url is declared in my zzz.R file in the following way:
.base_url <- "https://api.coolwebsite.com/v1/"
# Assign var to the environment
.onLoad <- function(libname, pkgname) {
assign('.base_url', .base_url, envir = parent.env(environment()))
}
Now, when building the package I receive the following error, I do believe there is something I don't fully grasp in the order of evaluation of files when loading a package.
Loading test
Error in paste0(.base_url, "me/", "endpoint") :
object '.base_url' not found
At the moment, the only way I've found to evaluate .base_url before MyClass is to "alter" evaluation order by declaring .base_url in a file that will be evaluated before zzz.R (like constants.R or anything that'll come earlier in alphabetical order).
How can I keep all my package-wide variables in zzz and avoid incurring in the error above?
Answering my own question here as no one has come up with an answer and it might be helpful to people facing the same issue.
Apparently there are two ways of overriding file order when a package gets attached (more on this here):
1. #include roxygen tag
Listing files a function might depend on using #include roxygen tag, will force to load those files before the function gets evaluated, making sure to have all the variables into the namespace at the time of function evaluation.
#' A class that will need a package-wide variable
#'
#' #include zzz.R
#'
#' #export
MyClass <- R6::R6Class(
# ... All those fancy class methods
)
This will be reflected by changes in the Collate section of your DESCRIPTION file.
2. Edit Collate section in DESCRIPTION file
If not using {roxygen} (but you really should) you can do things by hand and put zzz.R first in the Collate section of your DESCRIPTION file (right after Imports).

How to create new method for generics in ggplot2?

I'm trying to create a new guide for ggplot2 and in the process I had to create a new method for the ggplot2 generic guide_train(). In my code I have
# constructor function which returns an object of
# class = c("guide", "colorbar2", "colorbar")
#' #export
guide_colourbar2 <- function(...) {
# things
}
# methods
#' #export
guide_train.colorbar2 <- function(guide, scale) {
# things
}
The problem is that now CMD check complains that there's no documentation for guide_train.colorbar2. I gather this is because roxygen2 exports it as regular function (export(guide_train.colorbar2)). But since this method is internal to ggplot2 workings, it makes no sense to document it.
Creating my own generic dispatch changes my NAMESPACE so that the new method is exported as S3method(guide_train,colorbar2) and solves the CMD check warning, but now the new guide doesn't work. Using it throws
Error in UseMethod("guide_train") : no applicable method for
'guide_train' applied to an object of class "c('guide', 'colorbar2')"
In this vignette it says that "If you want to add a new method to an S3 generic, import it with #importFrom pkg generic." But adding #importFrom ggplot2 guide_train to my function definition makes my package fail to load with error
Error : object ‘guide_train’ is not exported by 'namespace:ggplot2'
This is the first time I'm working with the S3 system, but it seems to me that the correct way would be the #importFrom way and the real problem is that ggplot2 doesn't export the generic. Is that the case or there are other problems with my code?

Include R6 class object in R package

I am currently developing an R package and I want to include an object of class R6, which is basically an environment, so that users can easily use it (same way it works with datasets in a package).
I have an R6ClassConstructor Gridworld:
Gridworld <- R6::R6Class( ... )
Then I can create a new instance using grid = Gridworld$new(), which generates an R6 class. I then want to save this object grid in the package, so that a user can use it by just typing in grid.
I tried to save grid as an .RData object in the /data folder and document the R6 class in the /R folder:
#' Gridworld
#' #format R6 class
"grid"
but this causes an error in devtools::document: file 'grid.RData' has magic number 'X'
How can I include this R6 class object in the package?
Maybe it would be best to call new when the package is loaded. This way you will not have any troubles regarding reference semantics.
See the answer here
In your case, this would look like
# file R/zzz.R
.onLoad <- function(libname, pkgname){
gridworldInstance <- Gridworld$new()
}
# documentation
#' Instance of grid world
#'
#' some description
#'
#' #name gridworldInstance
NULL
#' #export

Exporting and importing S3 method between packages

Disclaimer: I found the solution to this problem when writing this question. But my question now is "How does it work?"
I am trying to export S3 method from one package (say pkg.from) and import it into another package (say pkg.to). To export the method I use roxygen2 comments.
#' #export
myclass <- function() {
structure(NULL, class = 'myclass')
}
#' #export
print.myclass <- function(x, ...) {
print('NULL with class myclass')
}
File NAMESPACE now contains both the constructor and method.
S3method(print,myclass)
export(myclass)
I believe that means both of them are exported. This supports the fact that I can call both after loading the package.
library(pkg.from)
methods(print)
# ...
# [130] print.myclass*
# ...
print(myclass())
# [1] "NULL with class myclass"
The problem is when I want to import this method into the other package using roxygen2.
#' #importFrom pkg.from print.myclass
During Build this message occurs:
Error : object 'print.myclass' is not exported by 'namespace:pkg.from'
ERROR: lazy loading failed for package 'pkg.to'
If I don't import print.myclass it works even if I don't have package pkg.from loaded. Hadley Wickham writes "you don’t need to do anything special for S3 methods". But I don't think importing a function is "anything special". Now I have more questions then I had when started writing this question.
Where does R stores the methods available for every generic?
How can one solve conflicts when he has installed more packages with the same method for a class?

What is the right way to lock a field for a R reference class within a package?

I'm trying to make a factory method for creating my reference class objects. I have one field which MUST be read-only, and so I want to give my package users a way to generate these reference class objects with a guaranteed lock on this field.
My RCs and my factory are both in a package. All of this works fine when I'm working outside of the package but I get the following error when I use the RCs in the package:
Error in methods:::.lockRefFields(def, ...) :
the definition of class "(my ref class name)" in package 'my package name' is locked so fields may not be modified Calls:
test_check ... eval -> build.rc -> ->
It looks like the error comes from this bit in the package:methods source code
if (.classDefIsLocked(def))
stop(gettextf("the definition of class %s in package %s is locked so fields may not be modified",
Important: When loading my source code, I can lock as normal without problems. The problem lies with my RCs being sourced from a package. It appears that their "classDefIsLocked" (whatever that means).
Here is my code that produces the error:
build.rc <- function(read_only_field, read_only_value, ref_class_name) {
generator <- getRefClass(ref_class_name)
# don't relock if the read_only_field has already been locked
if (!(read_only_field %in% generator$lock())) {
generator$lock(read_only_field)
}
return(generator$new(read_only_value))
}
And here is how I define my RC within my package:
#' #exportClass my.ref.class
setRefClass(
Class = "my.ref.class",
...
)

Resources