Удобный способ делегировать вызовы функций - PullRequest
1 голос
/ 11 июля 2019

У меня есть ряд похожих функций, которые все должны извлечь некоторые значения из фрейма данных.Примерно так:

foo_1 <- function(data, ...) {
  x <- data$x
  y <- data$y

  # some preparatory code common to all foo_X functions

  # .. do some boring stuff with x and y

  # pack and process the result into 'ret'
  return(ret)
}

Эти функции затем предоставляются в качестве аргументов для какой-либо другой функции (назовем ее «главной функцией». Я не могу изменить ведущую функцию).

ОднакоЯ хотел бы избежать переписывания одного и того же подготовительного кода в каждой из этих функций. Например, , я не хочу использовать data$x вместо того, чтобы назначать его для x и x, потому что это делает скучные вещи трудными для чтения.В настоящее время мне нужно написать x <- data$x (и т. Д.) Во всех функциях foo_1, foo_2 ....Что раздражает и загромождает код.Кроме того, упаковка и обработка являются общими для всех функций foo_N.Другой подготовительный код включает в себя масштабирование переменных или регуляризацию идентификаторов.

Каким будет элегантный и лаконичный способ сделать это?

Одна из возможностей - attach() фрейм данных (или использовать with(), как предложил Хонг в ответе ниже), но я не знаю, какие другие переменные будут в моем пространстве имен: присоединение данных может маскировать другие переменные, которые я использую в fun_1.Кроме того, предпочтительно, чтобы foo_N функции вызывались с явными параметрами, чтобы было легче увидеть, что им нужно и что они делают.

Следующая возможность, о которой я думал, была конструкция, подобная этой:

foo_generator <- function(number) {

  tocall <- switch(1=foo_1, 2=foo_2, 3=foo_3) # etc.

  function(data, ...) {
    x <- data$x
    y <- data$y
    tocall(x, y, ...)
    # process and pack into ret
    return(ret)
}

foo_1 <- function(x, y, ...) {
  # do some boring stuff
}

Тогда я могу использовать foo_generator(1) вместо foo_1 в качестве аргумента для главной функции.

Есть ли лучший или более элегантный способ?Я чувствую, что упускаю из виду нечто очевидное.

Ответы [ 4 ]

2 голосов
/ 26 июля 2019

Я не уверен, что следующее - хорошая идея.Это напоминает мне немного программирования с помощью макросов.Я не думаю, что я бы сделал это.Вам нужно тщательно документировать, потому что это неожиданно, сбивает с толку и не требует пояснений.

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

prepcode <- quote({
  x <- data$x
  y <- data$y
  }
)

foo_1 <- function(data, ...) {
  eval(prepcode)
  # some preparatory code common to all foo_X functions

  # .. do some boring stuff with x and y

  # pack and process the result into 'ret'
  return(list(x, y))
}

L <- list(x = 1, y = "a")
foo_1(L)
#[[1]]
#[1] 1
#
#[[2]]
#[1] "a"

Возможно, было бы лучше иметь prepcode в качестве аргумента для foo_1, чтобы убедиться, что не будет проблем с областями видимости.

1 голос
/ 25 июля 2019

Требования все еще кажутся мне немного расплывчатыми, но если ваш код настолько похож, что вы можете просто обернуть его вокруг вспомогательной функции, такой как tocall в вашем примере, и ваш ввод будет выполнен в виде списка (например,фрейм данных, который является просто списком столбцов), затем просто напишите все ваши foo_* функции, чтобы получить «объединенные» параметры, как в предложенном вами решении, и затем используйте do.call:

foo_1 <- function(x, y) {
  x + y
}

foo_2 <- function(x, y) {
  x - y
}

data <- list(x = 1:2, y = 3:4)

do.call(foo_1, data)
# [1] 4 6

do.call(foo_2, data)
# [1] -2 -2
1 голос
/ 11 июля 2019

Использование with внутри функции:

foo_1 <- function(data, ...) {
  with(data, {
    # .. in here, x and y refer to data$x and data$y
  }
}
0 голосов
/ 25 июля 2019

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

foo_1 <- function(data){
  list2env(data, envir = environment())
  x + y
}

foo_1(data.frame(x = 1:2, y = 3:4))

См. Также этот вопрос .

...