Search code examples
roperator-overloadingr-s3

S3 operator overloading for multiple classes


I defined two classes that can successfully add two of their own objects or a number and one of their own objects.

a <- structure(list(val = 1), class = 'customClass1')
b <- structure(list(val = 1), class = 'customClass2')
`+.customClass1` <- function(e1, e2, ...){
  val1 <- ifelse(is.numeric(e1), e1, e1$val)
  val2 <- ifelse(is.numeric(e2), e2, e2$val)
  val_res <- val1  + val2
  print('customClass1')
  return(structure(list(val = val_res), class = 'customClass1'))
}
`+.customClass2` <- function(e1, e2, ...){
  val1 <- ifelse(is.numeric(e1), e1, e1$val)
  val2 <- ifelse(is.numeric(e2), e2, e2$val)
  val_res <- val1  + val2
  print('customClass2')
  return(structure(list(val = val_res), class = 'customClass1'))
}
print.customClass1 <- function(x, ...){
  print(x$val)
}
print.customClass2 <- function(x, ...){
  print(x$val)
}
a + a
# [1] 2
a + 1
# [1] 2
b + b
# [1] 2
1 + b
# [1] 2

But obviously, it goes wrong when I try to add the two custom classes.

a + b
# Error in a + b : non-numeric argument to binary operator
# In addition: Warning message:
# Incompatible methods ("+.customClass1", "+.customClass2") for "+" 

I could define just one function for customClass1, but then that function would not work when I try to add two customClass2 objects. Is there any way to prioritize one function over the other?

R seems to do this naturally by prioritizing my functions over the base functions (e.g. of the type numeric or integer). When one of both arguments has the customClass type, R automatically redirects it to my function instead of the default function.


Solution

  • How R chooses which method to dispatch is discussed in the Details section of ?base::Ops

    The classes of both arguments are considered in dispatching any member of this group. For each argument its vector of classes is examined to see if there is a matching specific (preferred) or 'Ops' method. 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.

    If customClass1 and customClass2 are related, you can use a virtual class to allow operations using the two different classes. For example, you can mix POSIXct and POSIXlt because they both inherit from POSIXt. This is documented in ?DateTimeClasses:

    "POSIXct" is more convenient for including in data frames, and "POSIXlt" is closer to human-readable forms. A virtual class "POSIXt" exists from which both of the classes inherit: it is used to allow operations such as subtraction to mix the two

    For example:

    class(pct <- Sys.time())
    # [1] "POSIXct" "POSIXt"
    Sys.sleep(1)
    class(plt <- as.POSIXlt(Sys.time()))
    # [1] "POSIXlt" "POSIXt"
    plt - pct
    # Time difference of 1.001677 secs
    

    If the classes aren't related in this way, there's some good information in the answers to Emulating multiple dispatch using S3 for “+” method - possible?.