В R, как сделать переменные внутри функции доступными для функции нижнего уровня внутри этой функции? (With, attach, environment)

Обновить 2 @G. Гротендик опубликовал два подхода. Второй - изменение функциональной среды внутри функции. Это решает мою проблему слишком большого количества копий. Я не уверен, что это хороший способ пройти проверку CRAN, когда мои скрипты попадают в пакет. Я еще раз обновлюсь, когда у меня появятся некоторые выводы.

Обновить

Я пытаюсь передать много переменных входных аргументов в f2 и не хочу индексировать каждую переменную внутри функции как env$c, env$d, env$calls, поэтому я пытался использовать with f5 и f6 ( измененный f2). Однако assign не работает with внутри {}, перемещение assign снаружи with будет делать эту работу, но в моем реальном случае у меня есть несколько assign внутри with выражениями, которые я не знаю, как переместить их из with функцией легко,

Вот пример:

## In the <environment: R_GlobalEnv>
a <- 1
b <- 2
f1 <- function(){
    c <- 3
d <- 4
f2 <- function(P){
    assign("calls", calls+1, inherits=TRUE)
    print(calls)
    return(P+c+d)
 }
calls <- 0
v <- vector()
for(i in 1:10){
    v[i] <- f2(P=0)
    c <- c+1
    d <- d+1
  }
 return(v)
}
f1()

Функция f2 находится внутри f1, когда вызывается f2, она ищет calls,c,d переменных calls,c,d в среде environment(f1). Это то, чего я хотел.

Однако, когда я хочу использовать f2 также в других функциях, я буду определять эту функцию в глобальной среде, назовите ее f4.

f4 <- function(P){
  assign("calls", calls+1, inherits=TRUE)
  print(calls)
  return(P+c+d)
}

Это не сработает, потому что он будет искать calls,c,d в глобальной среде, а не внутри функции, где вызывается функция. Например:

f3 <- function(){
  c <- 3
  d <- 4
  calls <- 0
  v <- vector()
  for(i in 1:10){
    v[i] <- f4(P=0) ## or replace here with f5(P=0)
    c <- c+1
    d <- d+1
  }
  return(v)
}
f3()

Безопасный путь должен определять calls,c,d во входных аргументах f4 а затем передавать эти параметры в f4. Однако в моем случае слишком много переменных передается в эту функцию f4 и было бы лучше, если бы я мог передать ее как среду и сказать, что f4 не смотрят в глобальную среду (environment(f4)), только посмотрите внутри environment когда вызывается f3.

Как мне решить эту проблему сейчас, чтобы использовать окружающую среду в виде списка и использовать with функцией.

f5 <- function(P,liste){
  with(liste,{
     assign("calls", calls+1, inherits=TRUE)
     print(calls)
     return(P+c+d)
     }
  )
}
f3 <- function(){
  c <- 3
  d <- 4
  calls <- 0
  v <- vector()
  for(i in 1:10){
    v[i] <- f5(P=0,as.list(environment())) ## or replace here with f5(P=0)
    c <- c+1
    d <- d+1
  }
  return(v)
}
f3()

Однако теперь assign("calls", calls+1, inherits=TRUE) не работают так, как должно быть, поскольку assign не изменяет исходный объект. Переменные calls связаны с функцией оптимизации, где целевой функцией является f5. Именно по этой причине я использую assign вместо передачи calls в качестве входных аргументов. Использование attach также не ясно для меня. Вот мой способ исправить проблему с assign:

f7 <- function(P,calls,liste){
  ##calls <<- calls+1
  ##browser()
  assign("calls", calls+1, inherits=TRUE,envir = sys.frame(-1))
  print(calls)
  with(liste,{
    print(paste('with the listed envrionment, calls=',calls))
    return(P+c+d)
  }
  )
}
########
##################
f8 <- function(){
  c <- 3
  d <- 4
  calls <- 0
  v <- vector()
  for(i in 1:10){
    ##browser()
    ##v[i] <- f4(P=0) ## or replace here with f5(P=0)
    v[i] <- f7(P=0,calls,liste=as.list(environment()))
    c <- c+1
    d <- d+1
  }
  f7(P=0,calls,liste=as.list(environment()))
  print(paste('final call number',calls))
  return(v)
}
f8()

Я не уверен, как это должно быть сделано в R. Является ли я в правильном направлении, особенно когда вы проходите проверку CRAN? У кого-нибудь есть намеки на это?

Ответ 1

(1) Передать зону вызова. Вы можете явно передать родительскую среду и индекс в нее. Попробуй это:

f2a <- function(P, env = parent.frame()) {
    env$calls <- env$calls + 1
    print(env$calls)
    return(P + env$c + env$d)
}

a <- 1
b <- 2
# same as f1 except f2 removed and call to f2 replaced with call to f2a
f1a <- function(){
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2a(P=0)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1a()

(2) Сброс вызываемой функциональной среды - это сброс среды f2b в f1b как показано здесь:

f2b <- function(P) {
    calls <<- calls + 1
    print(calls)
    return(P + c + d)
}

a <- 1
b <- 2
# same as f1 except f2 removed, call to f2 replaced with call to f2b
#  and line marked ## at the beginning is new
f1b <- function(){
    environment(f2b) <- environment() ##
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2b(P=0)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1b()

(3) Макрос использованием eval.parent(substitute (...)) Еще один подход заключается в определении макроподобной конструкции, которая эффективно вводит тело f2c inline в f1c1. Здесь f2c совпадает с f2b за исключением calls <- calls + 1 line (no <<- needed) и обертывания всего тела в eval.parent(substitute({...})). f1c совпадает с f1a за исключением того, что вызов f2a заменяется вызовом f2c.

f2c <- function(P) eval.parent(substitute({
    calls <- calls + 1
    print(calls)
    return(P + c + d)
}))

a <- 1
b <- 2
f1c <- function(){
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2c(P=0)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1c()

(4) defmacro. Это почти то же самое, что и последнее решение, за исключением того, что в пакете gtools используется defmacro чтобы определить макрос, а не делать это самостоятельно. (Также см. Пакет Rcmdr для другой версии defmacro.) Из-за того, как работает defmacro мы также должны передавать calls но поскольку это макрос, а не функция, это просто говорит, что он заменяет calls и не совпадает с передачей calls на функция.

library(gtools)

f2d <- defmacro(P, calls, expr = {
    calls <- calls + 1
    print(calls)
    return(P + c + d)
})

a <- 1
b <- 2
f1d <- function(){
    c <- 3
    d <- 4
    calls <- 0
    v <- vector()
    for(i in 1:10){
        v[i] <- f2d(P=0, calls)
        c <- c+1
        d <- d+1
      }
     return(v)
}
f1d()

Ответ 2

В общем, я бы сказал, что любая переменная, которая нужна внутри функции, должна передаваться через ее аргументы. Кроме того, если его значение необходимо позже, вы передаете его обратно из функции. Не делать этого может довольно быстро привести к странным результатам, например, что, если есть несколько функций, определяющих переменную x, которую нужно использовать. Если количество переменных больше, вы создаете для него настраиваемую структуру данных, например, помещая их в именованный список.

Ответ 3

Можно также использовать функцию, которая переопределяет другие функции в указанной среде.

test_var <- "global"

get_test_var <- function(){
  return(test_var)
}

some_function <- function(){
  test_var <- "local"
  return(get_test_var()) 

}

some_function() # Returns "global". Not what we want here...

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

some_function2 <- function(){
  test_var <- "local"
  # define function locally
  get_test_var2 <- function(){
    return(test_var)
  }
  return(get_test_var2()) 
}

some_function2() # Returns "local", but 'get_test_var2' can't be used in other places.

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

add_function_to_envir <- function(my_function_name, to_envir) {
  script_text <- capture.output(eval(parse(text = my_function_name)))
  script_text[1] <- paste0(my_function_name, " <- ", script_text[1])
  eval(parse(text = script_text), envir = to_envir)
}

some_function3 <- function(){
  test_var <- "local"
  add_function_to_envir("get_test_var", environment()) 
  return(get_test_var()) 
}

some_function3() # Returns "local" and we can use 'get_test_var' from anywhere.

Здесь add_function_to_envir(my_function_name, to_envir) захватывает скрипт функции, анализирует и переоценивает ее в новой среде.

Примечание: имя функции для my_function_name должно быть в кавычках.