/ / Umgang mit einem Emacs Lisp dynamischen Bereich Fallstrick in alten Tagen - Emacs, elisp, Dynamic-Scope

mit einem Emacs Lisp dynamischen Spielraumfall in alten Tagen - Emacs, elisp, dynamic-scope

In den alten Zeiten hatte Emacs keine Unterstützung für den lexikalischen Geltungsbereich. Ich frage mich, wie die Menschen in diesen Tagen mit einer besonderen Falle des dynamischen Umfangs umgegangen sind.

Angenommen, Alice schreibt einen Befehl my-insert-stuff was auf die beruht fp-repeat Funktion definiert in fp.el (was wir für eine Bibliothek halten, die viele Funktionen für die von Bob geschriebene funktionale Programmierung bietet), und nehmen an fp-repeat ist für den wiederholten Aufruf einer Funktion viele Male.

Teil des Inhalts von init.el von Alice:

(require "fp)

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

Teil des Inhalts von fp.el von Bob:

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

Alice findet bald heraus, dass ihr Befehl nicht so funktioniert, wie sie es erwartet. Das liegt daran, dass Alice sie benutzt i und Bobs Verwendung von i kollidieren. In den alten Tagen, was konnte Alice oder / und Bob tun, um diese Art von Kollision zu verhindern?

Vielleicht könnte Bob den Docstring ändern

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

Antworten:

5 für die Antwort № 1

Alice hätte darauf geachtet, keine nicht lokalen Variablen zu verwenden lambda Körper, bewusst sein, dass lambda hat keine lexikalischen Verschlüsse erstellt.

In Emacs Lisp ist diese einfache Richtlinie tatsächlich ausreichend, um die meisten Probleme mit dynamischem Scoping zu vermeiden, da in Abwesenheit von Nebenläufigkeit lokal let-Bindungen dynamischer Variablen sind meist lexikalischen Bindungen gleichwertig.

Mit anderen Worten, Emacs Lisp Entwickler der "alten Tage" (die noch nicht so alt sind, da die Menge an dynamischem Emacs Lisp immer noch da ist) hätten kein a geschrieben lambda so wie das. Sie hätten es nicht einmal gewollt, weil Emacs Lisp keine funktionale Sprache war (und immer noch nicht ist), weshalb Schleifen und expliziter Iteration oft gegenüber Funktionen höherer Ordnung bevorzugt wurden.

In Bezug auf Ihr spezifisches Beispiel hätte Alice der "alten Tage" nur zwei verschachtelte Schleifen geschrieben.


4 für die Antwort № 2

Die Art, wie Emacs mit dem Problem umgegangen ist, folgt einer sehr strengen Konvention: Elisp-Programmierer schreiben Funktionen höherer Ordnung (wie z fp-repeat) sollten ungewöhnliche Variablennamen verwendet werdenals ein Lichtstrahl sie darauf aufmerksam machte, dass die Funktion von anderen benutzt werden konnte, und wenn der Lichtstrahl nicht funktionierte, wurde von ihnen erwartet, dass sie ihre täglichen Gebete (immer eine gute Idee in der Kirche von Emacs) gaben.


3 für die Antwort № 3

Zusätzlich zu dem, was Mondelfen und Stefan gesagt haben:

In dem bestimmten Beispiel, das du gabst, ging der Funarg über fp-repeat braucht tatsächlich keine Variable i überhaupt.

Das heißt, es braucht nichts damit zu tun i Als eine Variable. Das heißt, es muss nicht verwendet werden i als ein bestimmtes SYMBOL, dessen Wert zu einem bestimmten Zeitpunkt oder in einem bestimmten Kontext (Umgebung) bestimmt werden soll, wenn & wo die Funktion aufgerufen wird.

Alles, was diese Funktion wirklich braucht, ist der Wert von i Wann und wo ist die Funktion? definiert. Die Verwendung einer Variablen in diesem speziellen Fall ist Overkill --- es wird nur der Wert benötigt.

Also ein anderer Weg, um die Nadel zu fädeln ist Ersetzen Sie den Wert für die Variable in der Definition der Funktion, d.h. in der lambda Ausdruck:

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

Das funktioniert gut. Es gibt keine Möglichkeit der variablen Erfassung, weil Es gibt keine Variable.

Der Nachteil ist, dass es zur Kompilierzeit auch keine Funktion gibt: Es wird eine LIST aufgebaut, deren car ist lambda etc. Und diese Liste wird dann zur Laufzeit ausgewertet und als Funktion interpretiert.

Je nach konkretem Anwendungsfall kann dies einnützlicher Weg zu gehen. Ja, das bedeutet, dass Sie Kontexte unterscheiden müssen, in denen Sie wirklich eine Variable verwenden müssen (was die Funktion macht, verwendet VARIABLE) i, nicht nur ein Wert).