Почему вычисление выражения в system.time() делает переменные доступными в глобальной среде?

#r #scope

#r #область видимости

Вопрос:

Может кто-нибудь, пожалуйста, объяснить, что происходит, когда выражение вычисляется в system.time ? В частности, почему любые переменные, объявленные в expr аргументе, видны в глобальной среде?

Вот сокращенная версия внутренностей system.time , которая не делает ничего, кроме вычисления выражения, которое передается функции:

 st <- function(expr){
  expr
}

st(aa <- 1)
aa
[1] 1
 

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

Что здесь происходит?

Ответ №1:

Это связано с тем, что предоставленные аргументы вычисляются во фрейме вычисления вызывающей функции (как описано в разделе 4.3.3 документа определения языка R).

Выражение , заключенное пользователем в system.time() , является предоставленным аргументом , который сопоставляется позиционно expr . Затем, когда expr вычисление принудительно выполняется в теле system.time , оно вычисляется в фрейме оценки вызывающей функции. Если system.time() было вызвано из .GlobalEnv , то именно там будут выполняться любые назначения, которые являются частью expr .

Редактировать:

Вот пример, показывающий, что expr это вычисляется в глобальной среде, если это предоставленный (но не используемый по умолчанию) аргумент.

 st2 <- function(expr = newVar <- 33){
   expr
}

# Using the default argument -- eval and assignment 
# within evaluation frame of the function. 
st2()
newVar
Error: object 'newVar' not found

# Using a supplied argument -- eval and assignment
# within the calling function's evaluation frame (here .GlobalEnv)
st2(newVar <- 44)
newVar
# [1] 44
 

Комментарии:

1. Я могу поверить вам на слово, но начислю бонусные баллы, если вы сможете найти ссылку в руководствах R. 🙂

2. Ошибаюсь, ваш ответ ( 1) кажется моей интуиции гораздо более ясным 🙂

3. @Andrie — я хотел поблагодарить вас за интересный вопрос. Считается ли моя супер-слабая ссылка на определение языка R ссылкой на руководства R? 😉

4. 1 Ваша ссылка на руководства совсем не слабая и очень полезная. Спасибо.

Ответ №2:

РЕДАКТИРОВАТЬ: согласно комментарию @Tommy’s: Оценка фактически выполняется только после использования аргумента expr (это отложенная оценка).

Передается языковой объект, а не выражение. По сути, вы вкладываете <- функцию (с двумя аргументами) в вызов функции st(), и результат <- вызова передается в st . Как вы можете видеть в ?assignOps , <- функция возвращает присвоенное значение автоматически. Как @Josh уже говорил вам, эта оценка вложенной функции выполняется в среде, из которой вызывается функция.

То, что вы делаете, эквивалентно

 st(mean(1:10))
 

Чтобы увидеть разницу, вы можете сделать:

 st <- function(expr){
  typeof(expr)
}
> st(aa <- 1)
[1] "double"
> st(expression(aa <- 1))
[1] "expression"
 

Для структуры вызова вы можете сделать:

 st <- function(expr){
  str(as.list(match.call()))
}
> st(mean(1:10))
List of 2
 $     : symbol st
 $ expr: language mean(1:10)
> st(aa <- 1)
List of 2
 $     : symbol st
 $ expr: language aa <- 1
 

Комментарии:

1. Неверно говорить aa <- 1 , что это вычисляется перед вызовом st . Оно вычисляется только тогда, когда оно ИСПОЛЬЗУЕТСЯ в st функции. Попробуйте st <- function(expr) NULL , а затем st(stop('foo')) . Выражение stop не используется в st , поэтому вызов никогда не завершается ошибкой…

2. … также обратите внимание, что typeof(expr) вычисляется expr для проверки его типа.

Ответ №3:

Я думаю expr , что вычисляется перед обработкой функции. Пример POC:

 > st <- function(expr){
    eval(parse(text=expr))
  }
> 
> st('aa <- 1')
> aa
Error: object 'aa' not found
 

Поэтому я думаю, что функция получает expr только as aa . Другой пример:

 > st <- function(expr){
    str(expr)
  }
> 
> st(aa <- 1)
 num 1
 

Возможно, я ошибаюсь, это скорее интуиция 🙂 Но спасибо, это хорошая головоломка!


Обновить:

 > system.time(a <- 1)
   user  system elapsed 
      0       0       0 
> a
[1] 1
> rm(a)
> fn <- function() a <- 1
> system.time(fn())
   user  system elapsed 
      0       0       0 
> a
Error: object 'a' not found
 

Комментарии:

1. В исходном коде для system.time вы заметите, что expr это вычисляется только после выполнения нескольких действий по ведению домашнего хозяйства (например, запуска часов). Ваша модифицированная функция принимает строку в качестве входных данных, а не бит кода (как это делает system.time ).

2. @Andrie: это верно, что expr можно найти позже system.time , но это не влияет на основную идею моего ответа. Смотрите обновленные примеры.