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).
Related
Since roxygen2 version 4.0.0, the #S3method tag has been deprecated in favour of using #export.
The package now tries to detect if a function is an S3 method, and automatically adds the line S3method(function,class) to the NAMESPACE file if it think it is one.
The problem is that if a function is not an S3 method but its name contains a . then roxygen sometimes makes a mistake and adds the line when it shouldn't.
Is there a way to tell roxygen that a function is not an S3 method?
As requested, here's a reproducible example.
I have a package that imports R.oo, with a function named check.arg.
library(roxygen2)
package.skeleton("test")
cat("Imports: R.oo\n", file = "test/DESCRIPTION", append = TRUE)
writeLines(
"#' Check an argument
#'
#' Checks an argument.
#' #param ... Some arguments.
#' #return A value.
#' #export
check.arg <- function(...) 0",
"test/R/check.arg.R"
)
roxygenise("test")
Now the namespace contains the line S3method(check,arg).
check is an S3 generic in R.oo, so roxygen is trying to be smart and guessing that I want check.arg to be an S3 method. Unfortunately, these functions are unrelated, so I don't.
(To preempt suggestions that I just rename check.arg: this is legacy code written by others, and I've created a checkArg replacement, but I need to leave check.arg as a deprecated function for compatibility.)
As Mr Flick commented, appending the full function name to the roxygen line works correctly. If I change the line to:
#' #export check.arg
then the NAMESPACE file contains:
export(check.arg)
Use #method generic class and #export instead of #S3method. Take a look at this thread:
S3 method help (roxygen2)
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.
Since roxygen2 version 4.0.0, the #S3method tag has been deprecated in favour of using #export.
The package now tries to detect if a function is an S3 method, and automatically adds the line S3method(function,class) to the NAMESPACE file if it think it is one.
The problem is that if a function is not an S3 method but its name contains a . then roxygen sometimes makes a mistake and adds the line when it shouldn't.
Is there a way to tell roxygen that a function is not an S3 method?
As requested, here's a reproducible example.
I have a package that imports R.oo, with a function named check.arg.
library(roxygen2)
package.skeleton("test")
cat("Imports: R.oo\n", file = "test/DESCRIPTION", append = TRUE)
writeLines(
"#' Check an argument
#'
#' Checks an argument.
#' #param ... Some arguments.
#' #return A value.
#' #export
check.arg <- function(...) 0",
"test/R/check.arg.R"
)
roxygenise("test")
Now the namespace contains the line S3method(check,arg).
check is an S3 generic in R.oo, so roxygen is trying to be smart and guessing that I want check.arg to be an S3 method. Unfortunately, these functions are unrelated, so I don't.
(To preempt suggestions that I just rename check.arg: this is legacy code written by others, and I've created a checkArg replacement, but I need to leave check.arg as a deprecated function for compatibility.)
As Mr Flick commented, appending the full function name to the roxygen line works correctly. If I change the line to:
#' #export check.arg
then the NAMESPACE file contains:
export(check.arg)
Use #method generic class and #export instead of #S3method. Take a look at this thread:
S3 method help (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.
A simple example is that I have created an extension to show, which is a S4 base method. I don't want to cause a disambiguation fork by re-documenting show in my package, and I also want to consolidate the documentation of my extension to show in the documentation for the new class, myPkgSpClass, by adding an alias for show,myPkgSpClass-method.
#' #export
#' #aliases show,myPkgSpClass-method
#' #rdname myPkgSpClass-class
setMethod("show", "myPkgSpClass", function(object){ show(NA) })
The problem I'm having, is that this results in an serious warning during documentation-build by roxygen2, Rd files with duplicated alias 'show': because there is more than one class extension to show in this package, and roxygen2 has automatically added the generic term in the list of aliases to all the relevant *-class.Rd files:
\alias{show}
\alias{show,myPkgSpClass-method}
But I think I don't want the generic alias in any of the instances, because it will force the need for disambiguation between show in my package, and the base show. This issue also applies to other S4 methods extended from other packages besides show.
If I tag all class-specific methods to the same .Rd file, then the warning goes away, but the ambiguity remains, because the show alias still gets added automatically for that doc entry. If I manually remove \alias{show} from the .Rd file, then the problem seems solved, no warnings during roxygen or R CMD check pkgname. So how do I get Roxygen2 to not-add the generic alias?
Other background:
This is a specific question building from a previous issue for exporting/documenting S4 extensions to base methods:
Is it necessary to export base method extensions in an R package? Documentation implications?
It is more specific than, and not covered by, the following questions regarding documenting S4 methods / classes using Roxygen2:
How to properly document S4 methods using roxygen2
How to properly document S4 class slots using Roxygen2?
Seems to be fixed in roxygen2_3.1.0:
#' #export
#' #aliases show,myPkgSpClass-method
#' #rdname myPkgSpClass-class
setMethod("show", "myPkgSpClass", function(object){ show(NA) })
produces the myPkgSpClass-class.Rd:
\docType{methods}
\name{show,myPkgSpClass-method}
\alias{show,myPkgSpClass-method}
\usage{
\S4method{show}{myPkgSpClass}(object)
}
\arguments{
\item{object}{Any R object}
}
As Hadley stated, you do not need anymore to explicitly set the alias or the rd name, e.g.:
#' my title
#' #export
setMethod("show", "myPkgSpClass", function(object){ show(NA) })
will generate show-myPkgSpClass-method.Rd:
\docType{methods}
\name{show,myPkgSpClass-method}
\alias{show,myPkgSpClass-method}
\title{my title}
\usage{
\S4method{show}{myPkgSpClass}(object)
}
\arguments{
\item{object}{Any R object}
}
\description{
my title
}
Note that in that case you have to set the description. It will not generate a doc page if the documentation entry is empty.