From regetz at nceas.ucsb.edu  Fri Feb 29 08:43:44 2008
From: regetz at nceas.ucsb.edu (Jim Regetz)
Date: Thu, 28 Feb 2008 23:43:44 -0800
Subject: [Rd] Problems initializing an extended S4 class
Message-ID: <47C7B7B0.6070406@nceas.ucsb.edu>

Hi all,

I am having trouble extending S4 classes in cases where I'm using both 
validity and initialize methods. I've read as much S4 information as I 
can find, but I've yet to have that "a-ha" moment.

In my application, I am using validity methods to guard against 
inappropriate input data that my code has no way of handling, and 
initialize methods to detect and deal with fixable problems. As a toy 
example, consider classes A and B and associated methods as defined 
below. I use a validity method for A to complain about negative values, 
and an initialize method to "correct" small input values. B should 
simply extend A by adding an extra slot. The example is contrived, but 
it illustrates a key behavior that I also encounter in my real code:

setClass("A", representation(x="numeric"))
setClass("B", representation(y="character"), contains="A")

setValidity("A", function(object) {
     message("start validate A")
     retval <- NULL
     if (any(object at x<0))
         retval <- c(retval, "x must be positive")
     message("done validate A")
     if(is.null(retval)) return(TRUE) else return(retval)
})

setMethod("initialize", "A", function(.Object, ...) {
     message("start init A")
     .Object <- callNextMethod()
     x <- .Object at x
     .Object at x <- ifelse(log(x)<0, 1, x)
     message("done init A")
     .Object
})

setMethod("initialize", "B", function(.Object, ...) {
     message("start init B")
     callNextMethod()
})


# Creating an instance of A works just as I would expect
> a <- new("A", x=c(0.5, 2))
start init A
start validate A
done validate A
done init A
> a
An object of class ?A?
Slot "x":
[1] 1 2

# But subsequently creating a derived B object fails!
> new("B", a, y="foo")
start init B
start init A
start validate A
start init A
Error in checkSlotAssignment(object, name, value) :
   assignment of an object of class "logical" is not valid for slot "x"
in an object of class "A"; is(value, "numeric") is not TRUE

The two things I haven't quite figured out are:

1. Why is initialize invoked *twice* for A during instantiation of B?

2. The second time initialize is invoked for A, it appears .Object at x is 
only bound to its implicit prototype value of numeric(0). Why? Of 
course, this leads to an error because the ifelse expression 
subsequently evaluates to logical(0) rather than a numeric vector as 
intended. Again, this is a contrived example, but a very real problem in 
my code.

I suppose I could define a prototype for A that I know won't break my 
initialize method, but that seems inelegant and hard to maintain. Is 
there a better way to code this so that I can reliably instantiate B 
using a valid A object? Hopefully I've just got something wrong in the 
formals for my initialize methods or in my use of getNextMethod(), but 
I've had no luck trying some alternatives -- and ultimately I'd prefer 
to better understand the underlying behavior rather than stumble onto 
something that merely appears to work.

I'd be grateful for any suggestions...

Thanks,
Jim

------------------------------
James Regetz, Ph.D.
Scientific Programmer/Analyst
National Center for Ecological Analysis & Synthesis
735 State St, Suite 300
Santa Barbara, CA 93101



[Rd] Problems initializing an extended S4 class
Jim Regetz