Dear all, I have a question regarding setIs and method dispatch in S4 classes: Consider the following example: ##################################################### ## A02 "contains" A01 by setIs: setClass("A01", representation(a="numeric",b="numeric")) setClass("A02", representation(a="numeric",d="numeric")) setIs("A02","A01",coerce=function(obj){new("A01", a=obj at a, b=obj at d)}, replace=function(obj,value){new("A01", a=value at a, b=value at b)}) #only a "+" method for A01 setMethod("+", signature=c("A01","A01"), function(e1,e2){e1 at b+e2@b}) x1=new("A02", a=1, d=2) x2=new("A02", a=1, d=3) x1+x2 ## 5 as expected ## now: B00 mother class to B01 and B02, and again B02 "contains" B01 by setIs: setClass("B00", representation(a="numeric")) setClass("B01", representation(a="numeric",b="numeric"), contains= "B00") setClass("B02", representation(a="numeric",d="numeric"), contains= "B00") setIs("B02","B01",coerce=function(obj){new("B01", a=obj at a, b=obj at d)}, replace=function(obj,value){new("B01", a=value at a, b=value at b)}) # now two "+" methods for B00 and B01 setMethod("+", signature=c("B00","B00"), function(e1,e2){e1 at a+e2@a}) setMethod("+", signature=c("B01","B01"), function(e1,e2){e1 at b+e2@b}) x1=new("B02", a=1, d=2) x2=new("B02", a=1, d=3) x1+x2 ## 2 --- why? ##################################################### (tested on 2.3.0 alpha (2006-04-05 r37653)) I would have guessed that this again would give 5, as B02 is deeper in the class hierarchy than B00 and the calls to setIs specifies an unconditional is-relation. What is the principle then behind this dispatching mechanism? Is there a possibility to force usage of the B01 method /without/ explicitely coercing x1,x2 to B01, i.e. interfere in the dispatching precedence, telling R somehow (by particular arguments for setIs ?) to always use the is-relation defined by setIs first before mounting the hierarchy tree? Thank you for your attention Peter
as a fan of S4 i had a look at this; more definitive replies are undoubtedly to come> ## now: B00 mother class to B01 and B02, and again B02 "contains" B01 by setIs: > setClass("B00", representation(a="numeric")) > setClass("B01", representation(a="numeric",b="numeric"), contains= "B00")the direct specification of containment in the next statement is important for the dispatch process> setClass("B02", representation(a="numeric",d="numeric"), contains= "B00")if we omit the containment assertion your expectations are met setClass("B02", representation(a="numeric",d="numeric")) setIs("B02","B01",coerce=function(obj){new("B01", a=obj at a, b=obj at d)}, replace=function(obj,value){new("B01", a=value at a, b=value at b)}) two diagnostic steps that are illuminating are getClass("B02") under both approaches (with and without contains="B00" in definition of B02 class) and getMethods("+") under both approaches
Hi Peter, Peter Ruckdeschel <Peter.Ruckdeschel at uni-bayreuth.de> writes:> ## now: B00 mother class to B01 and B02, and again B02 "contains" B01 by > setIs: > setClass("B00", representation(a="numeric")) > setClass("B01", representation(a="numeric",b="numeric"), contains= "B00") > setClass("B02", representation(a="numeric",d="numeric"), contains= "B00") > setIs("B02","B01",coerce=function(obj){new("B01", a=obj at a, b=obj at d)}, > replace=function(obj,value){new("B01", a=value at a, b=value at b)}) > > # now two "+" methods for B00 and B01 > setMethod("+", signature=c("B00","B00"), function(e1,e2){e1 at a+e2@a}) > setMethod("+", signature=c("B01","B01"), function(e1,e2){e1 at b+e2@b}) > > x1=new("B02", a=1, d=2) > x2=new("B02", a=1, d=3) > > x1+x2 ## 2 --- why?My impression from reading over the man page for setIs, is that it isn't intended to be used to override the existing inheritance hierarchy. It also mentions that the return value is the extension info as a list, so that could also be useful in understanding what setIs is doing. Here's the output for your example: Slots: Name: a d Class: numeric numeric Extends: Class "B00", directly Class "B01", directly, with explicit coerce Use the contains arg of setClass to define the superclasses. With the contains arg, the order determines the precedence for method lookup. But I suspect you know that already.> Is there a possibility to force usage of the B01 method /without/ > explicitely coercing x1,x2 to B01, i.e. interfere in the dispatching > precedence, telling R somehow (by particular arguments for setIs ?) > to always use the is-relation defined by setIs first before mounting > the hierarchy tree?Perhaps explaining a bit more about what you are trying to accomplish will allow someone to provide a more helpful suggestion than mine :-) If you know the inheritance structure you want before run-time, then I'm not seeing why you wouldn't just use the contains arg. And if you want to force certain behavior at run-time, then I don't see what's wrong with an explicit coercion using as(foo, "bar"). But perhaps I'm missing something. Cheers, + seth
Hi Seth , thank you for your reply. Seth Falcon <sfalcon at fhcrc.org> writes:>Peter Ruckdeschel <Peter.Ruckdeschel at uni-bayreuth.de> writes: > > >> ## now: B00 mother class to B01 and B02, and again B02 "contains" B01 by >> setIs: >> setClass("B00", representation(a="numeric")) >> setClass("B01", representation(a="numeric",b="numeric"), contains= "B00") >> setClass("B02", representation(a="numeric",d="numeric"), contains= "B00") >> setIs("B02","B01",coerce=function(obj){new("B01", a=obj at a, b=obj at d)}, >> replace=function(obj,value){new("B01", a=value at a, b=value at b)}) >> >> # now two "+" methods for B00 and B01 >> setMethod("+", signature=c("B00","B00"), function(e1,e2){e1 at a+e2@a}) >> setMethod("+", signature=c("B01","B01"), function(e1,e2){e1 at b+e2@b}) >> >> x1=new("B02", a=1, d=2) >> x2=new("B02", a=1, d=3) >> >> x1+x2 ## 2 --- why? >> >> > >My impression from reading over the man page for setIs, is that it >isn't intended to be used to override the existing inheritance >hierarchy. It also mentions that the return value is the extension >info as a list, so that could also be useful in understanding what >setIs is doing. Here's the output for your example: > > Slots: > > Name: a d > Class: numeric numeric > > Extends: > Class "B00", directly > Class "B01", directly, with explicit coerce > >Use the contains arg of setClass to define the superclasses. With the >contains arg, the order determines the precedence for method lookup. >But I suspect you know that already. > >Yes, I have been aware of this, thank you.>> Is there a possibility to force usage of the B01 method /without/ >> explicitely coercing x1,x2 to B01, i.e. interfere in the dispatching >> precedence, telling R somehow (by particular arguments for setIs ?) >> to always use the is-relation defined by setIs first before mounting >> the hierarchy tree? >> >> > Perhaps explaining a bit more about what you are trying to accomplish > will allow someone to provide a more helpful suggestion than mine :-)In the "real" context, B00 stands for a class "AbscontDistribution", which implements absolutely continuous (a.c.) distributions. B01 is class "Gammad" which implements Gamma distributions, and B02 is class "Exp" which implements exponential distributions. The method still is "+", but interpreted as convolution. For a.c. distributions, the default method is an FFT-based numerical convolution algorithm, while for Gamma distributions (with the same scale parameter), analytic, hence much more accurate convolution formulas are used. For "Exp", I would tell R that it also 'is' a "Gammad" distribution by a call to setIs and use the "Gammad"-method. Of course, I could also declare explicitly "+" methods for signatures c("Exp", "Exp"), c("Exp", "Gammad"), and c("Gammad", "Exp") in which I would then use as(.) to coerce "Exp" to "Gammad" (and again the same procedure for further Gamma-methods). But, this would create an extra (3 or possibly much more) methods to dispatch, and I doubt whether this really is the preferred solution.> If you know the inheritance structure you want before run-time, then > I'm not seeing why you wouldn't just use the contains argI do not want to use the "+" method for "B00" for accuracy reasons (see above). The reason why I do not want to implement "B01" ("Gammad") as mother class of "B02" is that (a) the slot structure is not identical --- in the real context Gamma and Exp use different parametrizations --- + rate for "Exp" (cf ?rexp) and + shape for "Gammad" (cf rgamma) (b) also class "Weibull" could be used as mother class to "Exp", and I do not want to decide whether the Weibull or the Gamma is the (more) "legitimate" mother to Exp ;-) I know: 'contains' could be a vector of classes --- c("Gammad", "Weibull") --- but then which would be the correct slot structure for "Exp" the one of "Gammad" or the one of "Weibull" ? My context is a bad example, "Gammad", "Weibull" do have the same slots, but more generally this /is/ an issue... --- So my guess was to rather implement two 'is'-relations ( "Exp" 'is' "Gammad" and "Exp" 'is' "Weibull") declared by 'setIs' , and then on run time let the dispatching mechanism decide whether to use a Gamma or a Weibull method. But maybe there is a better solution ? Any suggestions are welcome.> And if you want to force certain behavior at run-time, then I don't > see what's wrong with an explicit coercion using as(foo, "bar").If you have two objects E1, E2 of class "Exp" (with the same rate) you (or the user for whom we provide these classes) rather want to call "+" by E1 + E2 than by as(E1, "Gammad") + as(E2,"Gammad") ... Anyway, thank you for your help Peter
Hi Seth and John, Thank you for your helpful responses,>John Chambers <jmc at r-project.org> writes: >>From your description of the application, it sounds like you would be >>better off just forcing "+" to behave as you want. Using inheritance is >>a much more powerful mechanism & can introduce results you don't want, >>as it seems to have in this case. >> >>An important point about using inheritance is that the subclass is a >>asserted to be substitutable for the superclass for ALL purposes. This >>applies whether using "contains=" or setIs().I am not sure whether I got the meaning of "substitutable for the superclass for ALL purposes" : In the application I sketched, any Exp(rate = lambda) distribution really /is/ a Gammad(shape = 1, scale = 1/lambda) distribution; so my understanding is that "Exp" is substitutable for "Gammad" for ALL purposes. "Gammad" was not designed to be the motherclass to "Exp" right from the beginning because the same 'is'-relation also applies to "Weibull": any Exp(rate = lambda) distribution /is/ a Weibull(shape = 1, scale = 1/lambda) distribution. Does "substitutable for the superclass for ALL purposes" mean 'without ambiguity' (as might enter through Weibull/Gammad)?>>When the focus is on a particular function, it's usually better to >>implement methods for that function, maybe along with setAs() >>methods--not setIs().You mean I should not leave the coercion decision up to the dispatching mechanism?>>It seems likely that such a solution would be cleaner in design, not to >>mention that it would likely work. (see also suggestion below)Yes, your indication does work; thank you!>>Peter Ruckdeschel <peter.ruckdeschel at uni-bayreuth.de> writes: >>>Of course, I could also declare explicitly "+" methods for signatures >>>c("Exp", "Exp"), c("Exp", "Gammad"), and c("Gammad", "Exp") in >>>which I would then use as(.) to coerce "Exp" to "Gammad" >>> (and again the same procedure for further Gamma-methods). >>> >>>But, this would create an extra (3 or possibly much more) methods >>>to dispatch, and I doubt whether this really is the preferred >>>solution. >>> >> Why not?It simply did not seem to me elegant to have three calls to setMethod() doing more or less the same thing. I thought that, as elegant as R solutions from the R core are most times, there should be some mechanism to avoid this threefold code---and in fact you indicated how to--- thank you!>> And you can avoid some of the extra methods by defining a >> virtual class that is the union of the classes for which you >> want the new methods. >> >> Something like (untested code!) >> >> setClassUnion("analyticConvolution", c("Exp", "Gammad")) >> setMethod("+", c("analyticConvolution", "analyticConvolution"), >> ....)Seth Falcon <sfalcon at fhcrc.org> writes:> Why class union here and not an abstract superclass?Am I right: the class generated by setClassUnion() does not enter the inheritance tree / mechanism? setClassUnion()---at least in my case---solves the problem; thank you again. [snip] Peter