справяне с капана на динамичния обхват на 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 от Боб се сблъскват. В старите времена какво биха могли да направят Алис и/или Боб, за да предотвратят този вид сблъсък?

Може би Боб може да промени docstring на

"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

В допълнение към казаното от lunaryorn и Stefan:

В конкретния пример, който дадохте, 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
Не е специфично за Elisp. Можете да намерите много информация за него, като потърсите в Google спрямо Lisp като цяло. Официалният документ на Common Lisp също го описва доста добре. В старите времена това се наричаше проблем с funarg (всъщност има проблеми с funarg както надолу, така и нагоре), така че funarg също може да бъде полезна дума за търсене тук. - person Drew; 02.11.2013