首页 > 解决方案 > S3(在R中)中单个重载运算符的多次调度

问题描述

在使用 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')"
> 

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

关于两者如何和平和富有成效地共存的任何想法?

标签: roperator-overloadingmultiple-dispatch

解决方案


您可以检查函数内部:

'*.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"

如此处所述,使用以下规则对两个参数进行调度:

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

因此,例如,由于还有一个*.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"

推荐阅读