Is it possible to emulate multiple sending using S3 for the + method?

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

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

I know that I can use S4 for multiple sending, but I want to know if there is a way to emulate this behavior using S3. My approach was as follows:

 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 great, but, of course, is undefined.

 b + b 

Now, to cover this case, I have 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 the methods a + b and b + a are incompatible and will cause errors.

 > 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 correctly identify all four cases using S3?

+6
source share
3 answers

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

 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 aa # Error in a - a : non-numeric argument to binary operator 

If you define Ops.a and Ops.b , it will also define an operation for other operators that can be accessed via .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 + aa # [1] "a - a" a*b # [1] "a * b" b/b # [1] "b / b" 

Update : another thing I discovered while playing with this. If you put this in a package, you will get the warning "non-numeric argument" and the warning "incompatible operators". This is due to the fact that R works with only a few operators, if they are exactly the same object, with the same address in memory, but somehow in the building and loading the package, both functions lose this exact identification. (You can verify this using pryr::address() )

One thing I discovered is to explicitly register S3 methods when loading the package. For example, this will go into 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, two methods point to the same object in memory, and it works (although it is a bit hacked).

+9
source

Well, you cannot use this strategy. This is specifically forbidden because you discovered and documented it on the help page (Ops).

"If a method is found for only one argument or the same method is found for both, it is used. If different methods are found, a warning about incompatible methods appears: in this case, or if the method is not found for any argument, the internal method is used.

Thus, you will need to put all cases in the same method. (Tested and succeeds.)

+4
source

How about just invoking an operator with modified arguments?

 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 highly recommend using the correct multiple sending and thus S4 for this. See the combination of the S4 and S3 methods in one function as well as the addition of sending S4 to the basic R S3 common .

+1
source

All Articles