Передача локальных переменных методам в R >= 4.4.0

Из ?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, а не во времени).

Почему это необходимо? Почему эти переменные не могут быть вычислены в методах? Я не могу представить себе вескую причину. С точки зрения пользователя это не имеет значения. Производительность такая же.

Roland 04.06.2024 07:58

Я хочу избежать дублирования кода, поэтому я подумал либо использовать функцию в методе, либо вычислить y и z в обобщенном виде. Я не говорю, что это необходимо, я ищу хорошую практику.

Thomas 04.06.2024 09:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
129
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Кажется, вы хотите, чтобы 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, поэтому ее можно легко сделать в функции, вызываемой в методах.)

Thomas 03.06.2024 21:27

Конечно, это тоже сработает.

user2554330 04.06.2024 10:18

Если вы выбрали свой подход, подготовившись с помощью 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 могут случайно переопределить локальные переменные, см. ссылку».

Thomas 04.06.2024 11:16
Ответ принят как подходящий

Как насчет этого?

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"

Другие вопросы по теме