Эмуляция множественной отправки с использованием S3 для метода "+" - возможно?

У меня два класса (a а также b) и я хочу определить + метод для них. Мне нужны разные методы для четырех возможных комбинаций двух классов, а именно:

a + a  method 1
a + b  method 2
b + a  method 3
b + b  method 4

Я знаю, что мог бы использовать S4 для многократной отправки, но я хочу знать, есть ли способ эмулировать это поведение, используя S3. Мой подход был следующим:

a <- "b"
class(a) <- "a"

b <- "e"
class(b) <- "b"

Ops.a <- function(e1, e2){
  if (class(e1) == "a" &
      class(e2) == "a")
    print("a & a")
  if (class(e1) == "a" &
        class(e2) == "b")
    print("a & b")
  if (class(e1) == "b" &
        class(e2) == "a")
    print("b & a")
  NULL
}

a + a
a + b
b + a

Все это прекрасно работает, но, конечно, следующее не определено.

b + b

Теперь, чтобы охватить этот случай, я добавил другое определение метода.

Ops.b <- function(e1, e2){
  if (class(e1) == "b" &
        class(e2) == "b")
    print("b & b")
  NULL
}

Это приведет к b + b работать но сейчас a + b а также b + a методы противоречивы и будут причиной и ошибкой.

> a + b
error in a + b : non-numeric argument for binary operator
additional: warning:
incompatible methods ("Ops.a", "Ops.b") for "+"

Есть ли способ правильно определить все четыре случая, используя S3?

3 ответа

Решение

Вы можете сделать это, определив +.a а также +.b как та же функция. Например:

a <- "a"
class(a) <- "a"
b <- "b"
class(b) <- "b"

`+.a` <- function(e1, e2){
  paste(class(e1), "+", class(e2))
}
`+.b` <- `+.a`

a+a
# [1] "a + a"
a+b
# [1] "a + b"
b+a
# [1] "b + a"
b+b
# [1] "b + b"

# Other operators won't work
a-a
# Error in a - a : non-numeric argument to binary operator

Если вы определите Ops.a а также Ops.b, он также определит операцию для других операторов, к которой можно получить доступ .Generic в функции:

##### Start a new R session so that previous stuff doesn't interfere ####
a <- "a"
class(a) <- "a"
b <- "b"
class(b) <- "b"

Ops.a <- function(e1, e2){
  paste(class(e1), .Generic, class(e2))
}

Ops.b <- Ops.a

a+a
# [1] "a + a"
a+b
# [1] "a + b"
b+a
# [1] "b + a"
b+b
# [1] "b + b"


# Ops covers other operators besides +
a-a
# [1] "a - a"
a*b
# [1] "a * b"
b/b
# [1] "b / b"

Обновление: еще одна вещь, которую я обнаружил, играя с этим. Если вы поместите это в пакет, вы получите сообщение об ошибке "не числовой аргумент" и предупреждение "несовместимые операторы". Это связано с тем, что R подходит для нескольких операторов только в том случае, если они представляют собой один и тот же объект с одним и тем же адресом в памяти - но каким-то образом при сборке и загрузке пакета две функции теряют эту точную идентичность. (Вы можете проверить это с помощью pryr::address())

Одна вещь, которую я нашел, это работает - это явная регистрация методов S3 при загрузке пакета. Например, это будет идти внутри вашего пакета:

# Shows the classes of the two objects that are passed in
showclasses <- function(e1, e2) {
  paste(class(e1), "+", class(e2))
}    

.onLoad <- function(libname, pkgname) {
  registerS3method("+", "a", showclasses)
  registerS3method("+", "b", showclasses)
}

В этом случае два метода указывают на один и тот же объект в памяти, и он работает (хотя это немного хакерство).

Ну, вы не можете использовать эту стратегию. Это специально запрещено, как вы обнаружили, и задокументировано на странице справки (Ops).

"Если метод найден только для одного аргумента или один и тот же метод найден для обоих, он используется. Если найдены разные методы, появляется предупреждение о" несовместимых методах ": в этом случае или если для одного из методов не найдено аргумент, внутренний метод используется."

Таким образом, вам нужно будет поместить все дела в один и тот же метод. (Проверено и успешно.)

Как насчет простого вызова оператора с обратными аргументами?

Ops.b <- function(e1, e2){
  if (class(e1) == "b" &
        class(e2) == "b")
    print("b & b")
  if (class(e1) =="b" & class(e2)=="a")
    e2+e1
  NULL
}

Но я настоятельно рекомендую использовать правильную множественную диспетчеризацию и, следовательно, S4 для этого. См. Объединение методов S4 и S3 в одной функции и Добавление диспетчеризации S4 к базовому R S3.

Другие вопросы по тегам