【问题标题】:Multiple dispatches on an a single overloaded operator in S3 (in R)S3(在R中)中单个重载运算符的多次调度
【发布时间】:2018-09-03 06:47:52
【问题描述】:

在使用 S3 类时,我想重载 R 中的“*”(乘法运算符)。

我看到 * 在系统中已经是通用的,但我想要它“generic2”,即在第二个参数上调度。

用例如下:假设我的类名为“Struct”。我希望能够允许所有这三种情况

Struct * Struct 
Struct * Number
Number * Struct

但是我发现,如果我允许在第二个参数上进行调度,则(已经存在的)第一个参数上的调度将被覆盖!

有没有办法在 S3 中做到这一点?

# "generic1" already exists for '*'

'*' <- function(x,y){
  UseMethod('*2',y)
}

'*.Struct'<-function(x,y){
  # must both be structs, so dispatch 'normally' if not
  "times(1)"
}

`*2.Struct`<-function(x,y){
  # must both be structs, so dispatch 'normally' if not
  "times(2)"
}

给我...

> struct1 * struct2
[1] "times(2)"
> 2 * struct2
[1] "times(2)"
> struct1 * 2
Error in UseMethod("*2", y) : 
  no applicable method for '*2' applied to an object of class "c('double', 'numeric')"
> 

如果我使用这个,而不是

'*' <- function(x,y){ UseMethod('*',x)}

然后第一个参数的调度起作用,相反的情况发生:

> struct1 * 2
[1] "times(1)"
> struct1 * struct2
[1] "times(1)"
> 2* struct1 
Error in UseMethod("*", x) : 
  no applicable method for '*' applied to an object of class "c('double', 'numeric')"
> 

所以看起来他们肯定会互相覆盖。

关于两者如何和平且富有成效地共存有什么想法吗?

【问题讨论】:

  • 我说你应该使用 S4。
  • 是的,也许这可以用 S4 类系统更好地处理......

标签: r operator-overloading multiple-dispatch


【解决方案1】:

您可以检查函数内部:

'*.Struct'<-function(x,y){
  if(inherits(x,'Struct') && inherits(y,'Struct'))
    "Struct*Struct"
  else if(inherits(y,'Struct'))
    "N*Struct"
  else
    "Struct*N"
}
# N.B.: you don't need to redefine `*`,`*2.Struct` etc

例如:

struct1=structure(5,class='Struct')
struct2=structure(3,class='Struct')

struct1*struct2
# [1] "Struct*Struct"
struct1*2
# [1] "Struct*N"
3*struct2
# [1] "N*Struct"

正如here 所述,调度对两个参数都有效,规则如下:

如果仅找到一个参数的方法或找到相同的方法 两者都使用它。如果找到不同的方法,有一个 关于“不兼容的方法”的警告:在这种情况下或者如果没有方法 发现任一参数都使用了内部方法。

因此,例如,由于还定义了一个 *.difftime 方法,这些情况会给出带有警告的奇怪结果:

difftimeObj <- Sys.time()-Sys.time()

struct1*difftimeObj
# [1] 0
# attr(,"units")
# [1] "secs"
# attr(,"class")
# [1] "Struct"
# Warning message:
# Incompatible methods ("*.Struct", "*.difftime") for "*" 

difftimeObj*struct2
# Time difference of 0 secs
# Warning message:
# Incompatible methods ("*.difftime", "*.Struct") for "*" 

而这些反而有效:

struct1*unclass(difftimeObj)
# [1] "Struct*N"
unclass(difftimeObj)*struct2
# [1] "N*Struct"

# skipping dispatching
`*.Struct`(struct1, difftimeObj)
# [1] "Struct*N"
`*.Struct`(difftimeObj, struct2)
# [1] "N*Struct"

【讨论】:

  • 谢谢。在这种情况下,这对我有用,因为 Struct 恰好是一个向量,所以默认情况下将处理乘以数字,但不幸的是,如果我不能依赖系统默认行为,我认为它不会起作用,即,如果我必须能够自己定义“Number * Struct”的行为......
  • @AlexGian:说实话,我不明白你的问题......在我的例子中,你可能有 3 个案例,如果你在乘以之前“取消分类”其中一个案例中的对象想要默认的 R 行为......但可能我错过了一些东西
  • 我怀疑是你不明白。这只是我使用 R 的第二天,所以我很可能会遗漏一些东西。我会再考虑一下,试试你建议的取消分类,看看我能不能想出点什么。不管怎么说,还是要谢谢你。 (顺便说一句,我的意思是¿HOW?即使第一个参数是一个数字,重载的运算符也会被触发吗?我认为调度正在发生,因为第一个参数是一个结构。
  • 对,是的,我注意到 difftime 方法定义了。它现在不知何故被覆盖了,我再也看不到它了。无论如何,我删除了 '' ', x) } 行,重新加载了所有内容,现在您的原始代码可以正常工作了!我不确定 difftime thingumy 自己到了哪里,但我很高兴按照现在的方式继续进行,我相信我稍后会处理这些细节。非常感谢您的详细帮助!
  • 当然 :) 还考虑到您可以使用 Ops.Struct &lt;- function(){} 覆盖整个运算符集(即 &gt;,&lt;,==,+,-,*...)检查函数的示例,检查 Ops.difftime 的实现方式
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-23
  • 1970-01-01
  • 2018-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多