S3 中的单个重载运算符的多次分派(在 R 中)

Multiple dispatches on an a single overloaded operator in S3 (in R)

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

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

用例如下:假设我的class被称为"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')"
> 

看来他们肯定是在互相覆盖。

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

你可以检查函数内部:

'*.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 调度对两个参数都起作用,规则如下:

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’: in that case or if no method is found for either argument the internal method is used.

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