Как вы используете "<< -" (назначение области видимости) в R?

Я только что закончил читать о области видимости во вступлении R, и мне очень любопытно <<- назначение.

Руководство показало один (очень интересный) пример для <<-Я чувствую, что понял. То, что я до сих пор упускаю, это контекст, когда это может быть полезно.

Так что я хотел бы прочитать от вас примеры (или ссылки на примеры) о том, когда использование <<- может быть интересным / полезным. Каковы могут быть опасности его использования (кажется, что его легко не заметить), а также любые советы, которыми вы можете поделиться.

7 ответов

Решение

<<- наиболее полезно в сочетании с замыканиями для поддержания состояния. Вот раздел из моей недавней статьи:

Закрытие - это функция, написанная другой функцией. Замыкания называются так, потому что они включают среду родительской функции и могут обращаться ко всем переменным и параметрам в этой функции. Это полезно, потому что позволяет нам иметь два уровня параметров. Один уровень параметров (родительский) управляет работой функции. Другой уровень (ребенок) делает работу. В следующем примере показано, как можно использовать эту идею для создания семейства степенных функций. Родительская функция (power) создает дочерние функции (square а также cube) которые на самом деле делают тяжелую работу.

power <- function(exponent) {
  function(x) x ^ exponent
}

square <- power(2)
square(2) # -> [1] 4
square(4) # -> [1] 16

cube <- power(3)
cube(2) # -> [1] 8
cube(4) # -> [1] 64

Способность управлять переменными на двух уровнях также позволяет поддерживать состояние при вызовах функций, позволяя функции изменять переменные в среде своего родителя. Ключом к управлению переменными на разных уровнях является оператор присваивания двойной стрелки <<-, В отличие от обычного присваивания одной стрелки (<-), который всегда работает на текущем уровне, оператор двойной стрелки может изменять переменные на родительских уровнях.

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

new_counter <- function() {
  i <- 0
  function() {
    # do something useful, then ...
    i <<- i + 1
    i
  }
}

Новая функция - это замыкание, а ее окружение - это окружающая среда. Когда закрытия counter_one а также counter_two запускаются, каждый изменяет счетчик в своей окружающей среде, а затем возвращает текущий счетчик.

counter_one <- new_counter()
counter_two <- new_counter()

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1

Это помогает думать о <<- как эквивалент assign (если вы установите inherits параметр в этой функции для TRUE). Преимущество assign является то, что он позволяет вам указать больше параметров (например, среды), поэтому я предпочитаю использовать assign над <<- в большинстве случаев.

С помощью <<- а также assign(x, value, inherits=TRUE) означает, что "окружающие среды поставляемой среды ищутся, пока не встретится переменная" x "". Другими словами, он будет продолжать проходить через окружения по порядку, пока не найдет переменную с этим именем, и назначит ее этому. Это может быть в рамках функции или в глобальной среде.

Чтобы понять, что делают эти функции, вам также необходимо понять среду R (например, используя search).

Я регулярно использую эти функции, когда выполняю большую симуляцию и хочу сохранить промежуточные результаты. Это позволяет вам создать объект вне области действия данной функции или apply петля. Это очень полезно, особенно если вас беспокоит неожиданное завершение большого цикла (например, отключение базы данных), и в этом случае вы можете потерять все в процессе. Это было бы эквивалентно записи ваших результатов в базу данных или файл во время длительного процесса, за исключением того, что вместо этого он сохраняет результаты в среде R.

Мое основное предупреждение с этим: будьте осторожны, потому что вы сейчас работаете с глобальными переменными, особенно при использовании <<-, Это означает, что вы можете столкнуться с ситуациями, когда функция использует значение объекта из среды, когда вы ожидали, что оно будет использовать значение, предоставленное в качестве параметра. Это одна из главных вещей, которых функциональное программирование пытается избежать (см. Побочные эффекты). Я избегаю этой проблемы, присваивая свои значения уникальным именам переменных (используя вставку с набором или уникальными параметрами), которые никогда не используются внутри функции, а просто используются для кэширования, и в случае, если мне нужно восстановить позже (или выполнить некоторые метаданные -анализ на промежуточные результаты).

Одно место, где я использовал <<- был в простых графических интерфейсах с использованием tcl/tk. Некоторые из первоначальных примеров имеют это - поскольку вам нужно различать локальные и глобальные переменные для полноты состояния. Смотри например

 library(tcltk)
 demo(tkdensity)

который использует <<-, В противном случае я согласен с Мареком:) - поиск Google может помочь.

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

fortest <- function() {
    mySum <- 0
    for (i in c(1, 2, 3)) {
        mySum <<- mySum + i
    }
    mySum
}

вы можете ожидать, что функция вернет ожидаемую сумму 6, но вместо этого она возвращает 0 с глобальной переменной mySum создается и присваивается значение 3. Я не могу полностью объяснить, что здесь происходит, но, конечно, тело цикла for не является новой областью действия "уровень". Вместо этого кажется, что R смотрит за пределы fortest функция, не могу найти mySum переменная для присвоения, поэтому создает единицу и присваивает значение 1, первый раз через цикл. На последующих итерациях RHS в назначении должен ссылаться на (неизменный) внутренний mySum переменная, тогда как LHS относится к глобальной переменной. Поэтому каждая итерация перезаписывает значение глобальной переменной на значение этой итерации iследовательно, он имеет значение 3 при выходе из функции.

Надеюсь, что это кому-то поможет - это поставило меня в тупик на пару часов сегодня! (Кстати, просто замените << на <, и функция будет работать как положено).

f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")

<<- Оператор также может быть полезен для ссылочных классов при написании ссылочных методов. Например:

myRFclass <- setRefClass(Class = "RF",
                         fields = list(A = "numeric",
                                       B = "numeric",
                                       C = function() A + B))
myRFclass$methods(show = function() cat("A =", A, "B =", B, "C =",C))
myRFclass$methods(changeA = function() A <<- A*B) # note the <<-
obj1 <- myRFclass(A = 2, B = 3)
obj1
# A = 2 B = 3 C = 5
obj1$changeA()
obj1
# A = 6 B = 3 C = 9

Я использую его для изменения внутри map() объекта в глобальной среде.

      a = c(1,0,0,1,0,0,0,0)

Скажем, я хочу получить вектор, который равен c(1,2,3,1,2,3,4,5), то есть если есть 1, пусть он равен 1, иначе добавляйте 1 до следующего 1.

      map(
  .x = seq(1,(length(a))),
  .f = function(x) {
    a[x] <<- ifelse(a[x]==1, a[x], a[x-1]+1)
    })
a
[1] 1 2 3 1 2 3 4 5
Другие вопросы по тегам