Я хотел бы написать метод %*%
для подкласса базовой матрицы. Мой подкласс является классом S3, и в документации help("%*%")
говорится, что %*%
является универсальным S4 и что методы S4 необходимо написать для функции с двумя аргументами, названными x
и y
. Я написал методы для классов S3 для универсальных методов S4 перед использованием methods::setOldClass()
и methods::setMethod()
, и я просмотрел исходный код пакета {Matrix}
для вдохновения, но по какой-то причине я не могу заставить это работать в этом случае. methods::showMethods()
показывает, что мой метод существует для моей целевой подписи, но мой метод никогда не вызывается R, когда я пытаюсь его использовать.
x <- diag(3)
class(x) <- c("atm2d", class(matrix()))
print(x)
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
attr(,"class")
[1] "atm2d" "matrix" "array"
По умолчанию %*%
избавляется от моего атрибута класса, и я хотел бы сохранить его.
print(x %*% x)
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
Я попытался создать метод %*%
для своего класса, который не избавляется от атрибута моего класса:
as.matrix.atm2d <- function(x, ...) {
class(x) <- NULL
x
}
matmult <- function(x, y) {
v <- as.matrix(x) %*% as.matrix(y)
class(v) <- c("atm2d", class(matrix()))
v
}
methods::setOldClass("atm2d")
# this alternate `setOldClass()` also doesn't work
# methods::setOldClass(c("atm2d", class(matrix())))
methods::setMethod("%*%",
c(x = "atm2d", y = "atm2d"),
function(x, y) matmult(x, y))
showMethods()
кажется, что был создан метод S4 с ожидаемой подписью:
showMethods("%*%", class = "atm2d")
Function: %*% (package base)
x = "atm2d", y = "atm2d"
Однако этот метод на самом деле не вызывается %*%
:
print(x %*% x)
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
Если бы мой метод был вызван, я бы вместо этого ожидал, что он также распечатает свой класс:
print(matmult(x, x))
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
attr(,"class")
[1] "atm2d" "matrix" "array"
Оператор %*%
является внутренне универсальным, что означает, что отправка происходит в коде C. В настоящее время (т.е. в R 4.2.3) соответствующая функция C do_matprod
(определенная здесь) содержит эту проверку:
if (PRIMVAL(op) == 0 && /* %*% is primitive, the others are .Internal() */
(IS_S4_OBJECT(x) || IS_S4_OBJECT(y))
&& R_has_methods(op)) {
SEXP s, value;
/* Remove argument names to ensure positional matching */
for(s = args; s != R_NilValue; s = CDR(s)) SET_TAG(s, R_NilValue);
value = R_possible_dispatch(call, op, args, rho, FALSE);
if (value) return value;
}
Если ни x
, ни y
не являются объектами S4, как в вашем примере, то do_matprod
продолжает обрабатывать их как традиционные матрицы, не глядя на атрибут class
любого аргумента. Раздел help("%*%")
, на который вы ссылаетесь:
Этот оператор является универсальным для S4, но не универсальным для S3. Методы S4 должны быть написаны для функции двух аргументов с именами
x
иy
.
пытается это выразить, но не особо ясно. (В конце концов, вы определили метод S4.)
Здесь есть две основные проблемы:
setOldClass
позволяет вам определять методы S4 с классами S3 в сигнатуре, но внутренне универсальные функции ищут методы S4 только тогда, когда один из аргументов является объектом S4 (для скорости).
%*%
не является общим для S3, поэтому даже если вы зарегистрируете метод S3, такой как %*%.zzz
, он не будет отправлен.
При этом оператор %*%
станет универсальным для S3, начиная с R 4.3.0, который должен быть выпущен 21 апреля. Вы найдете эту запись в НОВОСТИ:
Оператор умножения матриц
%*%
теперь является универсальным S3, принадлежащим новой общей группеmatrixOps
. Из вклада Томаша Калиновского в PR#18483.
Когда это произойдет, %*%
будет вести себя как +
и другие внутренне общие члены группы Ops
, в том смысле, что методы S3 %*%.zzz
будут отправляться там, где это необходимо. Однако методы S4 по-прежнему не будут отправляться, если ни один из аргументов не является объектом S4.
Возможно, setMethod
следует изменить, чтобы сигнализировать о предупреждении или ошибке, когда вы пытаетесь определить методы S4, которые никогда не будут отправлены, как в вашем примере.
Может помочь перечисление типов универсальных функций и типов методов, которые они отправляют, ограничивая внимание S3 и S4. Мы будем использовать этот сценарий для определения объектов для наших тестов, каждый из которых должен запускаться в новом процессе R:
## objects.R
w <- structure(0, class = "a")
x <- structure(0, class = "b")
setOldClass("c")
y <- structure(0, class = "c")
setClass("d", contains = "numeric")
z <- new("d", 0)
Эти методы отправки S3 для классов S3 и классов S4 через UseMethod
. Когда метод не найден, они отправляют метод по умолчанию *.default
или (если он не найден) выдают ошибку. Они никогда не отправляют методы S4.
source("objects.R")
h <- .__h__. <- function(x) UseMethod("h")
.S3method("h", "default", function(x) "default")
.S3method("h", "a", function(x) "a3")
setMethod("h", "c", function(x) "c4")
setMethod("h", "d", function(x) "d4")
h <- .__h__. # needed to undo side effect of 'setMethod'
h
## function(x) UseMethod("h")
h(w)
## [1] "a3"
h(x)
## [1] "default"
h(y)
## [1] "default"
h(z)
## [1] "default"
Эти методы диспетчеризации S4 для классов S3 (формально определенных с помощью setOldClass
) и классов S4 через standardGeneric
. Когда метод не найден, они отправляют метод по умолчанию *@default
. Если метод по умолчанию является универсальным S3, то отправка происходит снова, на этот раз любым доступным методам S3. Однако часто метод по умолчанию просто вызывает stop
, чтобы сигнализировать об ошибке.
source("objects.R")
h <- function(x) UseMethod("h")
.S3method("h", "default", function(x) "default")
.S3method("h", "c", function(x) "c3")
setMethod("h", "d", function(x) "d4")
h
## standardGeneric for "h" defined from package ".GlobalEnv"
##
## function (x)
## standardGeneric("h")
## <environment: 0x1044650b0>
## Methods may be defined for arguments: x
## Use showMethods(h) for currently available ones.
h@default
## Method Definition (Class "derivedDefaultMethod"):
##
## function (x)
## UseMethod("h")
##
## Signatures:
## x
## target "ANY"
## defined "ANY"
h(w)
## [1] "default"
h(x)
## [1] "default"
h(y)
## [1] "c3"
h(z)
## [1] "d4"
Все они определены в базе как примитивные функции. Вы должны обратиться к страницам справки или исходному коду, чтобы определить, являются ли они общими только для S3, только для S4 или для обоих S3 и S4. В третьем случае диспетчеризация S3 происходит только в том случае, если подходящие методы S4 не найдены. И, как я уже объяснил, отправка S4 происходит только в том случае, если один из аргументов в подписи является объектом S4.
Возьмем +
и %*%
в качестве примера. Оба являются общими для S4, но только +
является общим для S3.
source("objects.R")
.S3method("+", "default", function(e1, e2) "default")
.S3method("+", "a", function(e1, e2) "a3")
.S3method("+", "b", function(e1, e2) "b3")
.S3method("+", "c", function(e1, e2) "c3")
.S3method("+", "d", function(e1, e2) "d3")
setMethod("+", c("c", "c"), function(e1, e2) "c4")
setMethod("+", c("d", "d"), function(e1, e2) "d4")
w + w
## [1] "a3"
x + x
## [1] "b3"
y + y
## [1] "c3"
z + z
## [1] "d4"
Здесь отправляются методы S3. Первые три результата получены через диспетчеризацию S3. Последний результат получается через диспетчеризацию S4.
source("objects.R")
.S3method("%*%", "default", function(x, y) "default")
.S3method("%*%", "a", function(x, y) "a3")
.S3method("%*%", "b", function(x, y) "b3")
.S3method("%*%", "c", function(x, y) "c3")
.S3method("%*%", "d", function(x, y) "d3")
setMethod("%*%", c("c", "c"), function(x, y) "c4")
setMethod("%*%", c("d", "d"), function(x, y) "d4")
w %*% w
## [,1]
## [1,] 0
x %*% x
## [,1]
## [1,] 0
y %*% y
## [,1]
## [1,] 0
z %*% z
## [1] "d4"
Здесь методы S3 не отправляются. Первые три результата получены с помощью внутреннего метода по умолчанию. Последний результат получается через диспетчеризацию S4.