From cstrato at aon.at  Mon Mar  5 21:41:36 2007
From: cstrato at aon.at (cstrato)
Date: Mon, 05 Mar 2007 21:41:36 +0100
Subject: [Rd] Problem using callNextMethod() in S4
In-Reply-To: <6phwt1wcpuh.fsf@gopher4.fhcrc.org>
References: <45EAF340.50707@aon.at>
	<m2odn8apol.fsf@ziti.local>	<45EB1541.2020308@aon.at>
	<6phwt1wcpuh.fsf@gopher4.fhcrc.org>
Message-ID: <45EC8080.80707@aon.at>

Dear Martin

Thank you for this extensive explanation!
I hope that I have understood it, but I will realize it as I proceed with my
package. Now, my example works as I have expected it to work, great!

One problem that I face is lack of extensive explanation of S4 classes,
especially with respect to inheritance.  (Neither the tutorial "S4 Classes
in 15 pages" nor the book "S Programming" are really helpful.) Most
of the time I study the code in BioBase and affy, since there are not
many packages available using S4 and inheritance.
Your explanation below is a good example, how a good S4 tutorial
should cover these issues.

Best regards
Christian

Martin Morgan wrote:
> In this method...
>
> setMethod("initialize", "baseClass",
>    function(.Object, ...) {
> print("---initialize:baseClass---")
> #    .Object <- callNextMethod();
>       strg <- .Object at mydir;
> print(paste("base:strg = ", strg))
>       if (strg == "") {
>          .Object at mydir <- as.character(getwd());
>       }#if
>       if (substr(strg, nchar(strg), nchar(strg)) == "/") {
>          .Object at mydir <- substr(strg, 0, nchar(strg)-1);
>       }#if
> print(paste("base:mydir = ", .Object at mydir))
>     .Object <- callNextMethod();
>     .Object;
>    }
> )#initialize
>
> the argument '...' includes the argument mydir="". Later, when you
> .Object <- callNextMethod(), it invokes the 'next' method with the
> same argument, i.e., with mydir="". This causes the 'mydir' slot to be
> initialized with "", triggering the validity error. You can see this
> more clearly in the following, where the provided argument x=10:1
> overrides the assignment in initialize:
>
>   
>> setClass("A", representation=representation(x="numeric"))
>>     
> [1] "A"
>   
>> setMethod("initialize", "A",
>>     
> +           function(.Object, ...) {
> +               .Object at x <- 1:10
> +               callNextMethod()
> +           })
> [1] "initialize"
>
>   
>> new("A", x=10:1)
>>     
> An object of class "A"
> Slot "x":
>  [1] 10  9  8  7  6  5  4  3  2  1
>
> One solution is to name any arguments you're going to manipulate in
> the initialize method, and then make sure the correct arguments are
> passed to callNextMethod. You'll probably want to provide a sensible
> default argument to mydir, so that user doesn't have to do anything
> clever (like remember to pass "") to get the default behavior. Here's
> what I end up with:
>
> setMethod("initialize", "baseClass",
>           function(.Object, mydir=as.character(getwd()), ...) {
>               if (substr(mydir, nchar(mydir), nchar(mydir)) == "/") {
>                   mydir <- substr(mydir, 0, nchar(mydir)-1)
>               }
>               callNextMethod(.Object, mydir=mydir, ...);
>           })
>
> setMethod("initialize", "derivedClass",
>           function(.Object, mytitle="MyTitle", ...) {
>               callNextMethod(.Object, mytitle=mytitle, ...)
>           })
>
> Another solution is to follow the convention where callNextMethod()
> comes first (constructing a valid object!), and your initialize method
> then fills in slots with the appropriate values.
>
> One interesting part of your example is that new('derivedClass') does
> NOT cause a validity error, even though the object is invalid
> ('myname' is ""; also, none of your validity method messages are
> printed)! Apparently, the assumption is that you (the programmer, as
> opposed to the user) are not going to create an invalid object by
> default.
>
> Also, take a look at the initialize method that R has constructed for
> derivedClass:
>
>   
>> getMethod("initialize", "derivedClass")
>>     
> Method Definition:
>
> function (.Object, ...) 
> {
>     .local <- function (.Object, mytitle = "MyTitle", ...) 
>     {
>         callNextMethod(.Object, mytitle = mytitle, ...)
>     }
>     .local(.Object, ...)
> }
>
> Signatures:
>         .Object       
> target  "derivedClass"
> defined "derivedClass"
>
> Notice how the function is defined in terms of .Object and .... The
> named arguments not present in the generic signature (i.e., 'mytitle')
> are 'hidden' in the .local function definition. By the time
> callNextMethod() has been evaluated, '...' does NOT include
> 'mytitle'. I think this is why you must explicitly include any named
> arguments you want to pass to callNextMethod -- the default is to
> callNextMethod with the generic signature, but with symbols (.Object,
> ...) taking their current value. Here's a simpler illustration:
>
> setClass("A", representation=representation(x="numeric"))
> setMethod("initialize", "A",
>           function(.Object, x, ...) callNextMethod())
>
> This leads to the perhaps unexpected outcome
>
>   
>> new("A", x=10:1)
>>     
> An object of class "A"
> Slot "x":
> numeric(0)
>
> I say unexpected because, if there was no initialize method, or if the
> initialize method were written without 'x' in the signature, then the
> argument 'x' would be used to fill the slot:x.
>
> Here's the solution like that for baseClass, above:
>
> setMethod("initialize", "A",
>           function(.Object, x, ...)
>           callNextMethod(.Object, x=x, ...))
>
> ...which leads to
>
>   
>> new("A", x=10:1)
>>     
> An object of class "A"
> Slot "x":
>  [1] 10  9  8  7  6  5  4  3  2  1
>
> Hope that helps,
>
> Martin
>
>



[Rd] Problem using callNextMethod() in S4
cstrato