This is a two part Question. I want to set the value of a prototype s4 object based on a different slots value and alternatively I want to implement this as a method.
I have an object I am trying to create. It has some slots. I would like to set a slots value based on the values put in from another slot. This is a simplified version of what I want to do.
i.e.,
setClass("Person",
representation(name = "character", age = "numeric", doubleAge = "numeric"),
prototype(name = "Bob", age = 5, doubleAge = 10) )
now I want to create an object but have the doubleAge value set itself based on the age slot.
p1 <- new("Person", name = "Alice", age = 6)
I see
An object of class "Person"
Slot "name":
[1] "Alice"
Slot "age":
[1] 6
Slot "doubleAge":
[1] 10
but I want to see doubleAge be 12. In the prototype, I do not know how to change doubleAge = 10
to something like doubleAge = 2*age
So as a solution I tried to make a setting function init
which sets the value after creation. This is the part 2 question.
setGeneric("init", "Person", function(object) {
standardGeneric("init")
}
setMethod("init","Person", function(object) {
object@doubleAge <- object@age*2
object
}
if I print object@doubleAge
in the method it returns 12 but it seems that the scope ends because it is 10 when it returns
Currently what works is very similar but it is not correct.
setGeneric("init<-", "Person", function(object) {
standardGeneric("init<-")
}
setMethod("init<-","Person", function(object) {
object@doubleAge <- object@age*2
object
}
but then I have to call like init(p1) <- NULL
which just seems weird. I know this example seems trivial but it is just a barebones example of a more complicated real world problem.
It seems overriding the initialize method worked for me. For example
setClass("Person",
representation(name = "character", age = "numeric", doubleAge = "numeric"),
prototype(name = "Bob", age = 5, doubleAge = 10) )
setMethod("initialize", "Person", function(.Object, ...) {
.Object <- callNextMethod()
.Object@doubleAge <- .Object@age*2
.Object
})
(p1 <- new("Person", name = "Alice", age = 6))
# An object of class "Person"
# Slot "name":
# [1] "Alice"
# Slot "age":
# [1] 6
# Slot "doubleAge":
# [1] 12
The callNextMethod()
runs the "default" initializer to set up all the values that we are not messing with. Then we just change the values we want and return the updated object.
In addition to the above method for initialize
, you may want to set a method for @<-
. The reason for this is if you do something like
x <- new("person", age=5)
then you have a valid person with age=5
and doubleage=10
. But what if you now do
x@age <- 6
? Now age
is 6 but doubleage
is still 10 so the object is not valid any more.
The R documentation claims that you can write a method for @<-
that will fix this:
setMethod("@<-",signature(object="Person"),function(object,name,value){
if(name=="age"){
object@age <- x
object@doubleAge <- x*2
} else if(e2=="doubleAge"){
object@doubleAge <- x
object@age <- value/2
} else slot(object,name) <- value
object
})
however when you actually run the above, you get an error:
Error in setGeneric(f, where = where) : ‘@<-’ dispatches internally; methods can be defined, but the generic function is implicit, and cannot be changed.
This is a strange looking error, since we are not trying to redefine the generic. In fact we find, when we try
method.skeleton("@<-",signature(object="Person"))
that R reluctantly tells us
Error in genericForBasic(name) : methods may not be defined for primitive function ‘@<-’ in this version of R
So if you want to have the slots reliably be consistent, we will have to write our own getters and setters, along the lines of
setAge <- function(x,value){
x@age <- value
x@doubleAge <- value*2
x
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With