Из ?UseMethod
(также в файле НОВОСТИ):
UseMethod
создает новый вызов функции с аргументами, совпадающими с тем, как они поступили в общий. [Ранее локальные переменные, определенные до вызоваUseMethod
, были сохранены; начиная с версии R 4.4.0 это уже не так.]
С этим фиктивным дженериком и его методами:
f <- function(x) {
y <- head(x, 1)
z <- tail(x, 1)
UseMethod("f")
}
f.numeric <- function(x) {
x + y + z
}
f.character <- function(x) {
paste(x, y, z)
}
В R >= 4.4.0 мы имеем:
f(1:3)
# Error in f.numeric(1:3) : object 'y' not found
f(c("a", "b", "c"))
# Error in f.character(c("a", "b", "c")) : object 'y' not found
В R < 4.4.0 у нас было:
f(1:3)
# [1] 5 6 7
f(c("a", "b", "c"))
# [1] "a a c" "b a c" "c a c"
Какой сейчас рекомендуемый способ передачи локальных переменных в методы?
Мой вариант использования включает в себя несколько объектов, которые предварительно вычисляются из x
перед вызовом UseMethod()
. Мне интересно, есть ли альтернатива обертыванию этого шага в функцию, которая будет вызываться в каждом методе, например:
f <- function(x) {
UseMethod("f")
}
g <- function(x) {
y <- head(x, 1)
z <- tail(x, 1)
list(y = y, z = z)
}
f.numeric <- function(x) {
yz <- g(x)
x + yz$y + yz$z
}
f.character <- function(x) {
yz <- g(x)
paste(x, yz$y, yz$z)
}
Это остается менее практичным, чем прежнее поведение, поскольку объекты теперь вложены в список. В любом случае, я хочу избежать дублирования кода в методах, поскольку общий шаг относительно длинный (в LOC, а не во времени).
Я хочу избежать дублирования кода, поэтому я подумал либо использовать функцию в методе, либо вычислить y
и z
в обобщенном виде. Я не говорю, что это необходимо, я ищу хорошую практику.
Кажется, вы хотите, чтобы f()
и f.numeric()
делили окружающую среду. Так почему бы не сделать это явным образом, определив их обоих во внешней среде? Вот один из способов сделать это:
# Create a local environment to be the common environment of the two
# functions
fns <- local({
# Create a dummy variable to hold the common local variable
y <- NULL
f <- function(x) {
# Modify the common variable
y <<- 10
# Dispatch to the method
UseMethod("f")
}
f.numeric <- function(x) {
# This function can see both the argument x and y in the enclosing
# environment
x + y
}
# Base R doesn't have "multi-assign", so we put these functions in a list
list(f = f, f.numeric = f.numeric)
})
# Extract the two functions we just created.
f <- fns$f
f.numeric <- fns$f.numeric
f(1)
#> [1] 11
Created on 2024-06-03 with reprex v2.1.0
Есть некоторые проблемы с этим подходом. Возможно, вы не хотите, чтобы эти два определения были в одном файле. Тогда вы можете изменить это на это:
f <- local({
y <- NULL
function(x) {
y <<- 10
UseMethod("f")
}
})
f.numeric <- function(x) {
x + y
}
environment(f.numeric) <- environment(f)
f(1)
#> [1] 11
Created on 2024-06-03 with reprex v2.1.0
Теперь вам нужно только убедиться, что определения f
и f.numeric
имеют место, прежде чем настраивать среду. Это можно сделать, принудительно установив последовательность сортировки файлов или поместив изменение среды в файл zzz.R
.
Я бы рекомендовал против второго подхода. Поскольку и f()
, и f.numeric()
работают с некоторыми общими переменными, для меня имеет смысл определить их в одном исходном файле. Было бы очень легко изменить локальные переменные в f()
без обновления f.numeric()
, если бы это было не так.
+1 и спасибо за предложение другого подхода. Однако это кажется более сложным, чем заключать предварительную обработку в функцию, вызываемую в каждом методе. (Я обновлю свой вопрос, чтобы лучше отразить мой вариант использования; общая переменная y
на самом деле является производной от x
, поэтому ее можно легко сделать в функции, вызываемой в методах.)
Конечно, это тоже сработает.
Если вы выбрали свой подход, подготовившись с помощью g(), и эти промежуточные результаты привели к объекту списка; вы могли бы упростить синтаксис, чтобы исключить вызовы $
, например
f <- function(x) {
UseMethod("f")
}
g <- function(x) {
y <- head(x, 1)
z <- tail(x, 1)
list(y = y, z = z)
}
f.numeric <- function(x) {
with(g(x),{
x + y + z
})
}
f.character <- function(x) {
with(g(x),{
paste(x, y, z)
})
}
f(1)
f("1")
Спасибо! Однако я не думаю, что это можно назвать рекомендацией. Из ?with
: «Для интерактивного использования это очень эффективно и приятно читать. Однако для программирования, то есть в функциях, требуется больше внимания, и обычно следует воздерживаться от использования with()
, так как, например, переменные в data
могут случайно переопределить локальные переменные, см. ссылку».
Как насчет этого?
f <- function(x) {
y <- head(x, 1)
z <- tail(x, 1)
f_int(x, y, z)
}
f_int <- function(x, ...) UseMethod("f_int")
f_int.numeric <- function(x, y, z, ...) {
x + y + z
}
f_int.character <- function(x, y, z, ...) {
paste(x, y, z)
}
f(1:3)
#[1] 5 6 7
f(c("a", "b", "c"))
#[1] "a a c" "b a c" "c a c"
Почему это необходимо? Почему эти переменные не могут быть вычислены в методах? Я не могу представить себе вескую причину. С точки зрения пользователя это не имеет значения. Производительность такая же.