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?
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).
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With