Seth Falcon
2006-May-11 00:16 UTC
[Rd] S4 initialize methods, unexpected recursive callNextMethod
Hi, Given a simple three class hierarchy: A <-- B <-- C I want to define an initialize method for each class such that when I call new("C", x=5), the initialize methods for A and B are used to incrementally build the object. When I do what seems obvious to me using callNextMethod, I get an infinite recursion. An example follows... setClass("A", representation(a="numeric")) setClass("B", representation(b="numeric"), contains="A") setClass("C", representation(c="numeric"), contains="B") setMethod("initialize", signature(.Object="A"), function(.Object, x) { cat("in A\n") .Object at a <- x .Object }) setMethod("initialize", signature(.Object="B"), function(.Object, x) { cat("in B\n") .Object <- callNextMethod(.Object=.Object, x=x) .Object at b <- .Object at a + 1 .Object }) setMethod("initialize", signature(.Object="C"), function(.Object, x) { cat("in C\n") .Object <- callNextMethod(.Object=.Object, x=x) .Object at c <- .Object at a + .Object at b + 1 .Object }) ## Works as I am expecting for B> myB <- new("B", 4)in B in A ## When you create a C, the B method gets called but the appropriate ## next method info seems lost and we end up back in C's method ?!> myC <- new("C", 5)in C in B in C in B in C C-c C-c Should this work? Is there a better way to organize the initializers for a simple hierarchy (perhaps assume that args to the initializers are not the same for A, B, and C). Thanks, + seth
John Chambers
2006-May-11 15:51 UTC
[Rd] S4 initialize methods, unexpected recursive callNextMethod
It's a bug resulting from the combination of: 1. multiple recursive levels of callNextMethod() 2. nonstandard arguments in the method definition; that is, (.Object, x) vs .Object, ...) for the generic. Specifically, the callNextMethod code tries to build up a list of "excluded" classes, but in this example seems to fail. So the nextMethod selected for class "B", when it goes to find the next^2 method, forgets to exclude class "C"; hence it goes back to the "C" method & so infinite recursion. To see what's happening, use trace(callNextMethod, recover) or equivalent. The computation in addNextMethod, callNextMethod, etc. is all very heuristic (aka "kludgey") code, so it's not clear how easy a fix would be. The desirable route would be a better treatment of nonstandard arguments, but that may require digging deeper into the R evaluator than I've had the nerve to do so far. Meanwhile, a work around is to avoid mechanism 2 if you want to use mechanism 1. This may require separating out the guts of each method from the callNextMethod() part, with that part coming in a method definiton with standard arguments. With the alternative definition below, all is well (if not very useful): > new("C", a=1,b=2,c=3) in C in B in A An object of class "C" Slot "c": [1] 3 Slot "b": [1] 2 Slot "a": [1] 1 using the methods: setMethod("initialize", signature(.Object="A"), function(.Object, ...) { cat("in A\n") callNextMethod() }) setMethod("initialize", signature(.Object="B"), function(.Object, ...) { cat("in B\n") callNextMethod() }) setMethod("initialize", signature(.Object="C"), function(.Object, ...) { cat("in C\n") callNextMethod() }) Seth Falcon wrote:>Hi, > >Given a simple three class hierarchy: A <-- B <-- C > >I want to define an initialize method for each class such that when I >call new("C", x=5), the initialize methods for A and B are used to >incrementally build the object. > >When I do what seems obvious to me using callNextMethod, I get an >infinite recursion. An example follows... > >setClass("A", representation(a="numeric")) >setClass("B", representation(b="numeric"), contains="A") >setClass("C", representation(c="numeric"), contains="B") > >setMethod("initialize", signature(.Object="A"), > function(.Object, x) { > cat("in A\n") > .Object@a <- x > .Object > }) > >setMethod("initialize", signature(.Object="B"), > function(.Object, x) { > cat("in B\n") > .Object <- callNextMethod(.Object=.Object, x=x) > .Object@b <- .Object@a + 1 > .Object > }) > >setMethod("initialize", signature(.Object="C"), > function(.Object, x) { > cat("in C\n") > .Object <- callNextMethod(.Object=.Object, x=x) > .Object@c <- .Object@a + .Object@b + 1 > .Object > }) > > >## Works as I am expecting for B > > >>myB <- new("B", 4) >> >> >in B >in A > >## When you create a C, the B method gets called but the appropriate >## next method info seems lost and we end up back in C's method ?! > > >>myC <- new("C", 5) >> >> >in C >in B >in C >in B >in C > C-c C-c > >Should this work? Is there a better way to organize the initializers >for a simple hierarchy (perhaps assume that args to the initializers >are not the same for A, B, and C). > >Thanks, > >+ seth > >______________________________________________ >R-devel@r-project.org mailing list >https://stat.ethz.ch/mailman/listinfo/r-devel > > >[[alternative HTML version deleted]]
John Chambers
2006-May-13 13:59 UTC
[Rd] S4 initialize methods, unexpected recursive callNextMethod
The bug should now be fixed. The actual change is very small, but it enforces a semantic definition that may be relevant, so we should discuss it. The question is: what method does callNextMethod() refer to? The definition now enforced is: the "next method" is the method found for the defining signature (the signature in the call to setMethod()) if that method itself was absent. See the Details section of the documentation for callNextMethod in the latest development version. In previous versions, it was possible to use callNextMethod() to get at different inherited methods, depending on the instance, that is on the class of the particular object(s) in the call, while going through the _same_ method containing the callNextMethod(). I think the current semantics is cleaner and more appropriate to a functional style of programming. It is also needed if the dispatch of the next method were to be "compiled in" to the method in a future version (which should, in fact, be easy to do). But watch for uses of callNextMethod() that now behave differently. Seth Falcon wrote:>Hi, > >Given a simple three class hierarchy: A <-- B <-- C > >I want to define an initialize method for each class such that when I >call new("C", x=5), the initialize methods for A and B are used to >incrementally build the object. > >When I do what seems obvious to me using callNextMethod, I get an >infinite recursion. An example follows... > >setClass("A", representation(a="numeric")) >setClass("B", representation(b="numeric"), contains="A") >setClass("C", representation(c="numeric"), contains="B") > >setMethod("initialize", signature(.Object="A"), > function(.Object, x) { > cat("in A\n") > .Object@a <- x > .Object > }) > >setMethod("initialize", signature(.Object="B"), > function(.Object, x) { > cat("in B\n") > .Object <- callNextMethod(.Object=.Object, x=x) > .Object@b <- .Object@a + 1 > .Object > }) > >setMethod("initialize", signature(.Object="C"), > function(.Object, x) { > cat("in C\n") > .Object <- callNextMethod(.Object=.Object, x=x) > .Object@c <- .Object@a + .Object@b + 1 > .Object > }) > > >## Works as I am expecting for B > > >>myB <- new("B", 4) >> >> >in B >in A > >## When you create a C, the B method gets called but the appropriate >## next method info seems lost and we end up back in C's method ?! > > >>myC <- new("C", 5) >> >> >in C >in B >in C >in B >in C > C-c C-c > >Should this work? Is there a better way to organize the initializers >for a simple hierarchy (perhaps assume that args to the initializers >are not the same for A, B, and C). > >Thanks, > >+ seth > >______________________________________________ >R-devel@r-project.org mailing list >https://stat.ethz.ch/mailman/listinfo/r-devel > > >[[alternative HTML version deleted]]