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

144

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

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

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

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

Ответы:

202

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

Замыкание - это функция, написанная другой функцией. Замыкания называются так, потому что они охватывают среду родительской функции и могут получить доступ ко всем переменным и параметрам в этой функции. Это полезно, потому что позволяет нам иметь два уровня параметров. Один уровень параметров (родительский) контролирует работу функции. Другой уровень (ребенок) выполняет работу. В следующем примере показано, как можно использовать эту идею для создания семейства степенных функций. Родительская функция ( 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
Хэдли
источник
4
Привет, это нерешенная задача R на Rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Ну, это было ...
Карстен В.
1
Будет ли необходимость заключать более 1 замыкания в одну родительскую функцию? Я просто попробовал один фрагмент, кажется, было выполнено только последнее закрытие ...
mckf111 03
Есть ли альтернатива знаку равенства знаку «<< -»?
Genom
40

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

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

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

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

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

Шейн
источник
3
Спасибо, Тал. У меня есть блог, хотя я им особо не пользуюсь. Я никогда не смогу закончить пост, потому что не хочу публиковать ничего, кроме идеального, и у меня просто нет на это времени ...
Шейн
2
Один мудрый человек однажды сказал мне, что не важно быть идеальным - важно только выделяться, - а вы и есть ваши посты. Также - иногда читатели помогают улучшить текст комментариями (вот что происходит с моим блогом). Надеюсь, однажды вы передумаете :)
Тал Галили
9

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

 library(tcltk)
 demo(tkdensity)

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

Дирк Эддельбюттель
источник
Интересно, как-то не могу найти tkdensityв R 3.6.0.
NelsonGon
1
Пакет tcltk поставляется с R: github.com/wch/r-source/blob/trunk/src/library/tcltk/demo/…
Дирк Эддельбюттель,
5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
lcgong
источник
11
Это хороший пример того, где не стоит пользоваться <<-. В этом случае цикл for будет более понятным.
Хадли
5

По этому поводу я хотел бы отметить, что <<-оператор будет вести себя странно при применении (неправильно) в цикле 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.

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

Мэтью Уайз
источник
2
в вашем примере локальный mySumникогда не увеличивается, а только глобальный mySum. Следовательно, на каждой итерации цикла for значение global mySumполучает значение 0 + i. Вы можете следить за этим с помощью debug(fortest).
ClementWalter
Это не имеет ничего общего с тем, что это цикл for; вы ссылаетесь на две разные области. Просто используйте <-везде последовательно внутри функции, если вы хотите обновить только локальную переменную внутри функции.
smci
Или используйте << - везде @smci. Хотя лучше избегать глобалов.
Статистика обучения на примере
3

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

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
Карлос Чинелли
источник