Как переопределить реализацию неуниверсальной функции в R

В R у меня есть класс S3, который эмулирует одномерный вектор, поэтому я хочу реализовать пользовательские версии mean, sum, max и т. д. Допустим, это выглядит так:

my_class = function(){
  structure(list(), class='my_class')
}

Все вышеперечисленные методы работают нормально, если я определяю mean.my_class и т. д.:

mean.my_class = function(x){
  2
}

mean(my_class())

Однако я также хотел бы сделать это для таких функций, как var, который не является универсальным методом. Если я создам такую ​​функцию, а затем вызову var для экземпляра моего класса:

var.my_class = function(his){
  # Do stuff here
}

var(my_class())

Я получаю сообщение об ошибке:

Error in var(my_class()) : is.atomic(x) is not TRUE

Это потому, что универсальной функции var нет, и она просто вызывает stats::var в моей структуре. Как тогда я могу предоставить пользовательскую реализацию, если нет универсального метода? Я также не хочу нарушать метод var для обычных векторов.


person Migwell    schedule 26.03.2021    source источник
comment
Извините, у меня нет времени для полного ответа, но проверьте Создание новых методов и дженериков в adv-r.had.co.nz/OO-essentials.html   -  person Claudiu Papasteri    schedule 26.03.2021
comment
Верно, но поскольку уже есть функция var, должен ли я определить общую функцию с тем же именем или с другим именем?   -  person Migwell    schedule 26.03.2021


Ответы (2)


Из методов для неуниверсальных методов, S4 setMethod кажется совместимым с классом S3:

setMethod("var","my_class",function(x, y = NULL, na.rm = FALSE, use) {"Var for my_class"})
#> Creating a generic function for 'var' from package 'stats' in the global environment
#> in method for 'var' with signature '"my_class"': no definition for class "my_class"

my_class = function(){
  structure(list(), class='my_class')
}

var(my_class())
#> [1] "Var for my_class"
var(1:10)
#> [1] 9.166667
person Waldi    schedule 27.03.2021
comment
Конечно, это работает, но вы можете сделать то же самое в S3, нет необходимости использовать S4. В любом случае он создает совершенно новый дженерик, а не добавляет метод к существующему дженерику. - person Konrad Rudolph; 27.03.2021
comment
Мне нравится этот подход, потому что setMethod (и связанный с ним setGeneric) специально разработаны для моего варианта использования и, следовательно, могут делать полезные вещи, такие как копирование сигнатуры метода. Как отметил @KonradRudolph, вы все еще не можете заменить исходную функцию в исходном пакете, хотя я не ожидал, что это будет возможно. - person Migwell; 28.03.2021

Вы можете создать свою собственную универсальную функцию с тем же именем и сигнатурой, что и у существующей неуниверсальной функции:

var = function (x, y = NULL, na.rm = FALSE, use) UseMethod('var')
var.my_class = function (x, y, na.rm, use) 42

registerS3method('var', 'default', stats::var)
registerS3method('var', 'my_class', var.my_class)

… вместо того, чтобы вручную копировать список формальных аргументов для универсальной функции, вы можете использовать следующее для его автоматического создания:

var = function () UseMethod('var')
formals(var) = formals(stats::var)

— Конечно, описанное выше работает только в том случае, если вызывающая сторона вызывает вашу функцию var. Если кто-то вызовет, например, stats::var, ваш дженерик не будет найден, и поэтому диспетчеризация метода S3 не произойдет.


Имейте в виду, что в R 3.5 изменился способ работы поиска метода S3 в разных средах, и простого определения функции с соответствующим именем больше недостаточно для поиска метода в другой среде — вам необходимо вызвать registerS3method ( не только для новых дженериков, таких как var, но и для вашего mean!). К сожалению, официальной документации по этой функции практически нет, только несколько сообщения в блоге Курта Хорника.

person Konrad Rudolph    schedule 27.03.2021
comment
Позволит ли registerS3method использовать функцию var по умолчанию вместо mypackage::var? Кроме того, как это связано с S3method в файле NAMESPACE? - person Migwell; 28.03.2021
comment
@Migwell stats::var остается неуниверсальной функцией, изменить это невозможно. Но вы можете вызвать var вместо mypackage::var, если ваш пакет подключен (но достаточно, если подключена только функция var, а не остальные; это возможно, например, через 'box'). registerS3method не имеет к этому отношения, но если ваш код помещается в пакет, вам не нужен registerS3method: достаточно иметь S3method внутри файла NAMESPACE (и фактически имеет тот же эффект, что и вызов registerS3method). - person Konrad Rudolph; 28.03.2021