How do I define a method for a list of objects of the same class?
Eg.
foo <- letters[1:5]
foo2 <- letters[6:10]
class(foo) <- "abc"
class(foo2) <- "abc"
new_method <- function(...) {UseMethod("new_method", ...)}
new_method.abc <- function(...) {do.call("c", list(...))}
# use results in error
new_method(foo,foo2)
Error in new_method(foo, foo2) : '...' used in an incorrect context
Here I want ... to be an arbitrary length list of objects all of which have the same class and I want to do something with them (combine them, specific to my real class's use case).
It makes sense to me that ... doesn't have a class that can be sent to method dispatch; but a simple re-write also doesn't work since new_method.list doesn't / shouldn't exist
new_method <- function(...) {UseMethod("new_method", list(...))}
new_method(foo,foo2)
Error in UseMethod("new_method", list(...)) :
no applicable method for 'new_method' applied to an object of class "list"
Related
How could I use some of the arguments passed to a function using the ellipsis to create a new object of an S4 class, while naming the arguments?
Example:
foo <- function(a, ...){
cur_args <- lapply(match.call(expand.dots=TRUE)[-1], deparse)
args_to_keep <- names(cur_args) %in% slotNames("myClass1")
newClassObj <- new("myClass1", what can go here??? )
}
Is there any way to use do.call and comply with the types of the slots of the class / retain the type as it was passed to foo?
newClassObj <- do.call( "new", as.list( c("Class"="myClass1", cur_args[args_to_keep] )) )
You have the right idea,
you just have to create the list of arguments passed to new correctly:
setClass("A", slots=c(x="numeric"))
foo <- function(...) {
dots <- list(...)
valid_slots <- dots[intersect(names(dots), slotNames("A"))]
do.call(new, c(list(Class="A"), valid_slots))
}
> foo(x=1, y=2)
An object of class "A"
Slot "x":
[1] 1
c can be used to append lists to each other,
so you just need to wrap Class="A" in a list.
I wrote a function that returns an object of class myClass. Specific to this class, I have a function tryMe.myClass() that I'd like to be generic to myClass, such that I only have to call tryMe(object, x) instead of tryMe.myClass(object, x), where tryMe(object, x) will only work of object is of class myClass.
Both functions (the constructor and tryMe.myClass()) have their own .R-File inside a package I created.
What do I need to modify for this to work?
Much thanks!
Stefan
Just define a generic as in line ## below
tryMe <- function(object, ...) UseMethod("tryMe") ##
tryMe.myClass <- function(object, x, ...) "ok"
# test
obj <- structure(NA, class = "myClass")
tryMe(obj, 3)
## [1] ok
With the S3 object system, in a generic method you can delegate to the next method in a class hierarchy using the NextMethod() function. When Wickham describes this system at http://adv-r.had.co.nz/S3.html, he uses NextMethod() without any arguments in his example:
baz <- function(x) UseMethod("baz", x)
baz.C <- function(x) c("C", NextMethod())
I've always used NextMethod() without argument in my own code as well.
I just noticed that [.Date uses an explicit argument:
> `[.Date`
function (x, ..., drop = TRUE)
{
cl <- oldClass(x)
class(x) <- NULL
val <- NextMethod("[")
class(val) <- cl
val
}
The documentation for ?NextMethod says the following:
Normally 'NextMethod' is used with only one argument, 'generic'
In terms of performance, is calling NextMethod("generic") faster than NextMethod()? Is there any other reason to prefer one usage over the other?
I'm attempting to override base (non-S3) methods to provide colnames methods for a custom R object. I want to do this with S3 not S4.
For the colnames accessor, this can be achieved by setting the base function to be the default method, then providing a method for my class:
colnames <- function(x, ...) UseMethod("colnames")
colnames.default <- base::colnames
colnames.myclass <- function(x, ...) {
# some code here
}
However, how would one override the setter method. I would hope something like this should work?
"colnames<-" <- function(x, value) UseMethod("colnames<-")
"colnames<-.default" <- "base::colnames<-"
"colnames<-.myclass" <- function(x, value) {
print("Setting colnames for myclass")
# Some code
}
However, this seems to fail to call the base function correctly for a regular matrix:
> test <- matrix(1:10, 5)
> colnames(test) <- c("a", "b")
Error in UseMethod("colnames<-") :
no applicable method for 'colnames<-' applied to an object of class "c('matrix', 'integer', 'numeric')"
You set colnames<-.default to a character string (i.e. not a function). That's not going to work.
"colnames<-.default" <- "base::colnames<-"
Use backticks to reference objects with non-syntactic names.
`colnames<-.default` <- base::`colnames<-`
Lets say I want to make a class "myClass" with two slots A and B.
now I want a validObject function that ensures A and B are the same length
same_length <- function(object){
if(length(object#A)!=length(object#B)) {
"vectors are not the same length"
} else TRUE
}
setClass("myClass", representation(A="numeric", B="numeric"),
validity=same_length)
I saw a function somewhere that will ensure the class is valid when initialized:
setMethod("initialize", "myClass", function(.Object, ...){
value <- callNextMethod()
validObject(value)
value
})
which will send an error if I try
newObj <- new("myClass", A=c(1,2,3), B=c(1,2))
But if I do
newObj <- new("myClass")
newObj#A <- c(1,2,3)
newObj#B <- c(1,2)
no error is thrown. How do I get it to throw an error as soon as a new slot assignment does not validate?
Write a 'replacement method' that does the check. To do this, we need to create a generic function (because no function with the appropriate name and signature already exists)
setGeneric("slotA<-", function(x, ..., value) standardGeneric("slotA<-"))
We then need to implement the replacement method for the specific types of objects we want to handle -- the first argument is of class 'myClass', the second argument (value) is of class 'numeric':
setReplaceMethod("slotA", c("myClass", "numeric"), function(x, ..., value) {
x#A = value
validObject(x)
x
})
We might also write a 'getter' generic and method
setGeneric("slotA", function(x, ...) standardGeneric("slotA"))
setMethod("slotA", "myClass", function(x, ...) x#A)
and then
> a=new("myClass", A=1:10, B=10:1)
> slotA(a)
[1] 1 2 3 4 5 6 7 8 9 10
> slotA(a) = 1:5
Error in validObject(x) :
invalid class "myClass" object: vectors are not the same length
Note that the default initialize method calls checkValidity, so if you use callNextMethod as the last line in your constructor there's no need to explicitly check validity.