How to export new generic function for new S3 class? - r

I define a new function work_with_myS3 that is supposed to work with my new S3 class myS3:
work_with_myS3 = function (x) {
UseMethod("work_with_myS3", x)
}
work_with_myS3.myS3 = function(x) {
some code
}
When I source this in my normal R session (I'm using RStudio), it behaves exactly as expected. When I feed it an myS3 object, it works; when I feed it something strange, it throws an error:
> work_with_myS3(123)
Error in UseMethod("work_with_myS3", x) :
no applicable method for 'work_with_myS3' applied to an object of class "c('double', 'numeric')"
However, when I include this in my package, build it, reload it and try to call it:
Error: could not find function "work_with_myS3"
Help page works fine though, calling ?work_with_myS3. This is how I document it by devtools::document():
#' Do stuff with myS3
#'
#' What it does
#' #import dplyr
#' #param x object of class myS3
#' #method work_with_myS3 myS3
#' #export
There is also an entry in the namespace:
S3method(work_with_myS3,myS3)
Why is this, and how to make the function available in the package? I suspect I'm making some trivial mistake.

When you call work_with_myS3 with an object of class myS3 the UseMethod function looks for:
work_with_myS3.myS3 or
work_with_myS3.default
Therefore you need to export work_with_myS3.myS3, so
#' #export
work_with_myS3.myS3 = function(x) {
## do stuff
}
Alternatively, you could define a default method and export that.

Related

Unable to access object inside function using an external function R6

I have a R6 class with code like this
# Cutting out lots of code and only putting in relevant lines
public(
function1 <- function(){
var <- xyz$abc
},
function2 <- function(){
xyz <- blah blah
z <- function1()
}
)
When calling function2 I get an error in function1 saying that xyz is not found even though its assigned in function2 which is called before function1
Please let me know if I am understanding this correctly and how to fix it.
For "traditional" R functions the parent of the evaluation environment of a function is the calling environment.
For R6 function this not the same. The parent of the evaluation environment of a method is an environment enclosing the self variable that gives access to object properties.
You can test this by adding
print(ls(parent.env(environment()))) in your method.
This means that you can't have access to your xyz variable in function1. You must use public or private variables or pass it as a parameter to your function.
By the way you must also prepend self$ to the call of function1 (self$function1())

Idiom for default S4 method?

Is there any agreed upon way of writing a fallback method for an S4 generic? I mean, if no signatures are matched, do you write a function to be used as default method that throws perhaps a message() explaining valid signatures? And what return value do you use? Or do you throw an error? Or do you do nothing at all and let R throw the usual error "unable to find an inherited method for function (...)".
Any recommendation?
Yes, there’s a agreed-on way, depending on whether a good default function already exists as a non-generic or not.
1. If there’s already a non-generic function that can serve as a default
Simply make it generic:
setGeneric('function_name')
2. If there’s no non-generic default function yet
Define a new generic
setGeneric(
'function_name',
function (object) {
standardGeneric('function_name')
}
)
Add a default implementation with catch-all parameters
setMethod(
'function_name',
signature(object = 'ANY'),
function (object) {
…
}
)
If you can write a generic that might plausibly work for a wide variety of possible classes, then it is common to do so. For example, the (S3) default method for terms tries to find a $terms component (or attribute — note that this includes the case when the argument is an S4 object with a #terms slot) in its first argument:
function (x, ...)
{
v <- x$terms
if (is.null(v)) {
v <- attr(x, "terms")
if (is.null(v))
stop("no terms component nor attribute")
}
v
}
(terms happens to be an S3 generic but there is no reason why it would not still behave this way if it were S4.)
If there is no sensible thing you can do then all you can really write for a default method is a function that accepts all the arguments in the signature of the generic (plus ... in case there are some methods that have extra arguments) and just calls stop with an error message along the lines of what you suggest.
In S3, you didn't need to do this — you could just not define the default method. This is the case with predict — if you call predict(2,3) say then you will get an error no applicable method for 'predict' applied to an object of class "c('double', 'numeric')". In S4, you will need to define such a default method yourself in the call to setGeneric.

In R, how can I reference an S4 class from another package in my class definition?

Suppose I have two packages PackageA and PackageB. I have an S4 class, ClassA, in PackageA that I would like to use as a base class for ClassB in PackageB:
setClass(
"ClassB",
slots = c(),
validity = function(object) {
T
}
contains = "ClassA")
However, when I build I get the error:
no definition found for superclass "ClassA"
I have tried adding a reference to the PackageA with devtools:
devtools::use_package("PackageA")
Perhaps I need to use an roxygen directive?
Turns out that ClassA was not being imported properly. Adding the correct roxygen directive solved the problem:
#' #import PackageA
setClass(
"ClassB",
slots = c(),
validity = function(object) {
T
}
contains = "ClassA")

Is there anyway to document S4 class and its constructor separately using Roxygen2

I am trying to design a S4 class with my own initialization method and separately document them with Roxygen2. Assuming my class is defined as:
#' This is the classA
#' #name classA-class
#' #rdname classA-class
######## #aliases NULL
#' #exportClass classA
classA <- setClass(Class = "classA", slots = list(member = "ANY"))
With the initialization method:
#' This is the constructor
#' #name classA
#' #rdname classA
#' #export classA
setMethod("initialize", "classA", function(.Object, x) {
.Object#member = x
return(.Object)
})
After compiling the package with Roxygen2. I got 2 .Rd files with 3 help page:
classA-class: This is the classA
classA: This is the classA
classA: This is the constructor
Both class?classA and ?classA will show classA-class help page. This is definitely what I want as I hope class?classA will lead to classA-class: This is the classA while ?classA will lead to classA: This is the constructor.
So, my question is how to separate the class documentation and constructor documentation with Roxygen2?
Thank you so much for all your help.
(I understand that Roxygen2 will put aliases with the class name for S4 class by default. But when I set #aliases NULL, the classA-class: This is the classA help page disappeared!! left only the classA: This is the constructor)
The reasons that both help files show up under the class documentation is that the setClass always uses the class name as an alias for the class in the documentation. In addition, your method for the generic initialize defined for classA specifies #rdname classA which points it to the classA-method.
Here are two solutions:
First, you can simplify the Roxygen2 header codes and get the behaviour you want, but you will need to use #name to call the initialize method something other than classA.
#' \code{classA} class definition
#'
#' #slot member description of this slot here
classA <- setClass(Class = "classA", slots = list(member = "ANY"))
#' constructor for \link{classA-class}
#'
#' This is the constructor.
#' #name initializeClassA
#' #export
setMethod("initialize", "classA", function(.Object, x) {
.Object#member = x
return(.Object)
})
That gives you the classA-class page for ?classA and ?"classA-class" but for the constructor, you get whatever name you used after #rdname, e.g. ?initializeClassA, if that's what you want.
Second, I would recommend that you not use initialize at all, instead create new instances of classA using new("classA", memberValue). You can define a prototype in the class definition to assign default values of member if you want, and/or validator functions. Do you really need a separate help page for the constructor?

Proper way to implement S3 dispatch on R6 classes

I have an R6 class and I want to add an S3 method for it. The documentation I found mentioned briefly that in order to use S3 dispatch on R6 you must have class = TRUE, but I couldn't find an example of how it should be done.
I did see empirically that simply writing an S3 method in the form s3generic.r6class worked, but I wanted to know if that is indeed to right way to write an S3 method for R6.
For example, say I have an R6 class that enhances a list
library(R6)
R6list <- R6Class(
"R6list",
public = list(
orig = NULL,
initialize = function(x) {
self$orig <- x
}
)
)
Question 1
Naturally, I want to provide a method for obtaining the underlying list, so I wanted to add an as.list method. Is it standard to add both an S3 generic AND a as.list public function inside the class? My intuitive answer is to add both.
R6list <- R6Class(
"R6list",
public = list(
orig = NULL,
initialize = function(x) {
self$orig <- x
},
as.list = function() {
self$orig
}
)
)
as.list.R6list <- function(x, ...) {
x$as.list()
}
So that now if I have an object mylist <- R6list$new(as.list(letters[1:5])) I can either call as.list(mylist) or mylist$as.list(). Is one of those preferred over the other?
Question 2
Is there anything special about writing an S3 method for R6 classes, or is what I wrote above sufficient and the correct way? I wasn't sure if the S3 method has to be written outside of the class definition, or if R6 somehow provides a way to write S3 methods within it so that all the code relating to the class is localized.
I asked Winston Chang, the author of R6, about this on Github. According to him, the code provided in Question 1 above is the correct way of writing S3 methods for R6 classes.

Resources