[R] How to: initialize, setValidity, copy-constructor

Renaud Gaujoux renaud at mancala.cbio.uct.ac.za
Fri Jul 10 16:09:19 CEST 2009


Nice,
I'll see which methods suits better my use.
It's just that I was expecting the initialize-callNextMethod-validity 
workflow to work automatically in such a situation.

Thanks so much Martin.

Martin Morgan wrote:
> Renaud Gaujoux wrote:
>   
>> Hi,
>>
>> The technique of writing constructors prevent from using simply the
>> 'new' function (which I find nice as it takes the class to construct as
>> a character string (useful in my project) and provides a single
>> interface for object creation).
>> So doesn't the built-in schema of initialize-validate automatic calls
>> make itself unusable in practice (my use-case is quite standard)?
>>     
>
> No; for instance the Bioconductor project has hundreds of packages many
> of which use S4, and Matrix has an extensive S4 class hierarchy.
>
> For your scenario either of the following initialize methods work, and
> differ depending on how much you want to assume about the underlying
> class. There are likely many other solutions, too.
>
> ## pattern: create valid slots before callNextMethod
> setMethod('initialize', 'B',
>           function(.Object, ..., a=numeric(), b=a) {
>               callNextMethod(.Object, ..., a=a, b=b)
>           })
>
> ## pattern: instantiate base class before callNextMethod
> setMethod('initialize', 'B',
>           function(.Object, ..., b) {
>               aObj <- new("A", ...)
>               if (missing(b))
>                   b <- aObj at a
>               callNextMethod(.Object, aObj, b=b)
>           })
>
> The tricky part of your use case is the inter-class constraint implied
> by slot b having the same dimensions as slot a.
>
> Martin
>
>   
>> Renaud
>>
>> Martin Morgan wrote:
>>     
>>> Hi Renaud --
>>>
>>> Renaud Gaujoux <renaud at 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(object at a) != length(object at 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 <- .Object at a
>>>> }
>>>>
>>>> # set slot b
>>>> .Object at 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 at 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.
>>>>     
>>>>         
>>>   
>>>       
>
>




More information about the R-help mailing list