S3运算符重载多个类

时间:2017-03-28 10:01:28

标签: r operator-overloading r-s3

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

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会自动将其重定向到我的函数而不是默认函数。

2 个答案:

答案 0 :(得分:2)

?base::Ops

详细信息部分中讨论了R如何选择要调度的方法
  

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

如果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 - possible?的答案中会有一些很好的信息。

答案 1 :(得分:2)

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

我强烈建议放弃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')