Creating S3 class in an R Package - r

I have written code that very simply creates an S3 class for a package. I seek to create a new S3 class so that I can develop, e.g., custom print() methods.
I have tested the code in a simple R script, but as soon as the function is wrapped into a package, the functionality breaks and the S3 class is no longer created. I cannot provide reproducible code for the creation of the package, but a simplified version of the function I would like to build into the package is introduced below.
My code works perfectly when written either raw or within a function.
x <- 1:10
class(x)
class(x) <- append(class(x),"newS3class")
class(x) will return c("integer", "newS3class")
Likewise, now, declare a function that does the same thing. This also works fine. In reality, my function foo() first performs some action on the input, and then returns the output with a new class.
foo <- function(y) {
y <- y + 3
class(y) <- append(class(y), "newS3class")
y
}
class(1:5) returns "integer". class(foo(1:5)) returns c("integer", "newS3class"). This is as expected.
But, when I develop a package, e.g., mypkg, and then run mypkg::foo(), the functionality is broken. I.e., (mypkg::foo(1:5)) returns "integer" rather than c("integer", "newS3class").
Is something happening regarding scoping, in the process of building the package from its constituent functions, that is preventing this from working?

The mistake I made here was that I did not properly export the class. To fix this I added the following codeblock, including the export line, before using the roxygen package to build my documentation and NAMESPACE.
#' #export summary.objectclass
makeobjectclass <- function(x) {
class(x) <- c("objectclass", class(x))
x
}

Related

When I know the class of an R object, can I call the appropriate method directly?

Consider a simple R function:
#' #importFrom BarPackage Bar
Foo <- function (n, x) {
replicate(1e6, Bar(numeric(n), x))
}
If Bar() is a method, then calling Bar.numeric(numeric(n), x) saves R from having to look up the class of numeric(n); in a real-world example, this shaves about 10% off my run time.
However, Bar.numeric() is a function in a separate package (which I maintain). Roxygen creates export(Bar) and S3method(Bar, numeric) entries in NAMESPACE, but if I add importFrom BarPackage Bar to the FooPackage NAMESPACE and try to run Foo(), I see Warning message: 'Bar.numeric' is not exported by 'namespace:BarPackage' -- as indeed it's not.
It feels like I need to create a separate entry in my NAMESPACE reading export(Bar.numeric), but this feels like a very bad idea (in part because I don't think that Roxygen will do it). Is there any other way of avoiding R having to look up the class of numeric(n) on each call to Bar()?

Passing values between functions in an R package

In an R package, let's say we have two functions. One is setting some parameters; the other one is using those parameters. How can I build such a pattern in R. It is similar to event-driven applications. But I am not sure if it is possible in R or not.
For example:
If we run set_param( a=10), whenever we run print_a.R, it prints 10, and incase of running set_param(a=20), it prints 20.
I need a solution without assigning value to the global environment because CRAN checks raise notes.
I suggest adding a variable to your package, as #MrFlick suggested.
For instance, in ./R/myoptions.R:
.myoptions <- new.env(parent = emptyenv())
getter <- function(k) {
.myoptions[[k]]
}
setter <- function(k, v) {
.myoptions[[k]] <- v
}
lister <- function() {
names(.myoptions)
}
Then other package functions can use this as a key/value store:
getter("optA")
# NULL
setter("optA", 99)
getter("optA")
# [1] 99
lister()
# [1] "optA"
and all the while, nothing is in the .GlobalEnv:
ls(all.names = TRUE)
# character(0)
Values can be as complex as you want.
Note that these are not exported, so if you want/need the user to have direct access to this, then you'll need to update NAMESPACE or, if using roxygen2, add #' #export before each function definition.
NB: I should add that a more canonical approach might be to use options(.) for these, so that users can preemptively control and have access to them., programmatically.

How to define an S3 generic with the same name as a primitive function?

I have a class myclass in an R package for which I would like to define a method as.raw, so of the same name as the primitive function as.raw(). If constructor, generic and method are defined as follows...
new_obj <- function(n) structure(n, class = "myclass") # constructor
as.raw <- function(obj) UseMethod("as.raw") # generic
as.raw.myclass <- function(obj) obj + 1 # method (dummy example here)
... then R CMD check leads to:
Warning: declared S3 method 'as.raw.myclass' not found
See section ‘Generic functions and methods’ in the ‘Writing R
Extensions’ manual.
If the generic is as_raw instead of as.raw, then there's no problem, so I assume this comes from the fact that the primitive function as.raw already exists. Is it possible to 'overload' as.raw by defining it as a generic (or would one necessarily need to use a different name?)?
Update: NAMESPACE contains
export("as.raw") # export the generic
S3method("as.raw", "myclass") # export the method
This seems somewhat related, but dimnames there is a generic and so there is a solution (just don't define your own generic), whereas above it is unclear (to me) what the solution is.
The problem here appears to be that as.raw is a primitive function (is.primitive(as.raw)). From the ?setGeneric help page, it says
A number of the basic R functions are specially implemented as primitive functions, to be evaluated directly in the underlying C code rather than by evaluating an R language definition. Most have implicit generics (see implicitGeneric), and become generic as soon as methods (including group methods) are defined on them.
And according to the ?InternalMethods help page, as.raw is one of these primitive generics. So in this case, you just need to export the S3method. And you want to make sure your function signature matches the signature of the existing primitive function.
So if I have the following R code
new_obj <- function(n) structure(n, class = "myclass")
as.raw.myclass <- function(x) x + 1
and a NAMESPACE file of
S3method(as.raw,myclass)
export(new_obj)
Then this passes the package checks for me (on R 4.0.2). And I can run the code with
as.raw(new_obj(4))
# [1] 5
# attr(,"class")
# [1] "myclass"
So in this particular case, you need to leave the as.raw <- function(obj) UseMethod("as.raw") part out.

functions in .Rprofile are not found with are in .env

I have a .Rprofile I have copied from https://www.r-bloggers.com/fun-with-rprofile-and-customizing-r-startup/ However, when I load my R session the functions that are in env$ they don't work and the functions not in env works perfectly, here is an example:
sshhh <- function(a.package){
suppressWarnings(suppressPackageStartupMessages(
library(a.package, character.only=TRUE)))
}
auto.loads <-c("dplyr", "ggplot2")
if(interactive()){
invisible(sapply(auto.loads, sshhh))
}
.env <- new.env()
attach(.env)
.env$unrowname <- function(x) {
rownames(x) <- NULL
x
}
.env$unfactor <- function(df){
id <- sapply(df, is.factor)
df[id] <- lapply(df[id], as.character)
df
}
message("n*** Successfully loaded .Rprofile ***n")
Once R is loaded I can type sshhh and it shows the function, but if I type unfactor it shows object not found
Any help? Should I put all the functions on my workspace???
They functions created in a separate environment are intentionally hidden. This is to protect them from calls to rm(list=ls()).
From the original article:
[Lines 58-59]: This creates a new hidden namespace that we can store
some functions in. We need to do this in order for these functions to
survive a call to “rm(list=ls())” which will remove everything in the
current namespace. This is described wonderfully in this blog post [1].
To use the unfactor function, you would call .env$unfactor().
If you want to make those function available in the global namespace without having to refer to .env, you can simply leave out the whole .env part and just add the function the same way you did for the sshhh function.
[1] http://gettinggeneticsdone.blogspot.com.es/2013/07/customize-rprofile.html

Operator overload stops working in R package

I have a container class that is basically a list. Because I wanted to support subsetting, I have overloaded the subset [ operator (likely poorly implemented).
#' Constructor for spectra object
.spectra = function(n_spectrum = 0) {
object = vector(mode = "list", n_spectrum)
class(object) = "spectra"
return(object)
}
#' Operator overload
#' #export
`[.spectra` = function(x, i) {
x = unclass(x)
x = x[i] # Using the list's subset function
class(x) = "spectra"
return(x) # Should return a "spectra" object, not a list
}
Now, this works as expected when in my development environment (when I'm debugging the package). That is, if y_old is a spectra object and I do y_new = y_old[-1], y_new is still a spectra object.
However, when I compile the project as a package and install it, the subsetting operator returns a list instead of a spectra object.
Any clue of what is going on?
EDIT
I forgot to mention that I'm using RStudio and the devtools library.
This problem boils down to RStudio's default initialization of NAMESPACE when you choose to create a package. Inspecting the NAMESPACE file reveals:
exportPattern("^[[:alpha:]]+")
Which doesn't match the subset operator [, as MrFlick pointed out.
You can either add names to NAMESPACE manually or you can get RStudio and Roxygen to do the work for you. In RStudio 0.99.902 you would:
install.packages("roxygen2")
Check Generate documentation with Roxygen in the menu Build > Configure Build Tools > Build Tools.
Click on the Configure button and check the NAMESPACE file check box.
Now you obviously have to add Roxygen documentation to your functions and remember to use the #export tag, e.g.:
#' Print hi in R
#' #export
print_hi = function(x) print("hi")
After building you should have a automatically generated NAMESPACE file. For instance:
# Generated by roxygen2: do not edit by hand
export(print_hi)

Resources