[Rd] [R] S4 method dispatch matrixOrArray (fwd)

Gabor Grothendieck ggrothendieck at gmail.com
Fri Apr 14 16:02:59 CEST 2006


Right. That does not solve the whole problem (as I pointed out) but
was just a suggestion intended to be reasonably
consistent with how it works now.  The best would be if the search
order were a function only of the relationships among the classes
and not dependent on the setMethod order.

On 4/14/06, John Chambers <jmc at r-project.org> wrote:
> Gabor Grothendieck wrote:
> I think a rule is needed that would require that a class not succeed any
> of
its parents on the method list. That way "array" could never come
after
> "vector" and, in general, a parent would never be found prior
to any of its
> descendents.

>
> A good idea & it would help, we can certainly do something like this
> (probably for 2.4, not 2.3).  But so long as the inheritance search is
> greedy, a "wrong" method can still be chosen.
>
> Suppose, say, class A contains B and C, and C contains D.  Function foo has
> methods for B and D.  We would agree, I think, that foo(a) where a is from
> class A should get the B method (parent) rather than the D method
> (grandparent).  But classess B and D are unrelated so there's no obvious
> ordering.
>
> My preference would be for a full search.  The ordering you suggest would at
> least help avoid some branches of the search.  If we can manage at some
> point to re-implement important parts of the methods package,  in C and more
> built-in, that should save much more computing time than the full search
> would add.
>
> Of course that still does not address the problem that the order
of the
> setMethod's rather than the relationships among the classes
can influence
> the search order but it would eliminate the situation
we saw here.

On
> 4/13/06, John Chambers <jmc at r-project.org> wrote:

> What a delicious example! (I'm taking the liberty of sharing it
> with
r-devel, since it raises some good issues.)

You have two questions,
> presumably:

1 - how could the order of the setMethod calls make a
> difference in the
results?
2 - what's causing the infinite loop & how could
> it be avoided, reliably?

Second question first. The danger sign is the
> "vector" method:


setMethod("foo",
 signature(A = "vector"),
 function(A,
> ...) {
 foo(matrix(A, nrow = 1), ...)
 })

This handles a vector by
> recalling the generic with a matrix. But "vector"
is a superclass of
> "matrix" (see getClass("matrix")) and you DON'T have an
explicit method
> defined for "matrix". So for sure if "vector" was the only
method, you would
> be in immediate trouble. Coercing down the inheritance
tree is potentially
> bad news.

Generally, if you're going to take over dispatch by coercing an
> argument &
then recalling the generic, the result is simplest if you have an
> exact
match for the new call, not relying on inheritance. After all, if
> you're
doing the work to coerce the argument, might as well pick one that
> works
right away. Not a universal rule, but other things being equal
> ...

There's an additional issue with methods for "matrix" and "array"
> because R
never allows 2-way arrays to have class "array", which means using
> array()
here with the same dimensions would not have helped. Also, matrix
> and array
objects are weird in that they are not basic vectors but also have
> no class
attribute, and is.object() is FALSE for them.

More interesting
> though--how can the order of the setMethod() calls matter?
To see that we
> need to look at the methods list object. (The discussion is
simplified to
> the case that only one argument is involved, which doesn't
affect the
> result.)

The MethodsList object has a slot containing a named list of
> methods, with
the names being those of the classes that appeared in the
> setMethod calls,
in the order that the calls occurred(note!). All classes
> are essentially
equal from the view of the generic function, so there's no
> ordering favoring
the "more relevant".

When method dispatch occurs, the
> code first looks for an exact match to the
class of the actual
> argument--that's a quick search in the names of the
list.

If the direct
> search fails, the code now looks for an inherited method. The
key point is
> that this second search is "greedy"--the first inherited method
found is
> used.

NOW it should be clear why the order of the setMethod() calls
> matters. You
have two potential inherited methods here for "matrix"; namely,
> "array" and
"vector". WE know that the "array" method is `closer', and the R
> dispatcher
could decide this also, if it were willing to look through all
> possible
inheritance paths and drop one possibility if a better one was
> found.

It currently doesn't do any further search, and doing so would be a
> modest
efficiency hit. I'm inclined to think the cost would be worth it
> to
eliminate unpleasant suprises like this one, but opinions may differ.
> (Once
found, the inherited method is stored directly in the list used for
> the
first lookup, so the hit is only the first time a particular
> signature
turns up.)

To make the difference clearer, I added to your
> example another generic
"bar" with the same methods as "foo", but with the
> order of the setMethod
calls reversed.
By looking at the "methods" slot in
> the two cases, we can see why the bad
("vector") method is selected for
> bar() but not for foo():


> names(getMethods("foo")@methods)

> [1] "array" "vector"

> names(getMethods("bar")@methods)

> [1] "vector" "array"

After running foo(1:10) and trying to run bar(1:10):

> showMethods("foo")

> Function "foo":
 A = "array"
 A = "vector"
 A = "integer"
 (inherited from A
> = "vector")
 A = "matrix"
 (inherited from A = "array")

> showMethods("bar")

> Function "bar":
 A = "vector"
 A = "array"
 A = "integer"
 (inherited from A
> = "vector")
 A = "matrix"
 (inherited from A = "vector")

But including
> setMethod("bar", "matrix", ...) in the source code makes bar()
work
> fine.



Paul Roebuck wrote:
Sorry to bother but could you shed some light
> on this?

> I don't understand

> why order of setMethod calls makes

> any difference. Since it obviously does,

> it has shaken

> the foundations of what I thought I understood about
S4

> methods. Even Gabor was surprised...

>
---------- Forwarded message

> ----------

> Date: Wed, 12 Apr 2006 18:24:46 -0400
From: Gabor Grothendieck

> <ggrothendieck at gmail.com>
To: Paul Roebuck <roebuck at mdanderson.org> Cc: R
> Help Mailing List
<r-help at stat.math.ethz.ch> Subject: Re: [R] S4 method
> dispatch matrixOrArray
On 4/12/06, Paul Roebuck <roebuck at mdanderson.org>
> wrote:


On Wed, 12 Apr 2006, Gabor Grothendieck wrote:

>

> On 4/12/06, Paul Roebuck <roebuck at mdanderson.org> wrote:

>

> I have some code where the primary dispatching is on

> other parameters so I'd

> like not to have to create a

> set of functions for "matrix" and another

> duplicate

> set for "array". But the class union technique isn't
working as

> implemented below and I don't have my Green

> book with me. How do I fix my

> infinite recursion
problem?

>
##--------------------------------------------------------
library(methods)

setGeneric("foo",
> function(A, ...) {

>  cat("generic", match.call()[[1]], "\n")

> standardGeneric("foo")

>  })

setMethod("foo",
 signature(A = "vector"),

> function(A, ...) {

>  callGeneric(matrix(A, nrow = 1), ...)

> })

> setClassUnion("matrixOrArray", c("matrix", "array"))

setMethod("foo",

> signature(A = "matrixOrArray"),

>  function(A, ...) {
 cat("A =", A, "\n")

> })

> ## Test
foo(1:4)
foo(matrix(1:4, 1, 4))
foo(array(1:4, c(1, 4, 1)))


> I think its good enough to just define an array method, i.e. you

> don't need

> the matrix method or the matrixOrArray class, and the

> vector method can call

> foo(matrix(A,1), ...) so:

> setGeneric("foo",
 function(A, ...) {

> cat("generic", match.call()[[1]], "\n")

>  standardGeneric("foo")

> })

> setMethod("foo",
 signature(A = "array"),
 function(A, ...) {
 cat("A

> =", A, "\n")

>  })

setMethod("foo",
 signature(A = "vector"),
 function(A,

> ...) {

>  foo(matrix(A, nrow = 1), ...)
 })


> Something didn't seem right here. That was pretty close

> to what I had

> started with, before trying to go the

> classUnion route. Matter of fact, the

> vector method can

> retain use of callGeneric.

The solution has to do with

> the order in which calls to

> setMethod are made. Adding foo-vector after

> foo-array

> works fine; the other way around causes infinite recursion.


> This is surprising. I would have thought that the

> parent/child relationships

> determine the order that

> dispatched methods are invoked, not the order

> that

> the setMethod commands are issued in.




> ______________________________________________
R-devel at r-project.org
> mailing
> list
https://stat.ethz.ch/mailman/listinfo/r-devel


>



More information about the R-devel mailing list