[Rd] Problem with S4 inheritance: unexpected re-initialization?
cstrato
cstrato at aon.at
Fri Apr 6 19:10:13 CEST 2007
Dear Herve, dear Martin,
First I want to thank Herve very much for your continuous efforts. You can
not imagine, how relieved I was that you mangaged to reproduce this
behavior.
Just when I wanted to send my reply to Herve, I received Martin's comments:
As you mention, there were two problems in my example:
1. partial matching:
Thank you for your explanation, now I hope that I understand the behavior.
However, as Herve correctly mentioned, this is only a side issue.
2. lazy evaluation and validity methods:
This is the real problem, that I try to solve since three weeks, and I am
afraid, that I still do not know how to solve it.
Following Herve's example, I have managed to reduce my own example so that
it is only slightly larger than his example, however, now I had to expand
it again to make my point, see the final code below.
(For the moment I stick with my naming of the classes)
The result I get is the following:
> subsubB2 <- new("SubSubClassB2", filename="MyFileNameB2",
nameB="MyNameB")
------initialize:SubSubClassB2------
------initialize:SubClassB------
------initialize:BaseClass------
BaseClass:init:filename = MyFileNameB2
------setValidity:BaseClass------
------initialize:SubClassB------
------initialize:BaseClass------
BaseClass:init:filename = ERROR_FileName
------setValidity:BaseClass------
------setValidity:SubClassB------
------setValidity:SubClassB------
------setValidity:SubSubClassB2------
SubSubClassB2:val:filename = MyFileNameB2
Although the final output gives the correct result "filename =
MyFileNameB2",
in the interim code, filename is set to "filename = ERROR_FileName".
In my real package the line "filename <- ERROR_FileName" is replaced by
a function. To take advantage of inheritance, I have defined "filename"
in BaseClass, and I want to check for its validity only once in BaseClass.
With the current information I have to re-evaluate my code in order to
avoid "lazy evaluation", since this might be the problem.
Thank you both for your extensive help.
Best regards
Christian
# - - - - - - - - - - - - - - - BEGIN - - - - - - - - - - - - - - - -
setClass("BaseClass",
representation(filename = "character", "VIRTUAL"),
prototype(filename = "DefaultFileName")
)
setClass("SubClassB",
representation(nameB = "character"),
contains=c("BaseClass"),
prototype(nameB = "NameB")
)
setClass("SubSubClassB2",
representation(nameB2 = "character"),
contains=c("SubClassB"),
prototype(nameB2 = "NameB2")
)
setMethod("initialize", "BaseClass",
function(.Object, filename="", ...) {
cat("------initialize:BaseClass------\n")
if (filename == "" || nchar(filename) == 0) filename <-
"ERROR_FileName"
cat("BaseClass:init:filename = ", filename, "\n", sep="")
.Object <- callNextMethod(.Object, filename=filename, ...)
.Object at filename <- filename
.Object
}
)
setValidity("BaseClass",
function(object) {
cat("------setValidity:BaseClass------\n")
msg <- NULL
str <- object at filename
if (!(is.character(object at filename)))
msg <- cat("missing filename\n")
if (is.null(msg)) TRUE else msg
}
)
setMethod("initialize", "SubClassB",
function(.Object, nameB="", ...) {
cat("------initialize:SubClassB------\n")
.Object <- callNextMethod(.Object, nameB=nameB, ...)
}
)
setValidity("SubClassB",
function(object) {
cat("------setValidity:SubClassB------\n")
TRUE
}
)
setMethod("initialize", "SubSubClassB2",
function(.Object, ...) {
cat("------initialize:SubSubClassB2------\n")
.Object <- callNextMethod(.Object, ...)
}
)
setValidity("SubSubClassB2",
function(object) {
cat("------setValidity:SubSubClassB2------\n")
cat("SubSubClassB2:val:filename = ", object at filename, "\n", sep="")
TRUE
}
)
# - - - - - - - - - - - - - - - END - - - - - - - - - - - - - - - - -
Martin Morgan wrote:
> Christian, Herve --
>
> In the end, I think you're being caught by partial matching in
> function arguments:
>
>
>> f <- function(x1) cat("x1:", x1, "\n")
>> f(x="hello")
>>
> x1: hello
>
> In the functional call x gets matched to x1. In your initialization
> method for SubSubClassB2, nameB gets matched to nameB2 and so not
> passed as ... in the callNextMethod.
>
> The apparently extra calls to initialize with the wrong arguments come
> about because of the validity methods -- validity gets checked by
> coercing derived objects to their superclass, which involves a call to
> 'new' (hence initialize) followed by copying appropriate slots.
>
> The funny effect where class(object) seems to trigger construction of
> a new object is lazy evaluation -- the 'object' argument to
> setValidity is not evaluated until needed, i.e., until class(object)
> (anything would trigger this, including force(object)); only then do
> you see the attempt to create the new object of the previous
> paragraph.
>
> Without partial matching issues, the use of callNextMethod as here:
>
> setMethod("initialize", "SubSubClassB2",
> function(.Object, nameB2="MyNameB2", ...) {
> if (nameB2 == "") nameB2 <- "DefaultNameB2";
> callNextMethod(.Object, nameB2=nameB2, ...)
> }
> )
>
> is fine (though weird to have a prototype, a named argument, and a
> conditional assignment!).
>
> With
>
> setMethod("initialize", "SubSubClassB2",
> function(.Object, ...) {
> .Object <- callNextMethod()
> if (.Object at nameB2 == "") .Object at nameB2 <- "DefaultNameB2"
> .Object
> }
> )
>
> as suggested by Herve, the "SubClassB" initializer matches the nameB
> argument to 'new' to the nameB argument of the initialize method for
> SubClassB.
>
> Simplifying the code to illustrate the problem was very helpful. It
> would have been better to not present the validity methods (this might
> have been a different thread), to remove the cat statements, to remove
> unnecessary slots and classes (SubSubClassB1) and to name classes,
> slots, and argument values in an easier to remember way (e.g., classes
> A, B, C, slots a, b, c)
>
> Hope that helps.
>
>
> Martin
>
>
>
More information about the R-devel
mailing list