Skip to content

Problems initializing an extended S4 class

2 messages · Martin Morgan, Jim Regetz

#
Hi Jim --

I think your problems have to do with the way 'initialize' and
'validObject' work together. At some point in it's code, validObject
converts an instance 'b' of a derived class (e.g., B) into that of the
base class (e.g., A). It does this, by calling a = new("A"), and then
copying the relevant slots from the instance of b to new a. So...
...the initialize method for A gets invoked twice, once when B is
being created originally, and once when B is being checked for
validity and is being converted to A.
...and this is because of how A is being constructed from B -- with a
called to new("A"), with no additional arguments.

Practically, this means that your classes have to be valid when
created with new("A"). This imposes constraints on the prototype,and
on the initialize method, and in the end I've found myself writing
'constructors' that process user-friendly arguments into a format that
can be passed to 'new' as slots, e.g.,

A <- function(x=numeric(0), ...) {
    x <- ifelse(log(x)<0, 1, x)
    new("A", x=x, ...)
}

In some ways I think this is the right thing to do anyway (a layer of
abstraction between the user and implementation), even if the
motivation might not be so pure.

Martin


Jim Regetz <regetz at nceas.ucsb.edu> writes:

  
    
1 day later
#
Hi Martin,

Many thanks for the reply, that really helps a lot. Some follow-up 
observations below.
Martin Morgan wrote:
With your comments as context, I stepped more carefully through relevant 
source code in the methods package. As you indicated, my trouble arises 
in the 'validObject' function. In particular, it's when 'as' is called 
to coerce the nascent B object into its superclass A. [For reference, 
it's in line 456 of the SClasses.R in the R-2.6.2 source tarball.]

This led me to try defining a simple B -> A coercion method:
setAs("B", "A", function(from, to) new(to, x=from at x))

Indeed, after doing this, I can successfully create an instance of B.

It still seems peculiar that the initialize and validity methods are 
both called twice for A when B is instantiated -- particularly given 
that the 'a' object itself was already initialized and checked for 
validity. There may well be a good reason for all this, but I still 
wonder if I'm doing something inefficiently.

In any case, although I haven't tested it, I'm optimistic that defining 
'coerce' methods in this fashion will also solve the problem in my real 
code without my needing to redesign the initialize and validity methods.
Agreed, and I actually do have such user-level constructors. However, 
precisely in the interests of abstraction, I'd prefer not to expose the 
innards of my initialize methods in these functions. I admit this is 
partly on purity grounds, but more practical issues are (1) I may have 
several variants of the user-level constructors and don't want to repeat 
the common initialization code in each one, and (2) the operations I 
have in my initialize methods are only sensible for valid objects (and 
thus not meaningfully executed before 'new' is called). Hopefully this 
is all in the true spirit of the S4 object system...

Thanks again for the clear and helpful reply!

Cheers,
Jim