Having custom classes validate as slots are added - r

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.

Related

What is the difference between object and .Object in OOP in R?

I'm studying S4 classes and methods and I got confused to know when to use .Object and object (using as an argument to functions on classes). I don't understand if is there any difference between them.
For example, Would be correct:
setGeneric("getTimes",function(object){standardGeneric ("getTimes")})
setMethod("getTimes","Trajectories",
function(object){
return(object#times)
}
)
or:
setGeneric("getTimes",function(.Object){standardGeneric ("getTimes")})
setMethod("getTimes","Trajectories",
function(.Object){
return(.Object#times)
}
)
First, you should avoid the curly braces around {standardGeneric("getTimes")}.
The short answer for your question: there is no difference between the 2 code in your example. You were defining getTimes as a brand new generic function of your own. You can specify its arguments name whatever you like (object, x, xobject, .Object). Then, when you write the methods for the generic function, your methods' arguments name must match with the generic function's arguments name. For example:
setGeneric("getTimes", function(object) standardGeneric("getTimes"))
setMethod("getTimes", "Trajectories", function(object) object#times)
If not follow, there will be error (technically, a warning because R automatically/"silently" correct it. However, in my opinion, R should stop and throw an error in this case):
setGeneric("getTimes", function(object) standardGeneric("getTimes"))
setMethod("getTimes", "Trajectories", function(x) x#times)
# mismatch between `x` argument name in method and `object` argument name in generic
In the case you want to define methods for existing generic, you should use function method.skeleton.
Example 1:
setGeneric("getTimes", function(xobject) standardGeneric("getTimes")) # generic function is defined
getTimes # type function name without parentheses to get a summary of the generic
method.skeleton("getTimes", "Trajectories", stdout())
# copy this method skeleton to your script/source file and modify to your need
Example 2, show is a predefined generic with object as argument (see ?show) or you can type show without parentheses to check. Therefore, setMethod("show", "Trajectories", function(.Object) .Object) will be error. You can proceed using this approach, however, I think method.skeleton is a pretty useful alternative:
> method.skeleton("show", "Trajectories", stdout())
setMethod("show",
signature(object = "Trajectories"),
function (object)
{
stop("need a definition for the method here")
}
)
Example 3, initialize is a generic function and its argument .Object may be defined (type initialize without parentheses to check). From my understanding, the reason .Object is chosen as argument name in this case to invoke the feeling of a prototype object (you can read more at ?initialize). Similarly to Example 2, use the method.skeleton helper function:
> method.skeleton("initialize", "Trajectories", stdout())
setMethod("initialize",
signature(.Object = "Trajectories"),
function (.Object, ...)
{
stop("need a definition for the method here")
}
)
Note: there is a special case for replacement/assignment function (<-), that is its last argument must be named value. Read more. For example:
setClass("Trajectories", slots = c(times = "numeric"))
setGeneric("getTimes", function(x) standardGeneric("getTimes"))
setMethod("getTimes","Trajectories", function(x) x#times)
setGeneric("getTimes<-", function(xobject, value) standardGeneric("getTimes<-"))
setMethod("getTimes<-", c("Trajectories", "ANY"), function(xobject, value) {
xobject#times <- value
xobject
})
# test drive
m <- new("Trajectories", times = 32)
getTimes(m)
getTimes(m) <- 42
getTimes(m)
R will not output any error or warning if you use other name (new_value in below) when defining the generic and accompanying methods. However, when you use it, R will error:
setGeneric("getTimes<-", function(xobject, new_value) standardGeneric("getTimes<-"))
setMethod("getTimes<-", c("Trajectories", "ANY"), function(xobject, new_value) {
xobject#times <- new_value
xobject
})
# test drive
m <- new("Trajectories", times = 32)
getTimes(m)
getTimes(m) <- 42 # error because the right side of <- is always considered as `value` argument

R S4class containing list of another S4class

I have an issue verifying the validity of my class 'class2'; it is made of a list of 'class1' objects. I want to verify that it is indeed the case:
class2 <- setClass(
Class = "class2",
slots = c(slotListName = "list"),
validity = function(object){
lapply(object#slotListName, function(x){
if(!identical(is(x), "class1"))
stop(" not a class1 object");
});
});
The problem is that lapply returns values which are not accepted:
Error in validObject(.Object) :
invalid class “class2” object: 1: NULL
invalid class “class2” object: 2: NULL
I checked that the problem was coming from the lapply by testing only the first element of the list, which is working fine:
if(!identical(is(object#slotListName[[1]]), "class1"))
stop("not a class1 object");
I tried vectorizing but this does not change the problem.
Is there a way to verify that slotListName is indeed a list of 'class1' objects?
Thanks a lot!
The problem with your function is that it gives an error for an invalid object. It's supposed to return a diagnostic message, with the S4 object construction machinery taking care of the error.
Here's how you can do it using the recommended approach, which is to define methods for initialize and setValidity. See ?setClass for more details.
class2 <- setClass("class2", slots=c(slotListName="list"))
setMethod("initialize", "class2", function(.Object, lst)
{
.Object#slotListName <- lst
validObject(.Object)
.Object
})
# returns TRUE if the object is valid, otherwise a diagnostic message
setValidity("class2", function(object)
{
if(length(object#slotListName) < 1)
"must contain at least one class1 object"
else if(!all(sapply(object#slotListName, function(x) inherits(x, "class1"))))
"all objects in list must be class1"
else TRUE
})
## testing
x <- 42
class(x) <- "class1"
y <- 43
class(y) <- "bad"
l1 <- list(x, x, x)
l2 <- list(x, x, y)
## works
obj1 <- class2(l1)
## error: bad object in list
obj2 <- class2(l2)
## error: empty list
obj3 <- class2(list())

S3 Method dispatch for ... or equivalent

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"

Dispatching an argument's default value from an S4 generic function to its associated methods

Suppose all of your S4 methods associated to a specific S4 generic function/method share a formal argument that is supposed to have a specific default value. Intuitively, I would state such an argument in the definition of the S4 generic (as opposed to stating it in each method definition which would seem somewhat redundant to me).
However, I noticed that this way I'm running into trouble as it seems that the default value of the formal argument is not dispatched to the methods and thus an error is thrown.
Isn't this somewhat against the idea of having a combination of a generic and methods? Why would I have to state the formal argument in each method separately again when the default value is always the same? Can I explicitly dispatch formal arguments' default values somehow?
Below you'll find a short illustration of the behavior
Generic function
setGeneric(
name="testFoo",
signature=c("x", "y"),
def=function(
x,
y,
do.both=FALSE,
...
) {
standardGeneric("testFoo")
}
)
Method
setMethod(
f="testFoo",
signature=signature(x="numeric", y="numeric"),
definition=function(
x,
y
) {
if (do.both) {
out <- list(x=x, y=y)
} else {
out <- x
}
return(out)
}
)
Error
> testFoo(x=1, y=2)
Error in .local(x, y, ...) : object 'do.both' not found
Redundant statement of do.both fixes it
setMethod(
f="testFoo",
signature=signature(x="numeric", y="numeric"),
definition=function(
x,
y,
do.both=FALSE
) {
if (do.both) {
out <- list(x=x, y=y)
} else {
out <- x
}
return(out)
}
)
> testFoo(x=1, y=2)
[1] 1
When you call testFoo(x=1, y=2), it is processed first by the S4 generic, which looks for a method, finds it, and dispatches to it a call that looks like this: testFoo(x=1, y=2, do.both=FALSE, ...).
In the words of ?standardGeneric:
‘standardGeneric’ dispatches the method defined for a generic
function named ‘f’, using the actual arguments in the frame from
which it is called.
If the method to which it dispatches that call does not take a do.both argument, the method --- just like any other R function --- throws an error. No function can process a call containing an argument foo unless it's function definition contains either (a) a formal argument foo or (b) a "dots" argument, ..., which can absorb arbitrary supplied arguments.
Basically what you've tried is no different than the following, which fails in a similarly but perhaps easier-to-see way:
testFooGeneric <- function(x=1, y=2, do.both=FALSE, ...) {
## The line below does essentially what standardGeneric() does
if(is.numeric(x) & is.numeric(y)) {
testFooMethod(x=x, y=y, do.both=do.both)
}
}
testFooMethod <- function(x, y) {
cat("Success!\n")
}
testFooGeneric(x=1, y=2)
# Error in testFooMethod(x = x, y = y, do.both = do.both) :
# unused argument(s) (do.both = do.both)
To fix the above, you need to redefine testFooMethod() in one of the following two ways, either of which will also remedy your S4 method:
## Option 1
testFooMethod <- function(x, y, do.both) {
cat("Success!\n")
}
testFooGeneric(x=1, y=2)
# Success!
## Option 2
testFooMethod <- function(x, y, ...) {
cat("Success!\n")
}
testFooGeneric(x=1, y=2)
## Success!

How to write coercion methods

I'm having a bunch of custom-made Reference Classes and would like to write coercion methods for some of them. It'd be nice if a function call would look like this:
objectCoerce(src=obj, to="list", ...)
where ... is the crucial part as sometimes I want to pass additional stuff for certain coercions (see do.deep = TRUE/FALSE below.
However, in order to do that, do I need to implement sort of a "transformer" that takes the to argument, tries to instantiate an empty object of the class specified by to and then calls the "regular" method dispatch? Or is there a better way?
Below you'll find my current solution. It works, but I'm "loosing" the option to coerce to class character" as this class is used to process things to the regular dispatcher and a to = "character would result in infinite recursion. Plus, it's a lot of overhead.
EDIT 2011-12-02
Of course setAs would be the first address to check. But the function specified by arg def in setAs can only take one argument, and often that's too rigid for me. For example, I don't see how I could include the do.deep = TRUE/FALSE switch when using setAs.
Class Defs
setRefClass(Class="MyVirtual")
setRefClass(
Class="A",
contains="MyVirtual",
fields=list(
x="character"
)
)
setRefClass(
Class="B",
contains="MyVirtual",
fields=list(
x.a="A",
x.b="numeric",
x.c="data.frame"
)
)
setGeneric(
name="objectCoerce",
signature=c("src", "to"),
def=function(src, to, ...){
standardGeneric("objectCoerce")
}
)
Generic Method
setGeneric(
name="objectCoerce",
signature=c("src", "to"),
def=function(src, to, ...){
standardGeneric("objectCoerce")
}
)
Intermediate Transformer
setMethod(
f="objectCoerce",
signature=signature(src="ANY", to="character"),
definition=function(src, to, do.deep=FALSE, ...){
# Transform 'to' to a dummy object of class 'to'
to.0 <- to
# For standard R classes
try.res <- try(eval(substitute(
to <- CLASS(),
list(CLASS=as.name(to.0))
)), silent=TRUE)
# For S4 classes
if(inherits(try.res, "try-error")){
try.res <- try(eval(substitute(
to <- new(CLASS),
list(CLASS=to.0)
)), silent=TRUE)
# For my classes. In order to get an 'hollow' object, some of them
# need to be instantiated by 'do.hollow=TRUE'
if(inherits(try.res, "try-error")){
try.res <- try(eval(substitute(
to <- new(CLASS, do.hollow=TRUE),
list(CLASS=to.0)
)), silent=TRUE)
if(inherits(try.res, "try-error")){
stop(try.res)
}
}
}
# Pass transformed 'to' along so the standard method
# dispatcher can kick in.
out <- objectCoerce(src=src, to=to, do.deep=do.deep, ...)
return(out)
}
)
Coercion Method 'MyVirtual' to 'list'
setMethod(
f="objectCoerce",
signature=signature(src="MyVirtual", to="list"),
definition=function(src, to, do.deep=FALSE, ...){
fields <- names(getRefClass(class(src))$fields())
out <- lapply(fields, function(x.field){
src$field(x.field)
})
names(out) <- fields
if(do.deep){
out <- lapply(out, function(x){
out <- x
if(inherits(x, "MyVirtual")){
out <- objectCoerce(src=x, to=to, do.deep=do.deep, .ARGS=.ARGS)
}
return(out)
})
}
return(out)
}
)
Test Run
x <- new("B", x.a=new("A", x="hello world!"), x.b=1:5,
x.c=data.frame(a=c(TRUE, TRUE, FALSE)))
> objectCoerce(src=x, to="list")
$x.a
Reference class object of class "A"
Field "x":
[1] "hello world!"
$x.b
[1] 1 2 3 4 5
$x.c
a
1 TRUE
2 TRUE
3 FALSE
> objectCoerce(src=x, to="list", do.deep=TRUE)
$x.a
$x.a$x
[1] "hello world!"
$x.b
[1] 1 2 3 4 5
$x.c
a
1 TRUE
2 TRUE
3 FALSE
Maybe use setAs to create a coerce method (though one would rather have one's own base class to write the method on, rather than doing this for envRefClass)
setAs("envRefClass", "list", function(from) {
fields <- names(getRefClass(class(from))$fields())
Map(from$field, fields)
})
and then
> as(new("B"), "list")
$x.a
Reference class object of class "A"
Field "x":
character(0)
$x.b
numeric(0)
$x.c
data frame with 0 columns and 0 rows
? The deep version might be like
setAs("envRefClass", "list", function(from) {
fields <- names(getRefClass(class(from))$fields())
curr <- Map(from$field, fields)
recurr <- sapply(curr, is, "envRefClass")
curr[recurr] <- lapply(curr[recurr], as, "list")
curr
})
I don't have good ideas for combining these, other than to create a psuedo-class 'deep_list' and a coerce method to that. I feel like I'm not understanding your post.

Resources