Re: [R] Understanding S4 method dispatch
And it doesn't even select "the first method lexicographically in the ordering" (whatever that means): setClass("B", "NULL") setClass("A", "NULL") setMethod("show", "B", function(object) cat("B object\n")) setMethod("show", "A", function(object) cat("A object\n")) setClass("AB", contains=c("B", "A")) ab <- new("AB") Then: > ab B object > is(ab) [1] "AB" "B""A"".NULL" [5] "NULL" "OptionalFunction" "optionalMethod" So the order of the superclasses passed to 'contains' seems to actually matter (even though I don't remember I've seen this documented anywhere). Is it correct to speculate that, if the distance is the same, and if the tie cannot be broken by looking at whether one class involved in the tie extends the other one, then the order in which the superclasses were passed to 'contains' is looked at? Last but not least, if you try to redefine class AB by switching A and B in 'contains' again, it seems to have no effect: > setClass("AB", contains=c("A", "B")) > ab B object except that now this contradicts is(): > is(ab) [1] "AB" "A""B"".NULL" [5] "NULL" "OptionalFunction" "optionalMethod" I'm just going to stop trying to sort this mess out :-/ H. On 08/14/2013 09:54 AM, Hadley Wickham wrote: On Wed, Aug 14, 2013 at 11:36 AM, Simon Zehnder wrote: Because the signature is always (A,A) or (B,B). Then, as in AB we have A and B and no relationship between A and B, R chooses the method lexicographically. The result is as expected: f for A is chosen. It's not as expected, because it doesn't give a message. From ?Methods: If there is no best match, the selection is ambiguous and a message is printed noting which method was selected (the first method lexicographically in the ordering) and what other methods could have been selected. Hadley -- 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: hpa...@fhcrc.org Phone: (206) 667-5791 Fax:(206) 667-1319 __ R-help@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.
Re: [R] Understanding S4 method dispatch
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 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
Re: [R] Understanding S4 method dispatch
On Wed, Aug 14, 2013 at 11:36 AM, Simon Zehnder wrote: > Because the signature is always (A,A) or (B,B). Then, as in AB we have A and > B and no relationship between A and B, R chooses the method > lexicographically. The result is as expected: f for A is chosen. It's not as expected, because it doesn't give a message. From ?Methods: If there is no best match, the selection is ambiguous and a message is printed noting which method was selected (the first method lexicographically in the ordering) and what other methods could have been selected. Hadley -- Chief Scientist, RStudio http://had.co.nz/ __ R-help@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.
Re: [R] Understanding S4 method dispatch
Because the signature is always (A,A) or (B,B). Then, as in AB we have A and B and no relationship between A and B, R chooses the method lexicographically. The result is as expected: f for A is chosen. If you would do something like: setClass("A", contains = "list") setClass("B", contains = "list") setClass("AB", contains = c("A", "B")) 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") setMethod("f", signature("B", "B"), function(x, y) "B-B") ab <- new("AB") f(ab, ab) You get an ambiguity, as there is no function with signature pair that can be directly matched for A. But for B such a signature pair exists. So R chooses B. If you would specify again a function for signature (A,A) we are back: setMethod("f", signature("A", "A"), function(x, y) "A-A") f(ab, ab) Best Simon On Aug 14, 2013, at 5:25 PM, Hadley Wickham wrote: >> In my opinion the reason for the behavior lies in the specific multiple >> inheritance structure between AB, B and A. > > So what if we don't make such a weird inheritance structure, and > instead have A and B inherit from a common parent: > > setClass("A", contains = "list") > setClass("B", contains = "list") > setClass("AB", contains = c("A", "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") > > ab <- new("AB") > f(ab, ab) > > Why isn't there a warning about ambiguous dispatch? > > Hadley > > -- > Chief Scientist, RStudio > http://had.co.nz/ __ R-help@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.
Re: [R] Understanding S4 method dispatch
> In my opinion the reason for the behavior lies in the specific multiple > inheritance structure between AB, B and A. So what if we don't make such a weird inheritance structure, and instead have A and B inherit from a common parent: setClass("A", contains = "list") setClass("B", contains = "list") setClass("AB", contains = c("A", "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") ab <- new("AB") f(ab, ab) Why isn't there a warning about ambiguous dispatch? Hadley -- Chief Scientist, RStudio http://had.co.nz/ __ R-help@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.
Re: [R] Understanding S4 method dispatch
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 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: hpa...@fhcrc.org > Phone: (206) 667-5791 > Fax:(206) 667-1319 > > __ > R-help@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. __ R-help@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.
Re: [R] Understanding S4 method dispatch
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: hpa...@fhcrc.org Phone: (206) 667-5791 Fax:(206) 667-1319 __ R-help@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.
Re: [R] Understanding S4 method dispatch
If you take an example which works with slots, setClass("A", representation(a = "numeric") setClass("B", contains = c("A"), representation(b = "numeric")) a <- new("A", a = 2) b <- new("B", a = 3, b = 2) setClass("AB", contains = c("A", "B")) new("AB", a = 2, b = 3) You see, that there is only one @a slot, the one inherited from B, that B inherits from A. If this were not the case, which slot should be taken, if we would call @a? To avoid this kind of ambiguity, only one A class is inherited to AB: the one B already inherits from A. You could create a class, that contains another A object in a slot: setClass("AandB", contains = c("B"), representation(A = "A")) new("AandB", a = 2, b = 3, A = new("A", a = 3)) Now back to your example: as there is only one A object inside the B object which is contained by the AB object, method dispatch works the way as it should: It looks for a method f for an AB object. It does not find one. Then it looks for a method f for the contained B object (as this is the only one contained in AB) and it finds a method. Then it calls this method on the B part of the object AB and the result is "B-B" Best Simon On Aug 13, 2013, at 4:24 PM, Hadley Wickham wrote: >> The class AB inherits from A and from B, but B already inherits from class >> A. So actually you only have an object of class B in your object of class >> AB. When you call the function f R looks for a method f for AB objects. It >> does not find such a method and looks for a method of the object inherited >> from, B. Such a method is present and is then executed. >> >> The inheritance structure has to be changed. The behavior is actually >> desired, as if this behavior weren't given a diamond class inheritance would >> be fatal. > > Are you sure? That behaviour doesn't agree with the description of > method dispatch given in ?Methods, not with getClass("AB") which shows > that AB inherits from both A and B. (I totally agree that this is a > bad idea, and unlikely to be useful in real life, but I'm trying to > understand the details of S4 dispatch) > >> getClass("AB") > Class "AB" [in ".GlobalEnv"] > > Slots: > > Name: .xData > Class: NULL > > Extends: > Class "B", directly > Class "A", directly > 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 > > -- > Chief Scientist, RStudio > http://had.co.nz/ __ R-help@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.
Re: [R] Understanding S4 method dispatch
> The class AB inherits from A and from B, but B already inherits from class A. > So actually you only have an object of class B in your object of class AB. > When you call the function f R looks for a method f for AB objects. It does > not find such a method and looks for a method of the object inherited from, > B. Such a method is present and is then executed. > > The inheritance structure has to be changed. The behavior is actually > desired, as if this behavior weren't given a diamond class inheritance would > be fatal. Are you sure? That behaviour doesn't agree with the description of method dispatch given in ?Methods, not with getClass("AB") which shows that AB inherits from both A and B. (I totally agree that this is a bad idea, and unlikely to be useful in real life, but I'm trying to understand the details of S4 dispatch) > getClass("AB") Class "AB" [in ".GlobalEnv"] Slots: Name: .xData Class: NULL Extends: Class "B", directly Class "A", directly 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 -- Chief Scientist, RStudio http://had.co.nz/ __ R-help@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.
Re: [R] Understanding S4 method dispatch
Hadley, The class AB inherits from A and from B, but B already inherits from class A. So actually you only have an object of class B in your object of class AB. When you call the function f R looks for a method f for AB objects. It does not find such a method and looks for a method of the object inherited from, B. Such a method is present and is then executed. The inheritance structure has to be changed. The behavior is actually desired, as if this behavior weren't given a diamond class inheritance would be fatal. Best Simon On Aug 13, 2013, at 3:08 PM, 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") > > -- > Chief Scientist, RStudio > http://had.co.nz/ > > __ > R-help@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. __ R-help@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.
[R] Understanding S4 method dispatch
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") -- Chief Scientist, RStudio http://had.co.nz/ __ R-help@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.