Home > OS >  How to make Ops method compatible between two objects of a non base class
How to make Ops method compatible between two objects of a non base class

Time:04-08

Let's imagine that I have a class "my" and I want to trigger certain behaviour when it is added to an object that has units (i.e. from units package):

library(units)
my1 = structure(2, )

Ops.my <- function(e1, e2=NULL) {
  ok <-
    switch(
      .Generic,
      `-` = ,
      `*` = ,
      ` ` = ,
      '<=' = TRUE,
      FALSE
    )
  if (!ok) {
    stop(gettextf("%s not meaningful", sQuote(.Generic)))
  }
  get(.Generic)(as.integer(e1), as.integer(e2))
}

my1 set_units(5,nm)

Currently, it gives me the following warning:

Warning message:
Incompatible methods ("Ops.my", "Ops.units") for " " 

But I actually want to handle "my" and "units" addition in a certain way, how do I do it? I tried with something like Ops.my.units <- but it doesn't seem to work.

CodePudding user response:

There doesn't seem to be a way to do this with Ops. From the docs:

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’

This is probably a good thing. Part of the benefit of an object-oriented system in a non-compiled language like R is that it helps preserve type safety. This stops you from accidentally adding apples to oranges, as we can see in the following example:

apples  <- structure(2, class = "apples")

oranges <- structure(2, class = "oranges")

Ops.apples <- function(e1, e2) {
  value <- do.call(.Generic, list(as.integer(e1), as.integer(e2)))
  class(value) <- "apples"
  value
}

Ops.oranges <- function(e1, e2) {
  value <- do.call(.Generic, list(as.integer(e1), as.integer(e2)))
  class(value) <- "oranges"
  value
}

apples   apples
#> [1] 4
#> attr(,"class")
#> [1] "apples"

oranges   oranges
#> [1] 4
#> attr(,"class")
#> [1] "oranges"

apples   oranges
#> [1] 4
#> attr(,"class")
#> [1] "apples"
#> Warning message:
#>   Incompatible methods ("Ops.apples", "Ops.oranges") for " " 

You can see that even here, we could just ignore the warning.

suppressWarnings(apples   oranges)
#> [1] 4
#> attr(,"class")
#> [1] "apples"

But hopefully you can see why this may not be good - we have added 2 apples and 2 oranges, and have returned 4 apples.

Throughout R and its extension packages, there are numerous type-conversion functions such as as.integer, as.numeric, as.logical, as.character, as.difftime etc. These allow for some element of control when converting between types and performing operations on different types.

The "right" way to do this kind of thing is specifically convert one of the object types to the other in order to perform the operation:

as.my <- function(x) UseMethod("as.my")

as.my.default <- function(x) {
  value <- as.integer(x)
  class(value) <- 'my'
  value
}

my1   as.my(set_units(5,nm))
#> [1] 7
  • Related