From mtmorgan at fhcrc.org  Mon Mar  5 04:48:54 2007
From: mtmorgan at fhcrc.org (Martin Morgan)
Date: Sun, 04 Mar 2007 19:48:54 -0800
Subject: [Rd] Problem using callNextMethod() in S4
In-Reply-To: <45EB1541.2020308@aon.at> (cstrato@aon.at's message of "Sun, 04
	Mar 2007 19:51:45 +0100")
References: <45EAF340.50707@aon.at> <m2odn8apol.fsf@ziti.local>
	<45EB1541.2020308@aon.at>
Message-ID: <6phwt1wcpuh.fsf@gopher4.fhcrc.org>

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

cstrato <cstrato at aon.at> writes:

> Dear Seth
>
> Thank you for your comments. Please see my comments and at the end my 
> corrected code and output.
> Sorrowly, the problem remains the same.
>
> Seth Falcon wrote:
>> cstrato <cstrato at aon.at> writes:
>>
>>   
>>> Dear all,
>>>
>>> Maybe, I am doing something wrong, but using R-2.5.0 on my Intel-Mac, I 
>>> have problems
>>> using function callNextMethod() in method initialize.
>>>
>>> I am loading the following code as file "testS4.R":
>>>     
>>
>> I don't think this is the code in the same state as that which you ran
>> the examples.  Did you add/remove some comment lines perhaps?
>>
>> After copy/pasting the code you posted, I get:
>>
>>     > tmp<-new("derivedClass")
>>     [1] "---initialize:derivedClass---"
>>     [1] "mytitle =  MyTitle"
>>     > tmp<-new("derivedClass",myname="testname",mytitle="testitle")
>>     [1] "---initialize:derivedClass---"
>>     [1] "mytitle =  MyTitle"
>>
>>   
> I am sorry, you are correct, I have commented out ".Object <- 
> callNextMethod()" in method
> initialize for derivedClass afterwards.
>>> setValidity("baseClass",
>>>    function(object) {
>>> print("---setValidity:baseClass---")
>>>       strg <- object at myname;
>>>       if (!(is(strg, "character") && nchar(strg) > 0)) {
>>>          warning(paste(sQuote("myname"), "is missing"));
>>>       }#if
>>> print(paste("myname = ",object at myname))
>>>       strg <- object at mydir;
>>>       if (!(is(strg, "character") && file.exists(strg))) {
>>>          warning(paste(sQuote("mydir"), "is not a system directory"));
>>>       }#if
>>>       if (substr(strg, nchar(strg), nchar(strg)) == "/") {
>>>          object at mydir <- substr(strg, 0, nchar(strg)-1);
>>>       }#if
>>> print(paste("mydir = ",object at mydir))
>>>    }
>>> )#setValidity
>>>     
>>
>> Your validity function isn't valid :-P It should either return TRUE or
>> return a character vector describing what isn't valid about the
>> object.  Don't call warning() or print().
>>   
> Please see my corrected code where I use "validMsg()" from BioBase.
>> Also, you don't need those ';'
>>
>> And finally, you are operating on a _copy_ in the validity method
>> (just like everywhere else) and so this
>>
>>   
>>>       if (substr(strg, nchar(strg), nchar(strg)) == "/") {
>>>          object at mydir <- substr(strg, 0, nchar(strg)-1);
>>>       }#if
>>>     
>>
>> will not have any effect on the instance passed in.  It is an odd
>> thing to do in a validity method.
>>   
> You are right, I moved this code to method "initialize".
>> + seth
>>
>>   
> Here is my new code "testS4.R" (as used in the output):
>
> setClass("baseClass",
>    representation(myname = "character",
>                   mydir  = "character",
>                   "VIRTUAL"),
>    prototype(myname = "",
>              mydir  = "")
> )#baseClass
>
> setClass("derivedClass",
>    representation(mytitle = "character"),
>    contains=c("baseClass"),
>    prototype(mytitle = "")
> )#derivedClass
>
> # taken from package BioBase: tools.R
> validMsg <- function(msg, result) {
>    if (is.character(result)) {
>       append(msg, result);
>    } else {
>       msg;
>    }#if
> }
>
> 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
>
> setValidity("baseClass",
>    function(object) {
> print("---setValidity:baseClass---")
>       msg <- NULL;
>       strg <- object at myname;
>       if (!(is(strg, "character") && nchar(strg) > 0)) {
>          msg <- validMsg(msg, paste(sQuote("myname"), "is missing"));
>       }#if
> print(paste("base:myname = ",object at myname))
>       strg <- object at mydir;
>       if (!(is(strg, "character") && file.exists(strg))) {
>          msg <- validMsg(msg, paste(sQuote("mydir"), "is not a system 
> directory"));
>       }#if
> print(paste("base:mydir = ",object at mydir))
>       if (is.null(msg)) TRUE else msg;
>    }
> )#setValidity
>
> setMethod("initialize", "derivedClass",
>    function(.Object, ...) {
> print("---initialize:derivedClass---")
> #    .Object <- callNextMethod();
>       if (.Object at mytitle == "") {
>          .Object at mytitle = "MyTitle";
>       }#if
> print(paste("derived:mytitle = ",.Object at mytitle))
>     .Object <- callNextMethod();
>     .Object;
>    }
> )#initialize
>
> setValidity("derivedClass",
>    function(object) {
> print("---setValidity:derivedClass---")
>       msg <- NULL;
>       strg <- object at mytitle;
>       if (!(is(strg, "character") && nchar(strg) > 0)) {
>          msg <- validMsg(msg, paste(sQuote("mytitle"), "is missing"));
>       }#if
> print(paste("derived:mytitle = ",object at mytitle))
>       if (is.null(msg)) TRUE else msg;
>    }
> )#setValidity
>
>
> Here is the new output with the same error:
>
>  > library(methods)
>  > source("testS4.R")
>  > tmp<-new("derivedClass")
> [1] "---initialize:derivedClass---"
> [1] "derived:mytitle =  MyTitle"
> [1] "---initialize:baseClass---"
> [1] "base:strg =  "
> [1] "base:mydir =  /Volumes/CoreData/CRAN/Workspaces/tests"
>  >
>  > tmp<-new("derivedClass",myname="testname",mydir="",mytitle="testitle")
> [1] "---initialize:derivedClass---"
> [1] "derived:mytitle =  MyTitle"
> [1] "---initialize:baseClass---"
> [1] "base:strg =  "
> [1] "base:mydir =  /Volumes/CoreData/CRAN/Workspaces/tests"
> [1] "---setValidity:baseClass---"
> [1] "base:myname =  testname"
> [1] "base:mydir =  "
> Error in validObject(.Object) : invalid class "derivedClass" object: 
> 'mydir' is not a system directory
>
> I do not understand why "mydir" is not recognized correctly?
>
> Thank you
> Christian
>
> ______________________________________________
> R-devel at r-project.org mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel

-- 
Martin Morgan
Bioconductor / Computational Biology
http://bioconductor.org



[Rd] Problem using callNextMethod() in S4
Martin Morgan