5

我定义了两个类,它们可以成功添加两个自己的对象或一个数字和一个自己的对象。

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

但很明显,当我尝试添加两个自定义类时会出错。

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

我可以只为 customClass1 定义一个函数,但是当我尝试添加两个 customClass2 对象时,该函数将不起作用。有没有办法将一个功能优先于另一个功能?

R 似乎通过将我的函数优先于基本函数(例如数字或整数类型)来自然地做到这一点。当两个参数之一具有 customClass 类型时,R 会自动将其重定向到我的函数而不是默认函数。

4

2 回答 2

3

R 如何选择调度哪个方法在详细信息部分讨论?base::Ops

在调度该组的任何成员时会考虑这两个参数的类。对于每个参数,检查其类向量以查看是否存在匹配的特定(首选)或“Ops”方法。如果只为一个参数找到一个方法,或者为两个参数找到相同的方法,则使用它。如果找到不同的方法,则会出现关于“不兼容方法”的警告:在这种情况下,或者如果没有为任一参数找到方法,则使用内部方法。

如果customClass1customClass2相关,您可以使用虚拟类来允许使用两个不同的类进行操作。例如,您可以混合使用POSIXctPOSIXlt因为它们都继承自POSIXt. 这记录在?DateTimeClasses

"POSIXct"更方便包含在数据框中, "POSIXlt"更接近人类可读的形式。存在一个虚拟类 "POSIXt",两个类都从该类继承:它用于允许诸如减法之类的操作将两者混合

例如:

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

如果类不以这种方式相关,那么在Emulating multiple dispatch using S3 for "+" method 的答案中有一些很好的信息 - 可能吗?.

于 2017-03-29T12:27:14.663 回答
3

Joshua 解释了为什么在不构建虚拟超类等的情况下使用 S3 时您的方法永远无法顺利运行。使用 S3,您必须手动管理您使用的每个可能功能中的课程分配。忘记分配一次超级类,你就会去寻找可能持续一段时间的错误。

我强烈建议放弃 S3 并转向 S4。然后,您可以为“Ops”组定义双向的方法。这样做的好处是现在为这两个类定义了所有算术、逻辑和比较运算符。如果要将其限制为子组或单个运算符,请将“Ops”替换为子组或运算符。帮助页面上的更多信息?S4GroupGeneric

一个基于 S3 类的示例,使用虚拟类使事情变得更容易:

# Define the superclass
setClass("super", representation(x = "numeric"))
# Define two custom classes
setClass("foo", representation(slot1 = "character"),
         contains = "super")
setClass("bar", representation(slot1 = "logical"),
         contains = "super")

# Set the methods
setMethod("Ops",
          signature = c('super','ANY'),
          function(e1,e2){
            callGeneric(e1@x, e2)
          })
setMethod("Ops",
          signature = c('ANY','super'),
          function(e1,e2){
            callGeneric(e1, e2@x)
          })
# Redundant actually, but limits the amount of times callGeneric
# has to be executed. 
setMethod("Ops",
          signature = c('super','super'),
          function(e1,e2){
            callGeneric(e1@x, e2@x)
          })

foo1 <- new("foo", x = 3, slot1 = "3")
bar1 <- new("bar", x = 5, slot1 = TRUE)

foo1 + bar1
#> [1] 8
bar1 + foo1
#> [1] 8
bar1 < foo1
#> [1] FALSE
foo1 / bar1
#> [1] 0.6

具有 2 个插槽名称不同的类的示例:

setClass("foo", representation(x = "numeric"))
setClass("bar", representation(val = "numeric"))

setMethod("Ops",
          signature = c('foo','ANY'),
          function(e1,e2){
            callGeneric(e1@x, e2)
          })
setMethod("Ops",
          signature = c('bar','ANY'),
          function(e1,e2){
            callGeneric(e1@val, e2)
          })
setMethod("Ops",
          signature = c('ANY','bar'),
          function(e1,e2){
            callGeneric(e1, e2@val)
          })
setMethod("Ops",
          signature = c('ANY','foo'),
          function(e1,e2){
            callGeneric(e1, e2@x)
          })

您可以再次使用上面的代码来检查结果。请注意,当您以交互方式尝试此操作时,您将在此处获得有关所选方法的注释。为避免这种情况,您可以添加签名方法c('foo','bar')c('bar','foo')

于 2017-03-29T12:51:18.940 回答