работа с ловушкой динамической области Emacs Lisp в старые времена

Раньше Emacs не поддерживал лексическую область видимости. Мне интересно, как в те дни люди справлялись с определенной ловушкой динамической области видимости.

Предположим, что Алиса пишет команду my-insert-stuff, которая опирается на функцию fp-repeat, определенную в fp.el (которая, как мы предполагаем, представляет собой библиотеку, предоставляющую множество функций для функционального программирования, написанную Бобом), и предположим, что fp-repeat предназначена для многократного многократного вызова функции.

Часть содержимого init.el от Алисы:

(require 'fp)

(defun my-insert-stuff ()
  (interactive)
  ;; inserts "1111111111\n2222222222\n3333333333" to current buffer
  (dolist (i (list "1" "2" "3"))
    (fp-repeat 10
               (lambda ()
                 (insert i)))
    (insert "\n")))

Часть содержимого fp.el от Боба:

(defun fp-repeat (n func)
  "Calls FUNC repeatedly, N times."
  (dotimes (i n)
    (funcall func)))

Вскоре Алиса обнаруживает, что ее команда работает не так, как она ожидала. Это потому, что использование Алисой i и использование Бобом i противоречат друг другу. В прежние времена, что могли сделать Алиса и/или Боб, чтобы предотвратить такое столкновение?

Возможно, Боб мог бы изменить строку документации на

"Calls FUNC repeatedly, N times.
Warning: Never use i, n, func in FUNC body as nonlocal variables."

person Jisang Yoo    schedule 15.08.2013    source источник


Ответы (3)


Алиса позаботилась бы о том, чтобы не использовать нелокальные переменные в телах lambda, зная, что lambda не создает лексических замыканий.

В Emacs Lisp этой простой политики на самом деле достаточно, чтобы избежать большинства проблем с динамической областью видимости, потому что при отсутствии параллелизма локальные let-привязки динамических переменных в основном эквивалентны лексическим привязкам.

Другими словами, разработчики Emacs Lisp в «старые времена» (которые не так уж и стары, учитывая все еще существующее количество Emacs Lisp с динамической областью видимости) не написали бы такой lambda. Они бы даже не захотели, потому что Emacs Lisp не был функциональным языком (и до сих пор таковым не является), поэтому циклы и явные итерации часто предпочтительнее, чем функции более высокого порядка.

Что касается вашего конкретного примера, Алиса «старых времен» просто написала бы два вложенных цикла.

person lunaryorn    schedule 15.08.2013
comment
Функция высшего порядка mapcar всегда активно использовалась (и в меньшей степени mapc и mapconcat). Пакет CL добавляет множество очень полезных функций высшего порядка: mapcan, mapcon, maplist, some, every, notany, notevery, reduce, remove-if, remove-if-not и т. д. Отказ от использования функциональное программирование. - person to_the_crux; 02.11.2013
comment
@to_the_crux О, дорогой, пожалуйста, прочитай вопрос и помести мой ответ в его контекст. Я не отказываюсь от функционального программирования. Я просто констатирую тот факт, что большая часть кода Emacs Lisp предпочитает явную итерацию функциям более высокого порядка. Конечно, в Emacs есть такие функции, как вы заметили, но они не часто использовались (до сих пор?) с лямбда-выражениями и еще реже с замыканиями именно потому, что в Emacs долгое время не было замыканий. И по сей день Emacs Lisp не является функциональным языком. В лучшем случае это императивный язык, использующий некоторые методы FP. - person lunaryorn; 02.11.2013

Emacs решил эту проблему, следуя очень строгому соглашению: программисты на Elisp, пишущие функции более высокого порядка (такие как fp-repeat), должны были использовать необычные имена переменных, когда луч света сообщал им, что функцию можно использовать. другими, и когда луч света не работал, они должны были совершать свои ежедневные молитвы (всегда хорошая идея в церкви Emacs).

person Stefan    schedule 15.08.2013

В дополнение к тому, что сказали лунариорн и Стефан:

В конкретном примере, который вы привели, funarg, переданный fp-repeat, на самом деле НЕ нуждается в переменной i вообще.

То есть с i КАК ПЕРЕМЕННОЙ ничего делать не нужно. То есть не нужно использовать i как конкретный СИМВОЛ, значение которого должно быть определено в определенное время или в определенном контексте (среде), когда и где вызывается функция.

Все, что действительно нужно этой функции, — это ЗНАЧЕНИЕ i, когда и где функция определена. Использование переменной в этом конкретном случае является излишним --- нужно только ее значение.

Таким образом, еще один способ заправить иглу — подставить значение переменной в определение функции, т. е. в выражение lambda:

 (defun my-insert-stuff ()
   (interactive)
   (dolist (i (list "1" "2" "3"))
     (fp-repeat 10 `(lambda () (insert ',i)))
     (insert "\n")))

Это прекрасно работает. Захват переменных невозможен, поскольку нет переменных.

Недостатком является то, что во время компиляции также нет функции: создается СПИСОК, чей car равен lambda и т. д. И этот список затем оценивается во время выполнения, интерпретируется как функция.

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

person Drew    schedule 15.08.2013
comment
Спасибо вам и @Stefan за ваши ответы. Мне было интересно, как избежать конфликтов имен в elisp с динамической областью действия, и, похоже, в официальной документации очень мало информации по этой теме. - person to_the_crux; 02.11.2013
comment
Это не относится к Элиспу. Вы можете найти много информации об этом, погуглив Lisp в целом. Официальный документ Common Lisp также описывает это довольно хорошо. В старые времена это упоминалось как задача funarg (на самом деле существуют как нисходящие, так и восходящие задачи funarg), так что funarg также может быть полезным поисковым термином здесь. - person Drew; 02.11.2013