Hi Renaud --
Renaud Gaujoux <ren...@mancala.cbio.uct.ac.za> writes:
Hello list,
I'm having troubles setting up a basic calss hierarchy with S4.
Here is a simplified schema of what I'd like to do:
- Two classes: A and B that extends A
- Ensure that the slots added by B are consistent with the slots of A
- Let A initialize itself (I'm not supposed to know the internal
cooking of A)
- By default set the slots of B based on the slots that A initialized
Another question is: what is the recommended way of implementing a
copy-constructor in R?
I know that all of this is easily done in C++. The constructor of each
class is called recursively back-up to the root class. Validity checks
can be performed after/during associated
initialization. Copy-constructor are basics in C++.
Here below is a piece of code that I thought would work (but it does
not... therefore my post), what's wrong with it?
I think the main issue is when is the validity check performed: why is
it performed before the end of the initialize method?
loosely, new("B", ...) calls initialize(prototypeOfB, ...).
initialize,B-method uses callNextMethod(), so initialize,A-method sees
as .Object the value prototypeOfB. If initialize,A-method is
well-behaved, it'll call initialize,ANY-method, which also sees
prototypeOfB. You'll see that, when the ... argument is not empty
getMethod(initialize, "ANY")
eventually calls validObject, in this case on prototypeOfB. Hence what
you are seeing, an 'early' check on the validity of B.
There are many creative ways around this in initialize,B-method, e.g.,
assigning B slots before callNextMethod(), or explicitly creating a
new instance of A from appropriate supplied arguments (in
initialize,B-method, name arguments meant to initialize B slots and
pass ... to the A constructor) and using that to initialize B, etc.
The approach I find most palatable (not meant to be real code) is to
have a constructor
B <- function(x, y, z, ...) {
# do all the work to map x, y, z into slots of A, B (or an
# instance of A and slots of B), then...
new("B", a=, b=, ...) # or new("B", instanceOfA, b=, ...)
}
and avoid writing explicit initialize methods.
Oddly enough, this solution leads to a copy constructor, viz.,
initialize(instanceOfB, b=)
I'm not sure that this really does anything more than move the
'pattern' from the initialize method to the constructor.
Martin
Thank you for your help.
Renaud
# define class A with a single numeric slot
setClass('A', representation(a='numeric'))
# define class B that extends class A, adding another numeric slot
setClass('B', representation('A', b='numeric'))
# we want for example to ensure that slots a and b have the same length
setValidity('B',
function(object){
cat("*** B::validate ***\n")
print(object)
cat("*****************\n")
if( length(obj...@a) != length(obj...@b) ) return('Inconsistent lengths')
TRUE
}
)
# As a default behaviour if b is not provided, we want slot b to be
equal to slot a
setMethod('initialize', 'B',
function(.Object, b, ...){
cat("*** B::initialize ***\n")
print(.Object)
# Let the superclass (A) initialize itself via callNextMethod
# I thought it would only do that: initialize and optionnaly validate
the class A part of the object
#But it generates an ERROR: apparently it calls the validation method of B,
# before leaving me a chance to set slot b to a valid value
.Object <- callNextMethod(.Object, ...)
# now deal with the class B part of the object
cat("*** Test missing b ***\n")
if( missing(b) ){
cat("*** b is MISSING ***\n")
b <- .obj...@a
}
# set slot b
.obj...@b <- b
.Object
}
)
### Testing
# empty A: OK
aObj <- new('A')
aObj
# class A with some data: OK
aObj <- new('A', a=c(1,2) )
aObj
# empty B: OK
bObj <- new('B')
bObj
# initialize B setting the slot of class A: ERROR
bObj <- new('B', a=c(1,2))
# initialize B setting only the slot class B: OK!! Whereas it produces
a non valid object.
bObj <- new('B', b=c(1,2))
bObj
######### RESULTS:
> # empty A: OK
> aObj <- new('A')
> aObj
An object of class “A”
Slot "a":
numeric(0)
>
> # class A with some data: OK
> aObj <- new('A', a=c(1,2) )
> aObj
An object of class “A”
Slot "a":
[1] 1 2
>
> # empty B: OK
> bObj <- new('B')
*** B::initialize ***
An object of class “B”
Slot "b":
numeric(0)
Slot "a":
numeric(0)
*** Test missing b ***
*** b is MISSING ***
> bObj
An object of class “B”
Slot "b":
numeric(0)
Slot "a":
numeric(0)
>
> # initialize B setting the slot of class A: ERROR
> bObj <- new('B', a=c(1,2))
*** B::initialize ***
An object of class “B”
Slot "b":
numeric(0)
Slot "a":
numeric(0)
*** B::validate ***
An object of class “B”
Slot "b":
numeric(0)
Slot "a":
[1] 1 2
*****************
Error in validObject(.Object) :
invalid class "B" object: Inconsistent lengths
>
> # initialize B setting only the slot class B: OK!! Whereas it
creates a non valid object.
> bObj <- new('B', b=c(1,2))
*** B::initialize ***
An object of class “B”
Slot "b":
numeric(0)
Slot "a":
numeric(0)
*** Test missing b ***
> bObj
An object of class “B”
Slot "b":
[1] 1 2
Slot "a":
numeric(0)
-----------------------------
> sessionInfo()
R version 2.9.1 (2009-06-26)
x86_64-pc-linux-gnu
locale:
LC_CTYPE=en_ZA.UTF-8;LC_NUMERIC=C;LC_TIME=en_ZA.UTF-8;LC_COLLATE=en_ZA.UTF-8;LC_MONETARY=C;LC_MESSAGES=en_ZA.UTF-8;LC_PAPER=en_ZA.UTF-8;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=en_ZA.UTF-8;LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices datasets utils methods base
other attached packages:
[1] Biobase_2.4.1
______________________________________________
R-help@r-project.org mailing list
https://stat.ethz.ch/mailman/listinfo/r-help
PLEASE do read the posting guide http://www.R-project.org/posting-guide.html
and provide commented, minimal, self-contained, reproducible code.