Include R6 class object in R package - r

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

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).

Export error on overwriting primitives with S3 in R package

I'm trying to create a S3 method in my package called dimnames. This is a primitive in R, but there should be an S3 in my package with the same name.
I've got the following file dimnames.r
#' S3 overwriting primitive
#'
#' #param x object
#' #export
dimnames = function(x) {
UseMethod("dimnames")
}
#' title
#'
#' #export
dimnames.data.frame = function(x) {
dimnames.default(x)
}
#' title
#'
#' #export
dimnames.list = function(x) {
lapply(x, dimnames)
}
#' title
#'
#' #export
dimnames.default = function(x) {
message("in S3 method")
base::dimnames(x)
}
I then create a package from it (in R=3.3.2):
> package.skeleton("rpkg", code_files="dimnames.r")
> setwd("rpkg")
> devtools::document() # version 1.12.0
And then check the package
R CMD build rpkg
R CMD check rpkg_1.0.tar.gz
I get the following output (among other messages):
Warning: declared S3 method 'dimnames.default' not found
Warning: declared S3 method 'dimnames.list' not found
Loading the package and checking its contents, dimnames.data.frame is exported while dimnames.default and dimnames.list are not. This does not make sense to me. As far as I understand, I declared the exports correctly. Also, the NAMESPACE file looks good to me:
S3method(dimnames,data.frame)
S3method(dimnames,default)
S3method(dimnames,list)
export(dimnames)
Why does this not work, and how to fix it?
(Bonus points for: why do I need #' title in the S3 implementations when they should not be needed with roxygen=5.0.1?)
S3 methods are only exported if it is desired that the user be able to access them directly. If they are always to be invoked via the generic then there is no need to export them.
The problem with R CMD check is likely due to defining your own generic for dimnames. Normally one just defines methods and leverages off the primitive generic already in R. Remove the dimnames generic from dimnames.r.
There should be no problem in adding methods for new classes but you may have problems trying to override the functionality of dimnames for existing classes that R's dimnames handles itself.

How to declare a base S3 function so it can also be used in other packages?

In R, I wish to declare a new S3 class, and then define methods for it in two packages.
Currently, this works fine for one package, but the second package to load masks the first, an then the class methods for package one are not found (and it ends up calling the .default method.
Asked on devotees google list, and got the working solution from Hadley to create a third package, declare the base class there, then import and export that namespace in the two real packages. But I'd really rather not use a third package...
Any suggestions? (not this is all being built with devtools, which treats S3 objects appropriately, based on the . in the function name)
For those not familiar with S3, you declare a default handler, class handlers, and a stub function which figures out a class handler exists, and if not calls the default handler.
So, in package1, I define and export a base class, some useful handlers, and a default.
#' #export
#' myFun <- function(x, ...){
#' UseMethod("myFun", x)
#' }
#' #export
#' myFun.x1 <- function(x, ...){
#' message("hi, I handled an x1 object from package 1")
#' }
#' #export
#' myFun.default <- function(x,...){
#' print("myFun is not defined")
#'}
In package 2, I wish just to define and export some additional object handlers
#' #export
#' myFun.x2 <- function(x, ...){
#' message("hi, I handled an x2 object from package 2")
#' }
The second package depends on the several fns in package 1, so I depend on it in the DESCRIPTION file:
Depends:
packageOne
FYI, if I import packageOne, I get the error
Error : object 'myFun' not found whilst loading namespace 'packageTwo'
Depending on package1 ensures that it is loaded and available whenever package2 is loaded, but R's resolve chain seems only to look in which ever package loads last for handlers. Calling an S3 function for an object with a handler in package 1 works fine. But if the handler is defined in package2, the lookup table fails to find a handler in package 2, and calls default in package 1.
I guess I could write a myFun.defaul handler in package 2 which explicitly calls package1's version of myFun. Suggestions welcomed
#' #export
#' myFun.default <- function(x,...){
#' package1::myfun(x, ...)
#'}
perhaps that will work

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

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")

How to specify in which order to load S4 methods when using roxygen2

I ran into following problem already multiple times.
Say you have two classes, classA and classB described in the following files classA.R :
#' the class classA
#'
#' This is a class A blabla
#' \section{Slots}{\describe{\item{\code{A}}{a Character}}}
#' # name classA
#' #rdname classA
#' #exportClass classA
setClass("classA",representation(A="character"))
And classB.R
#' the class classB
#'
#' This is a class B blabla
#' \section{Slots}{\describe{\item{\code{B}}{an object of class A}}}
#' # name classB
#' #rdname classB
#' #exportClass classB
setClass("classB",representation(B="classA"))
I believed these files were read in alphabetical order by roxygen2, but that is not the case. If I try to build the package, I might get the following error:
roxygenize("./myExample")
Error in getClass(Class, where = topenv(parent.frame())) :
"ClassA" is not a defined class
How can I make sure that roxygenize() knows in which order to read the files, i.e. which class definition should be read before the other?
Note : I know I answered my own question. That is because I ran into this problem rather often, and realized the proper way to do this after looking at the code of roxygen2. So for reference, here's my findings.
There are two ways to achieve this:
As described in ?collate_roclet, you can use the #include tag to specify which class should be read before which. In this case, you can add to the file classB.r the following line right before the actual R code:
#' #include classA.r
These tags are read specifically to update the Collate field in the DESCRIPTION file, and are the recommended way of dealing with the problem.
In some cases the dependencies might be so complex you want to keep an overview yourself and not rely completely on adding #include tags in your codebase. In that case you can just specify the Collate field at the end of the DESCRIPTION file, like this:
Package: myExample
Type: Package
...
Collate:
'classA.R'
'classB.R'
The function roxygenize() first checks the DESCRIPTION file, and loads whatever files are specified there first in the order they're specified. Only then the rest of the package is loaded.

Resources