Перегрузка оператора S3 для нескольких классов
Я определил два класса, которые могут успешно добавлять два своих собственных объекта или число и один из своих собственных объектов.
a <- structure(list(val = 1), class = 'customClass1')
b <- structure(list(val = 1), class = 'customClass2')
`+.customClass1` <- function(e1, e2, ...){
val1 <- ifelse(is.numeric(e1), e1, e1$val)
val2 <- ifelse(is.numeric(e2), e2, e2$val)
val_res <- val1 + val2
print('customClass1')
return(structure(list(val = val_res), class = 'customClass1'))
}
`+.customClass2` <- function(e1, e2, ...){
val1 <- ifelse(is.numeric(e1), e1, e1$val)
val2 <- ifelse(is.numeric(e2), e2, e2$val)
val_res <- val1 + val2
print('customClass2')
return(structure(list(val = val_res), class = 'customClass1'))
}
print.customClass1 <- function(x, ...){
print(x$val)
}
print.customClass2 <- function(x, ...){
print(x$val)
}
a + a
# [1] 2
a + 1
# [1] 2
b + b
# [1] 2
1 + b
# [1] 2
Но очевидно, что происходит ошибка, когда я пытаюсь добавить два пользовательских класса.
a + b
# Error in a + b : non-numeric argument to binary operator
# In addition: Warning message:
# Incompatible methods ("+.customClass1", "+.customClass2") for "+"
Я мог бы определить только одну функцию для customClass1, но тогда эта функция не работала бы, когда я пытался добавить два объекта customClass2. Есть ли способ расставить приоритеты одной функции над другой?
Кажется, что R делает это естественным образом, отдавая приоритет моим функциям над базовыми функциями (например, типа numeric или integer). Когда один из обоих аргументов имеет тип customClass, R автоматически перенаправляет его в мою функцию вместо функции по умолчанию.
2 ответа
Как R выбирает способ отправки, обсуждается в разделе " Подробности " ?base::Ops
Классы обоих аргументов учитываются при отправке любого члена этой группы. Для каждого аргумента проверяется его вектор классов, чтобы увидеть, существует ли соответствующий специфический (предпочтительный) метод или метод Ops. Если метод найден только для одного аргумента или один и тот же метод найден для обоих, он используется. Если найдены разные методы, появляется предупреждение о "несовместимых методах": в этом случае или если ни один из аргументов не найден, используется внутренний метод.
Если customClass1
а также customClass2
связаны, вы можете использовать виртуальный класс, чтобы разрешить операции с использованием двух разных классов. Например, вы можете смешать POSIXct
а также POSIXlt
потому что они оба наследуют от POSIXt
, Это задокументировано в ?DateTimeClasses
:
"POSIXct"
удобнее включать в кадры данных, а"POSIXlt"
ближе к читабельным формам. Виртуальный класс"POSIXt"
существует, от которого наследуются оба класса: он используется, чтобы позволить таким операциям, как вычитание, смешивать два
Например:
class(pct <- Sys.time())
# [1] "POSIXct" "POSIXt"
Sys.sleep(1)
class(plt <- as.POSIXlt(Sys.time()))
# [1] "POSIXlt" "POSIXt"
plt - pct
# Time difference of 1.001677 secs
Если классы не связаны таким образом, в ответах на Эмуляцию множественной диспетчеризации есть некоторая полезная информация, используя S3 для метода "+" - возможно?,
Джошуа объяснил, почему ваш подход никогда не может работать гладко при использовании S3 без создания виртуальных суперклассов и тому подобного. С S3 вам придется вручную управлять назначениями классов в каждой возможной функции, которую вы используете. Забудьте назначить суперкласс один раз, и вы отправитесь на охоту за ошибками, которая может продолжаться некоторое время.
Я настоятельно рекомендую отказаться от S3 и перейти к S4. Затем вы можете определить методы в обоих направлениях для группы "Ops". Преимущество этого заключается в том, что все арифметические, логические и операторы сравнения теперь определены для обоих классов. Если вы хотите ограничить это подгруппой или отдельным оператором, замените "Ops" на подгруппу или оператора. Больше информации на странице помощи ?S4GroupGeneric
,
Пример, основанный на ваших классах S3, использующих виртуальный класс для упрощения:
# Define the superclass
setClass("super", representation(x = "numeric"))
# Define two custom classes
setClass("foo", representation(slot1 = "character"),
contains = "super")
setClass("bar", representation(slot1 = "logical"),
contains = "super")
# Set the methods
setMethod("Ops",
signature = c('super','ANY'),
function(e1,e2){
callGeneric(e1@x, e2)
})
setMethod("Ops",
signature = c('ANY','super'),
function(e1,e2){
callGeneric(e1, e2@x)
})
# Redundant actually, but limits the amount of times callGeneric
# has to be executed.
setMethod("Ops",
signature = c('super','super'),
function(e1,e2){
callGeneric(e1@x, e2@x)
})
foo1 <- new("foo", x = 3, slot1 = "3")
bar1 <- new("bar", x = 5, slot1 = TRUE)
foo1 + bar1
#> [1] 8
bar1 + foo1
#> [1] 8
bar1 < foo1
#> [1] FALSE
foo1 / bar1
#> [1] 0.6
Пример с 2 классами, где имена слотов различны:
setClass("foo", representation(x = "numeric"))
setClass("bar", representation(val = "numeric"))
setMethod("Ops",
signature = c('foo','ANY'),
function(e1,e2){
callGeneric(e1@x, e2)
})
setMethod("Ops",
signature = c('bar','ANY'),
function(e1,e2){
callGeneric(e1@val, e2)
})
setMethod("Ops",
signature = c('ANY','bar'),
function(e1,e2){
callGeneric(e1, e2@val)
})
setMethod("Ops",
signature = c('ANY','foo'),
function(e1,e2){
callGeneric(e1, e2@x)
})
Опять же, вы можете использовать код выше, чтобы проверить результаты. Обратите внимание, что здесь вы получите примечание о выбранных методах при попытке сделать это в интерактивном режиме. Чтобы избежать этого, вы можете добавить метод для подписи c('foo','bar')
а также c('bar','foo')