С универсальным шаблоном, использующим UseMethod
, NextMethod
работает так, как я ожидаю:
f <- function(...) UseMethod("f")
f.default <- function(...) 5
f.foo <- function(...) {print("hi"); NextMethod()}
x <- structure(1, class = "foo")
f(x) # prints "hi", returns 5
Со следующим внутренним дженериком аналогичный подход не работает:
# NB: base::cbind is an internal generic
cbind.default <- function(...) 5
cbind.foo <- function(...) {print("hi"); NextMethod()}
cbind(x) # prints "hi", Error in NextMethod() : generic function not specified
Можно ли использовать NextMethod
с внутренними дженериками, и если да, то какие аргументы и т. д. необходимы?
Похоже, что для cbind
метод «.default» для функции по сути ничего не делает. Он не определен и фактически не вызывается. Так что по сути нет NextMethod, который можно было бы найти. Вы можете изменить класс внутри своего собственного метода, чтобы добиться желаемого поведения. Здесь мы зачистим класс и попробуем еще раз вызвать на базу cbind
.
cbind.foo <- function(x, ...) {print("hi"); cbind(unclass(x))}
# [1] "hi"
# [,1]
# [1,] 1
Он использует значение 1 из x
, а не значение 5 из cbind.default
, потому что оно никогда не вызывается.
as.vector.foo <- function(x, mode = "any") { print("foo"); NextMethod() }; as.vector.default <- function(x, mode = "any") { print("default"); x }; as.vector(structure(0, class = "foo"))
Глядя на methods(as.vector)
, у него есть as.vector,ANY-method
метод S4. Так что, возможно, в этом и есть разница.
Таким образом, cbind
может отправлять метод, специфичный для класса cbind.foo
, но не устанавливает .Generic
, .Method
, .Class
, как сказал Микаэль. Таким образом, cbind.foo
должен быть реализован как обычная функция и не может выполнять наследование. Но наследование можно как бы смоделировать, изменив класс x
и вызвав cbind(x)
внутри cbind.foo
.
Разница в том, что do_asvector
в coerce.c
использует DispatchOrEval
, а do_bind
в bind.c
использует applyClosure
напрямую, т. е. не строит правильный фрейм оценки для вызова метода.
Следовательно, мне кажется, что это ошибка на уровне C do_bind
.
Adv-R 13.7.2: «внутренние дженерики... не вызывайте UseMethod(), а вместо этого вызывайте функции C DispatchGroup() или DispatchOrEval()». Если это точно, то вызов applyClosure
вместо DispatchOrEval
вполне может быть ошибкой.
С другой стороны, поскольку cbind
и rbind
отправляются на ...
, а не на x
, понятно, что они действуют по-разному. Фактически help("cbind")
четко документирует, что отправка в этих случаях действительно является исключительной. Я все равно напишу отчет об ошибке, главным образом потому, что c
, который также отправляется на ...
, не имеет этой проблемы.
Такое поведение обусловлено специальным механизмом отправки на ...
на уровне C do_bind
(см. bind.c ). Он не использует традиционный механизм отправки, описанный в help("NextMethod")
и реализованный на уровне C Dispatch[Any]OrEval()
(см. eval.c). Важно отметить, что поиск метода завершается успешно, но вызов метода не оценивается в среде, определяющей переменные .Generic
, .Method
, .Class
и т. д., необходимые для отправки NextMethod
.
С одной стороны, хорошо документировано, что механизм отправки, используемый [cr]bind
, является исключительным (см. раздел «Отправка» в help("cbind")
). С другой стороны, c
концептуально очень параллелен [cr]bind
, также выполняет внутреннюю отправку ...
, но c
не имеет этого ограничения:
> m <- function(...) { cat("a\n"); NextMethod() }
> for (generic in c("c", "cbind", "rbind")) .S3method(generic, "a", m)
> x <- structure(0, class = "a")
> c(x)
a
[1] 0
> cbind(x)
a
Error in NextMethod() : generic function not specified
> rbind(x)
a
Error in NextMethod() : generic function not specified
> debug(m)
> c(x)
debugging in: c.a(x)
debug at #1: {
cat("a\n")
NextMethod()
}
Browse[1]> ls(all.names = TRUE)
[1] "..." ".Class" ".Generic" ".GenericCallEnv"
[5] ".GenericDefEnv" ".Group" ".Method"
Browse[1]> Q
> cbind(x)
debugging in: cbind(deparse.level, ...)
debug at #1: {
cat("a\n")
NextMethod()
}
Browse[1]> ls(all.names = TRUE)
[1] "..."
Browse[1]> Q
> rbind(x)
debugging in: rbind(deparse.level, ...)
debug at #1: {
cat("a\n")
NextMethod()
}
Browse[1]> ls(all.names = TRUE)
[1] "..."
Browse[1]> Q
Мне кажется совершенно «неправильным», что [cr]bind
и c
здесь должны вести себя по-разному. Итак, я подал отчет об ошибке: https://bugs.r-project.org/show_bug.cgi?id=18779
as.vector
является внутренним универсальным (в.internalGenerics
) и ведет себя не так, какcbind
.