[R] Understanding S4 method dispatch

Hervé Pagès hpages at fhcrc.org
Wed Aug 14 19:42:28 CEST 2013


Hi Zehnder,

You're right that the fact that B already inherits from A is probably
part of the story but it's not all the story:

   setClass("A", "NULL")
   setClass("B", "A")
   setMethod("show", "A", function(object) cat("A object\n"))
   setMethod("show", "B", function(object) cat("B object\n"))

   setClass("C", "B")
   setClass("AC", contains = c("A", "C"))
   ac <- new("AC")

Then:

   > ac
   A object

So even if B already inherits from A, A seems to be considered "closer"
to AC than B is. This is confirmed by showClass():

   > showClass("AC")
   Class "AC" [in ".GlobalEnv"]

   Slots:

   Name:  .xData
   Class:   NULL

   Extends:
   Class "C", directly
   Class "A", directly
   Class "B", by class "C", distance 2
   Class ".NULL", by class "A", distance 2
   Class "NULL", by class "A", distance 3, with explicit coerce
   Class "OptionalFunction", by class "A", distance 4, with explicit coerce
   Class "optionalMethod", by class "A", distance 4, with explicit coerce

and also reflected by is():

   > is(ac)
   [1] "AC"               "C"                "A"                "B" 

   [5] ".NULL"            "NULL"             "OptionalFunction" 
"optionalMethod"

There seems to be an attempt at ordering the superclasses of a given
class (performed when the class is defined): first by distance, then,
by looking at whether one class involved in the tie inherits from the
other. Like in Hadley's example:

   setClass("AB", contains = c("A", "B"))
   ab <- new("AB")

Then:

   > ab
   B object

showClass("AB") reports that distance(AB, A) = distance(AB, B) so
there is a tie. However, this tie can be disambiguated because
B inherits from A. This disambiguation is reflected by the output
of is():

   > is(ab)
   [1] "AB"               "B"                "A"                ".NULL" 

   [5] "NULL"             "OptionalFunction" "optionalMethod"

Unfortunately ?Methods is not particularly clear about all this.
You need to read it between the lines.

Note that ordering of superclasses is well defined in terms of distance
only (with no ties ever) if you don't use multiple inheritance. So this
is one more reason to stay away from multiple inheritance (just in case
you were still not convinced ;-) )

Cheers,
H.


On 08/14/2013 03:51 AM, Simon Zehnder wrote:
> Ambiguity is indeed detected by R and the user is informed on it. But in the case of Hadley's example, I still believe, that the specific multiple inheritance structure creates this behavior. If you call:
>
> showMethods("f")
> Function: f (package .GlobalEnv)
> x="A", y="A"
> x="AB", y="AB"
>      (inherited from: x="B", y="B")
> x="B", y="B"
>
> you see, that AB inherited the method from B. As B inherits already from A and AB does as well, AB would have two A objects, the one from its inheritance from A and the one from B. In the case of direct inheritance, the only slot is the .xData slot, which is NULL. If one class contains another one, it contains all its slots. As the .xData slot is the same for each class A, B and AB, R must remove the ambiguity of slots, this is done by allowing only one slot .xData, which is the one from B. So in some way A is hidden by B in AB - but this is actually a common technique in OOP. In C++ for example we have the same problem and we can define which members should be inherited if multiple inheritance is aware, by using the keyword 'virtual'.
>
> Towards Hervé's second point:
>
> If we build a class C:
>
> setClass("C", contains = c("A"))
> setMethod("f", signature("C", "C"), function(x, y) "C-C")
>
> And then construct a multiple inheritance structure within a class BC:
>
> setClass("BC", contains = c("B", "C"))
> bc <- new("BC")
>
> we see, that indeed the first lexicographical signature is chosen:
>
> showMethods("f")
> Function: f (package .GlobalEnv)
> x="A", y="A"
> x="B", y="B"
> x="BC", y="BC"
>      (inherited from: x="B", y="B")
> x="C", y="C"
>
> f(bc, bc)
> [1] "B-B"
>
> In this case, we have a 'true' tie in the inheritance structure: B from A and C from A, any one of the two As have to be taken into the BC object and the one from B is chosen. But method dispatch is in this case independent of the second level inheritance. B and C are not related directly to each other, so method dispatch is chosen lexicographically.
>
> In my opinion the reason for the behavior lies in the specific multiple inheritance structure between AB, B and A.
>
> Best
>
> Simon
>
>
>
>
> On Aug 14, 2013, at 2:11 AM, Hervé Pagès <hpages at fhcrc.org> wrote:
>
>> Hi Hadley,
>>
>> I suspect that the dispatch algorithm doesn't realize that selection
>> is ambiguous in your example. For 2 reasons:
>>
>>   (1) When it does realize it, it notifies the user:
>>
>>         setClass("A", "NULL")
>>         setGeneric("f", function(x, y) standardGeneric("f"))
>>         setMethod("f", signature("A", "ANY"), function(x, y) "A-ANY")
>>         setMethod("f", signature("ANY", "A"), function(x, y) "ANY-A")
>>         a <- new("A")
>>
>>       Then:
>>
>>         > f(a, a)
>>         Note: method with signature ‘A#ANY’ chosen for function ‘f’,
>>          target signature ‘A#A’.
>>          "ANY#A" would also be valid
>>         [1] "A-ANY"
>>
>>   (2) When dispatch is ambiguous, the "first method lexicographically in
>>       the ordering" should be selected (according to ?Methods). So it
>>       should be A#A, not B#B.
>>
>> So it looks like a bug to me...
>>
>> Cheers,
>> H.
>>
>>
>> On 08/13/2013 06:08 AM, Hadley Wickham wrote:
>>> Hi all,
>>>
>>> Any insight into the code below would be appreciated - I don't
>>> understand why two methods which I think should have equal distance
>>> from the call don't.
>>>
>>> Thanks!
>>>
>>> Hadley
>>>
>>> # Create simple class hierarchy
>>> setClass("A", "NULL")
>>> setClass("B", "A")
>>>
>>> a <- new("A")
>>> b <- new("B")
>>>
>>> setGeneric("f", function(x, y) standardGeneric("f"))
>>> setMethod("f", signature("A", "A"), function(x, y) "A-A")
>>> setMethod("f", signature("B", "B"), function(x, y) "B-B")
>>>
>>> # These work as I expect
>>> f(a, a)
>>> f(b, b)
>>>
>>> setClass("AB", contains = c("A", "B"))
>>> ab <- new("AB")
>>>
>>> # Why does this return B-B? Shouldn't both methods be an equal distance?
>>> f(ab, ab)
>>>
>>> # These both return distance 1, as I expected
>>> extends("AB", "A", fullInfo=TRUE)@distance
>>> extends("AB", "B", fullInfo=TRUE)@distance
>>> # So why is signature("B", "B") closer than signature("A", "A")
>>>
>>
>> --
>> Hervé Pagès
>>
>> Program in Computational Biology
>> Division of Public Health Sciences
>> Fred Hutchinson Cancer Research Center
>> 1100 Fairview Ave. N, M1-B514
>> P.O. Box 19024
>> Seattle, WA 98109-1024
>>
>> E-mail: hpages at fhcrc.org
>> Phone:  (206) 667-5791
>> Fax:    (206) 667-1319
>>
>> ______________________________________________
>> R-help at r-project.org mailing list
>> https://stat.ethz.ch/mailman/listinfo/r-help
>> PLEASE do read the posting guide http://www.R-project.org/posting-guide.html
>> and provide commented, minimal, self-contained, reproducible code.
>

-- 
Hervé Pagès

Program in Computational Biology
Division of Public Health Sciences
Fred Hutchinson Cancer Research Center
1100 Fairview Ave. N, M1-B514
P.O. Box 19024
Seattle, WA 98109-1024

E-mail: hpages at fhcrc.org
Phone:  (206) 667-5791
Fax:    (206) 667-1319



More information about the R-help mailing list