S4 initialize methods, unexpected recursive callNextMethod

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

S4 initialize methods, unexpected recursive callNextMethod

Seth Falcon-2
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

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 initialize methods, unexpected recursive callNextMethod

John Chambers-2
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
>
>______________________________________________
>[hidden email] mailing list
>https://stat.ethz.ch/mailman/listinfo/r-devel
>
>  
>

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 initialize methods, unexpected recursive callNextMethod

Seth Falcon-2
John Chambers <[hidden email]> writes:

> 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.

Thanks for the explanation and work around suggestion.  If I'm feeling
brave, perhaps I'll take a look at the addNextMethod and
callNextMethod computations.  We found this bug for a "real" use-case,
so I have some interest in a solution that doesn't involve changing
the initializers for the three classes involved...

Best,

+ seth

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: S4 initialize methods, unexpected recursive callNextMethod

John Chambers-2
In reply to this post by Seth Falcon-2
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
>
>______________________________________________
>[hidden email] mailing list
>https://stat.ethz.ch/mailman/listinfo/r-devel
>
>  
>

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel