[R-pkg-devel] Is a double dispatch possible with two S3 classes?

Leonardo Silvestri lsilvestri at ztsdb.org
Fri Feb 24 03:59:26 CET 2017


Jens,

Thanks for this detailed alternative. It's neat and works as you 
describe, but it does fall a little short of what S4 dispatch can do. In 
particular if a second package besides 'bar', say 'baz', needs to define 
operations 'foo + baz' or 'baz' + 'foo' it can't define 'baz::+.foobar' 
without breaking 'bar::+.foobar'...

Leo


On 02/22/2017 01:44 PM, "Jens Oehlschlägel" wrote:
> Leonardo,
>
> I had a similar problem when I tried to implement reasonable behaviour for logical operators for classes .bit and .bitwhich in package 'bit'.
>
> Part of your problem is that R refuses to dispatch to any of two incompatible classes and instead falls back to a meaningless default.
>
> In theory, *this* problem *should* be solved in S3 by having a common super-class 'foobar' for class(foo) <- c('foo','foobar') and  class(bar) <- c('bar','foobar'), but again, R does refuses to dispatch on the common super-class (it directly tries to dispatch on the most special subclasses it finds and does not check for a common class in the class hierarchy).
>
> However, it appears that by having a common sub-class 'foobar' for class(foo) <- c('foobar','foo') and  class(bar) <- c('foobar','bar') we can get a common dispatch and hence get control. I plan for my next release of package 'bit' a common subclass 'booltype' to which R can dispatch without conflict. If you can convince the author of 'foo' to use class(foo) <- c('foobar','foo') and base his dispatch on 'foo', then you can dispatch on 'foobar' with no conflict and use NextMethod to invoke his code for the foo-foo case, and your code for the other cases. If the author refuses for some reason, you can still provide a wrapper to convert class 'foo' into class c('foobar','foo'), or do more nasty things like patching his class generator, but at least you don't need to patch his methods!
>
> I admit, using a physical subclass as a logical superclass is ugly: instead of declaring 'apples' and 'oranges' as special cases of 'fruits', we tell R that 'a fruit is an apple' and that 'a fruit is an orange', such that R dispatches to fruit-code for any combination of apples and oranges. Well ... the good thing is that NextMethod can invoke the original apple-code or orange-code, and we only need to write apple-orange-code and orange-apple-code to handle the new combinations.
>
> @R-devel: any comments or suggestions?
>
> HTH
>
>
> Jens Oehlschlägel
>
>
> P.S. Example code and output follows
>
>
> # If we try to have 'foo' and 'bar' as specializations of of a superclass 'foobar'
>
> f <- "fooobj"
> oldClass(f) <- c('foo','foobar')
> "+.foo" <- function(e1,e2){
>   paste(e1,'+foo+',e2)
> }
>
> b <- "barobj"
> oldClass(b) <- c('bar','foobar')
> "+.bar" <- function(e1,e2){
>   paste(e1,'+bar+',e2)
> }
>
> "+.foobar" <- function(e1,e2){
>   if (inherits(e1,"foo") && inherits(e2,"bar"))
>     paste(e1,'+foobar+',e2)
>   else if (inherits(e1,"bar") && inherits(e2,"foo"))
>     paste(e1,'+barfoo+',e2)
>   else stop("should not be invoked for foo+foo or bar+bar (but it does)")
> }
>
> # Yes, this works
> f + f
> b + b
> # but here R does not even dispatch on the common superclass 'foobar'
> f + b
> b + f
>
>
> # However, it seems that we can trick R to do the right thing pretending that 'foobar' is a subclass of both, 'foo' and 'bar'
>
> oldClass(f) <- c('foobar','foo')
> "+.foo" <- function(e1,e2){
>   paste(e1,'+foo+',e2)
> }
>
> b <- "barobj"
> oldClass(b) <- c('foobar','bar')
> "+.bar" <- function(e1,e2){
>   paste(e1,'+bar+',e2)
> }
>
> "+.foobar" <- function(e1,e2){
>   if (inherits(e1,"foo") && inherits(e2,"bar"))
>     paste(e1,'+foobar+',e2)
>   else if (inherits(e1,"bar") && inherits(e2,"foo"))
>     paste(e1,'+barfoo+',e2)
>   else NextMethod(e1,e2)  # make sure we dispatch to foo+foo and bar+bar
> }
>
> # This works
> f + f
> b + b
> # but R does not even dispatch on the common ground 'foobar'
> f + b
> b + f
>
>
>
>> # If we try to have 'foo' and 'bar' as specializations of of a superclass 'foobar'
>>
>> f <- "fooobj"
>> oldClass(f) <- c('foo','foobar')
>> "+.foo" <- function(e1,e2){
> +   paste(e1,'+foo+',e2)
> + }
>>
>> b <- "barobj"
>> oldClass(b) <- c('bar','foobar')
>> "+.bar" <- function(e1,e2){
> +   paste(e1,'+bar+',e2)
> + }
>>
>> "+.foobar" <- function(e1,e2){
> +   if (inherits(e1,"foo") && inherits(e2,"bar"))
> +     paste(e1,'+foobar+',e2)
> +   else if (inherits(e1,"bar") && inherits(e2,"foo"))
> +     paste(e1,'+barfoo+',e2)
> +   else stop("should not be invoked for foo+foo or bar+bar (but it does)")
> + }
>>
>> # Yes, this works
>> f + f
> [1] "fooobj +foo+ fooobj"
>> b + b
> [1] "barobj +bar+ barobj"
>> # but here R does not even dispatch on the common superclass 'foobar'
>> f + b
> Fehler in f + b : nicht-numerisches Argument für binären Operator
> Zusätzlich: Warnmeldung:
> Inkompatible Methoden ("+.foo", "+.bar") für "+"
>> b + f
> Fehler in b + f : nicht-numerisches Argument für binären Operator
> Zusätzlich: Warnmeldung:
> Inkompatible Methoden ("+.bar", "+.foo") für "+"
>>
>>
>> # However, it seems that we can trick R to do the right thing pretending that 'foobar' is a subclass of both, 'foo' and 'bar'
>>
>> oldClass(f) <- c('foobar','foo')
>> "+.foo" <- function(e1,e2){
> +   paste(e1,'+foo+',e2)
> + }
>>
>> b <- "barobj"
>> oldClass(b) <- c('foobar','bar')
>> "+.bar" <- function(e1,e2){
> +   paste(e1,'+bar+',e2)
> + }
>>
>> "+.foobar" <- function(e1,e2){
> +   if (inherits(e1,"foo") && inherits(e2,"bar"))
> +     paste(e1,'+foobar+',e2)
> +   else if (inherits(e1,"bar") && inherits(e2,"foo"))
> +     paste(e1,'+barfoo+',e2)
> +   else NextMethod(e1,e2)  # make sure we dispatch to foo+foo and bar+bar
> + }
>>
>> # This works
>> f + f
> [1] "fooobj +foo+ fooobj"
>> b + b
> [1] "barobj +bar+ barobj"
>> # but R does not even dispatch on the common ground 'foobar'
>> f + b
> [1] "fooobj +foobar+ barobj"
>> b + f
> [1] "barobj +barfoo+ fooobj"
>
>
>
> Gesendet: Donnerstag, 16. Februar 2017 um 17:52 Uhr
> Von: "Leonardo Silvestri" <lsilvestri at ztsdb.org>
> An: r-package-devel at r-project.org
> Betreff: [R-pkg-devel] Is a double dispatch possible with two S3 classes?
> Hello,
>
> I have stumbled upon an S3 class dispatch issue which is easily solved
> using S4 classes, but I'd like to know if there is a way to solve it
> without changing the type of class used.
>
> The problem is as follows. There is an S3 class 'foo' which is defined
> in a package over which I don't have control. Then there is an S3 class
> 'bar' derived from 'foo' in a package that can be modified.
>
> Here is some code for 'foo':
>
> as.foo <- function(x) {
> oldClass(x) <- "foo"
> x
> }
>
> print.foo <- function(x, ...) print(paste(x, "foo"))
>
> "-.foo" <- function(e1, e2) "-.foo was called"
>
>
> And here is some code for 'bar':
>
> as.bar <- function(x) {
> oldClass(x) <- c("bar", "foo")
> x
> }
>
> print.bar <- function(x, ...) print(paste(x, "bar"))
>
>
> Now the '-' operator must be defined in such a way that the behaviour is
> different depending on the operand classes, and in particular
> 'bar'-'bar' needs to be different from 'bar'-'foo'. If I define the
> following function:
>
> "-.bar" <- function(e1, e2) "-.bar was called"
>
> then I get the following results.
>
> as.foo(1) - as.foo(2) # uses '-.foo'
> as.bar(1) - as.bar(2) # uses '-.bar'
> as.bar(1) - as.foo(2) # uses '-.default' and issues
> # Incompatible methods warning
> as.foo(1) - as.bar(2) # uses '-.default' and issues
> # Incompatible methods warning
>
> So that seems like a dead end.
>
> If, overcoming an instinctive shudder of disgust, I redefine '-.foo' in
> the "bar" package, I have checked that correctness is at the mercy of
> the order of loading of the packages, even though the "bar" package
> imports the "foo" package. So that doesn't seem to work either.
>
> Before committing to making 'bar' an S4 class, does anyone know if there
> is another option that would allow 'bar' to remain an S3 class?
>
> Thanks,
> Leo
>
> ______________________________________________
> R-package-devel at r-project.org mailing list
> https://stat.ethz.ch/mailman/listinfo/r-package-devel
>



More information about the R-package-devel mailing list