Несоответствие поведения диспетчеризации S4 для классов R6
Актуальные вопросы
Не должен факт, что классы R6 наследуются от (неформального S3) класса
R6
разрешить определение методов S4 для сигнатурных аргументов этого самого класса?Поскольку это - AFAICT - не тот случай, какой будет обходной путь, который соответствует текущим стандартам S3/S4 или который может в некоторой степени рассматриваться как "наилучшая практика" в таких ситуациях?
Фон и пример
Справочные классы
Рассмотрим следующий пример, в котором вы хотите определить методы, которые отправляют суперклассу, от которого наследуются все экземпляры ссылочных классов (envRefClass
):
TestRefClass <- setRefClass("TestRefClass", fields= list(.x = "numeric"))
setGeneric("foo", signature = "x",
def = function(x) standardGeneric("foo")
)
setMethod("foo", c(x = "envRefClass"),
definition = function(x) {
"I'm the method for `envRefClass`"
})
> try(foo(x = TestRefClass$new()))
[1] "I'm the method for `envRefClass`"
Эта структура наследования не является очевидной class()
не буду раскрывать этот факт:
class(TestRefClass$new())
[1] "TestRefClass"
attr(,"package")
[1] ".GlobalEnv"
Однако взгляд на атрибуты объекта генератора классов показывает это:
> attributes(TestRefClass)
[... omitted ...]
Reference Superclasses:
"envRefClass"
[... omitted ...]
Вот почему отправка работает
R6 Классы
Когда вы хотите подобную вещь для классов R6, вещи не кажутся прямыми, даже если они изначально выглядят так (по сравнению с эталонными классами):
TestR6 <- R6Class("TestR6", public = list(.x = "numeric"))
setMethod("foo", c(x = "R6"),
definition = function(x) {
"I'm the method for `R6`"
})
> try(foo(x = TestR6$new()))
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘foo’ for signature ‘"TestR6"’
Под "появлением прямо" я подразумеваю, что class()
фактически предполагает, что все классы R6 наследуются от класса R6
это может быть использовано в качестве суперкласса для отправки метода:
class(TestR6$new())
[1] "TestR6" "R6"
Страница справки R6Class()
на самом деле показывает, что класс R6
просто добавляется как неформальный класс S3 до тех пор, пока class = TRUE
, По этой же причине при попытке определить метод S4 для этого класса появляется предупреждение.
Таким образом, это оставляет нам два возможных варианта / обходных пути:
- Включи класс
R6
в формальный класс черезsetOldClass()
- Пусть все экземпляры классов R6 наследуются от какого-то другого суперкласса, скажем,
.R6
Объявление 1)
setOldClass("R6")
> isClass("R6")
[1] TRUE
Это работает при взломе в стиле S3 в таблице / графике классов:
dummy <- structure("something", class = "R6")
> foo(dummy)
[1] "I'm the method for `R6`"
Однако, это терпит неудачу для фактических экземпляров класса R6:
> try(foo(x = TestR6$new()))
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘foo’ for signature ‘"TestR6"’
Объявление 2)
.R6 <- R6Class(".R6")
TestR6_2 <- R6Class("TestR6_2", inherit = .R6, public = list(.x = "numeric"))
setMethod("foo", c(x = ".R6"),
definition = function(x) {
"I'm the method for `.R6`"
})
> try(foo(x = TestR6_2$new()))
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘foo’ for signature ‘"TestR6_2"’
Заключение
В то время как подход 1 сортировки работает в "серой зоне", чтобы сделать S3 и S4 несколько совместимыми, подход 2 выглядит как совершенно правильное решение "чистого S4", которое должно работать IMO. Тот факт, что это не привело меня к постановке вопроса, существует ли несоответствие в реализации классов R6 в отношении взаимодействия неформальных / формальных классов и диспетчеризации методов в R.
1 ответ
С разрешения Хэдли Уикхем я узнал, что setOldClass()
фактически решает проблему при включении структуры наследования:
require("R6")
setOldClass(c("TestR6", "R6"))
TestR6 <- R6Class("TestR6", public = list(.x = "numeric"))
setGeneric("foo", signature = "x",
def = function(x) standardGeneric("foo")
)
setMethod("foo", c(x = "R6"),
definition = function(x) {
"I'm the method for `R6`"
})
try(foo(x = TestR6$new()))
Тем не менее, AFAICT, это означает, что для ваших пакетов, вы должны убедиться, что setOldClass()
вызывается таким образом для всех ваших классов R6, для которых вы хотите, чтобы ваши методы S4 работали.
Это можно сделать, связав эти вызовы в функции .onLoad()
или же .onAttach()
(см. здесь):
.onLoad <- function(libname, pkgname) {
setOldClass(c("TestR6_1", "R6"))
setOldClass(c("TestR6_2", "R6"))
setOldClass(c("TestR6_3", "R6"))
}
Предполагается, что вы определили три класса R6 (TestR6_1
через TestR6_3
)