Как вы используете «<< -» (обзорное назначение) в R? - PullRequest
117 голосов
/ 13 апреля 2010

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

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

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

Ответы [ 6 ]

165 голосов
/ 13 апреля 2010

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

Закрытие - это функция, написанная другой функцией.Замыкания называются так, потому что они охватывают окружение родительской функции и могут обращаться ко всем переменным и параметрам в этой функции.Это полезно, потому что позволяет нам иметь два уровня параметров.Один уровень параметров (родительский) управляет работой функции.Другой уровень (ребенок) делает работу.В следующем примере показано, как можно использовать эту идею для создания семейства степенных функций.Родительская функция (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
33 голосов
/ 13 апреля 2010

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

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

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

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

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

7 голосов
/ 13 апреля 2010

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

 library(tcltk)
 demo(tkdensity)

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

4 голосов
/ 13 апреля 2010
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
2 голосов
/ 15 июня 2015

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

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
2 голосов
/ 15 июня 2015

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

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

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

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

...