emulating multiple dispatch using S3 for “+” method - possible?

喜夏-厌秋 提交于 2019-12-04 11:50:10

问题


I have two classes (a and b) and I want to define the + method for them. I need different methods for the four possible combinations of the two classes, i.e.:

a + a  method 1
a + b  method 2
b + a  method 3
b + b  method 4

I know I could use S4 for multiple dispatch, but I want to know if there is a way to emulate this behaviour using S3. My approach was the following:

a <- "b"
class(a) <- "a"

b <- "e"
class(b) <- "b"

Ops.a <- function(e1, e2){
  if (class(e1) == "a" &
      class(e2) == "a")
    print("a & a")
  if (class(e1) == "a" &
        class(e2) == "b")
    print("a & b")
  if (class(e1) == "b" &
        class(e2) == "a")
    print("b & a")
  NULL
}

a + a
a + b
b + a

All this works fine, but of course the following is not defined.

b + b

Now to cover this case I added another method definition.

Ops.b <- function(e1, e2){
  if (class(e1) == "b" &
        class(e2) == "b")
    print("b & b")
  NULL
}

This will cause b + b to work but now a + b and b + a methods are inconsistent and will cause and error.

> a + b
error in a + b : non-numeric argument for binary operator
additional: warning:
incompatible methods ("Ops.a", "Ops.b") for "+"

Is there a way to define all four cases properly using S3?


回答1:


You can do it by defining +.a and +.b as the same function. For example:

a <- "a"
class(a) <- "a"
b <- "b"
class(b) <- "b"

`+.a` <- function(e1, e2){
  paste(class(e1), "+", class(e2))
}
`+.b` <- `+.a`

a+a
# [1] "a + a"
a+b
# [1] "a + b"
b+a
# [1] "b + a"
b+b
# [1] "b + b"

# Other operators won't work
a-a
# Error in a - a : non-numeric argument to binary operator

If you define Ops.a and Ops.b, it will also define the operation for other operators, which can be accessed by .Generic in the function:

##### Start a new R session so that previous stuff doesn't interfere ####
a <- "a"
class(a) <- "a"
b <- "b"
class(b) <- "b"

Ops.a <- function(e1, e2){
  paste(class(e1), .Generic, class(e2))
}

Ops.b <- Ops.a

a+a
# [1] "a + a"
a+b
# [1] "a + b"
b+a
# [1] "b + a"
b+b
# [1] "b + b"


# Ops covers other operators besides +
a-a
# [1] "a - a"
a*b
# [1] "a * b"
b/b
# [1] "b / b"

Update: one more thing I discovered while playing with this. If you put this in a package, you'll get the "non-numeric argument" error and "incompatible operators" warning. This is because R is only OK with the multiple operators if they are exactly the same object, with the same address in memory -- but somehow in the building and loading of a package, the two functions lose this exact identity. (You can check this by using pryr::address())

One thing I've found that works is to explicitly register the S3 methods when the package is loaded. For example, this would go inside your package:

# Shows the classes of the two objects that are passed in
showclasses <- function(e1, e2) {
  paste(class(e1), "+", class(e2))
}    

.onLoad <- function(libname, pkgname) {
  registerS3method("+", "a", showclasses)
  registerS3method("+", "b", showclasses)
}

In this case, the two methods point to the exact same object in memory, and it works (though it's a bit of a hack).




回答2:


Well you cannot use that strategy. It is specifically prohibited as you discovered and documented as so in the help(Ops) page.

"If a method is found for just one argument or the same method is found for both, it is used. If different methods are found, there is a warning about ‘incompatible methods’: in that case or if no method is found for either argument the internal method is used."

So you would need to put all cases into the same method. (Tested and does succeed.)




回答3:


What about just calling the operator with the arguments reversed?

Ops.b <- function(e1, e2){
  if (class(e1) == "b" &
        class(e2) == "b")
    print("b & b")
  if (class(e1) =="b" & class(e2)=="a")
    e2+e1
  NULL
}

But I'd strongly suggest using proper multiple dispatch and thus S4 for this. See Combining S4 and S3 methods in a single function and Adding S4 dispatch to base R S3 generic .



来源:https://stackoverflow.com/questions/13659449/emulating-multiple-dispatch-using-s3-for-method-possible

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!