Эмуляция множественной отправки с использованием 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.