¿Cómo vivir con el alcance dinámico de Emacs Lisp?
elisp scope (10)
Además del último párrafo de la respuesta de Gilles, aquí es cómo RMS argumenta a favor del alcance dinámico en un sistema extensible:
Algunos diseñadores de idiomas creen que debe evitarse la vinculación dinámica y, en su lugar, se debe usar la aprobación explícita de argumentos. Imagine que la función A vincula la variable FOO y llama a la función B, que llama a la función C, y C usa el valor de FOO. Supuestamente A debería pasar el valor como argumento a B, que debería pasarlo como argumento a C.
Sin embargo, esto no se puede hacer en un sistema extensible porque el autor del sistema no puede saber cuáles serán todos los parámetros. Imagine que las funciones A y C son parte de una extensión de usuario, mientras que B es parte del sistema estándar. La variable FOO no existe en el sistema estándar; es parte de la extensión Para usar un argumento explícito, pasar requerirá agregar un nuevo argumento a B, lo que significa reescribir B y todo lo que llame a B. En el caso más común, B es el ciclo del despachador del comando del editor, que se llama desde un número horrible de lugares.
Lo que es peor, C también se debe pasar un argumento adicional. B no se refiere a C por nombre (C no existía cuando B fue escrito). Probablemente encuentre un puntero a C en la tabla de envío de comandos. Esto significa que la misma llamada que a veces llama a C también podría llamar a cualquier definición de comando del editor. Entonces, todos los comandos de edición deben reescribirse para aceptar e ignorar el argumento adicional. Por ahora, ¡no queda nada del sistema original!
Personalmente, creo que si hay un problema con Emacs-Lisp, no es el alcance dinámico per se, sino que es el predeterminado, y que no es posible lograr el alcance léxico sin recurrir a extensiones. En CL, se puede usar el alcance dinámico y el léxico, y - a excepción del nivel superior (que es abordado por varias implementaciones deflex) y las variables especiales declaradas globalmente - el valor predeterminado es el alcance léxico. En Clojure, también, puede usar el alcance léxico y dinámico.
Para citar a RMS nuevamente:
No es necesario que el alcance dinámico sea la única regla de alcance provista, solo útil para que esté disponible.
Aprendí Clojure anteriormente y realmente me gusta el lenguaje. También amo a Emacs y he pirateado algunas cosas simples con Emacs Lisp. Sin embargo, hay una cosa que me impide hacer algo más sustancial con Elisp mentalmente. Es el concepto de alcance dinámico. Estoy asustado porque es tan extraño para mí y huele a variables semi-globales.
Entonces, con declaraciones variables, no sé qué cosas son seguras y cuáles peligrosas. Por lo que he entendido, las variables establecidas con setq caen dentro del alcance dinámico (¿es correcto?) ¿Qué pasa con las variables de let? En alguna parte que he leído que permite te permite hacer un alcance léxico simple, pero en otro lugar he leído que los vars también tienen un alcance dinámico.
Lo que más me preocupa es que mi código (utilizando setq o let) accidentalmente rompa algunas variables de la plataforma o código de terceros que llamo o que después de dicha llamada mis variables locales se dañen accidentalmente. ¿Cómo puedo evitar esto?
¿Hay algunas reglas simples que pueda seguir y saber exactamente qué sucede con el alcance sin ser mordido de alguna manera rara y difícil de depurar?
Como señaló Peter Ajtai:
Desde emacs-24.1 puede habilitar el alcance léxico por archivo poniendo
;; -*- lexical-binding: t -*-
encima de tu archivo elisp
El alcance dinámico y léxico tiene comportamientos diferentes cuando se usa un fragmento de código en un ámbito diferente del que se definió en. En la práctica, hay dos patrones que cubren la mayoría de los casos problemáticos:
Una función sombrea una variable global, luego llama a otra función que usa esa variable global.
(defvar x 3) (defun foo () x) (defun bar (x) (+ (foo) x)) (bar 0) ⇒ 0
Esto no aparece a menudo en Emacs porque las variables locales tienden a tener nombres cortos (a menudo de una sola palabra) mientras que las variables globales tienden a tener nombres largos (a menudo prefijados por nombre de
packagename-
). Muchas funciones estándar tienen nombres que son tentadores de usar como variables locales, comolist
ypoint
, pero las funciones y variables viven en espacios de nombre separados, las funciones locales no se utilizan con mucha frecuencia.Una función se define en un contexto léxico y se usa fuera de este contexto léxico porque se pasa a una función de orden superior.
(let ((cl-y 10)) (mapcar* (lambda (elt) (* cl-y elt)) ''(1 2 3))) ⇒ (10 20 30) (let ((cl-x 10)) (mapcar* (lambda (elt) (* cl-x elt)) ''(1 2 3))) ⇑ (wrong-type-argument number-or-marker-p (1 2 3))
El error se debe al uso de
cl-x
como nombre de variable enmapcar*
(del paquetecl
). Tenga en cuenta que el paquetecl
utilizacl-
como prefijo incluso para sus variables locales en funciones de orden superior. Esto funciona razonablemente bien en la práctica, siempre que tenga cuidado de no utilizar la misma variable como nombre global y como nombre local, y no necesita escribir una función recursiva de orden superior.
La edad de PS Emacs Lisp no es la única razón por la cual tiene un alcance dinámico. Es cierto que en esos días, los ceceos tendían a un alcance dinámico: Scheme y Common Lisp aún no habían comenzado. Pero el alcance dinámico también es un activo en un lenguaje dirigido a extender un sistema de forma dinámica: te permite conectarte a más lugares sin ningún esfuerzo especial. Con gran poder viene una gran cuerda para ahorcarse: te arriesgas a engancharte accidentalmente en un lugar que no conocías.
En primer lugar, elisp tiene enlaces de funciones y variables separados, por lo que algunos inconvenientes del alcance dinámico no son relevantes.
En segundo lugar, aún puede usar setq
para establecer variables, pero el valor establecido no sobrevive a la salida del alcance dinámico en el que se realiza. Esto no es, fundamentalmente, diferente del alcance léxico, con la diferencia de que con el alcance dinámico de un setq en una función a la que llama puede afectar el valor que ve después de la llamada a la función.
Hay lexical-let
, una macro que (esencialmente) imita enlaces léxicos (creo que hace esto caminando el cuerpo y cambiando todas las ocurrencias de las variables lexically let a un nombre gensymmed, eventualmente desentrañando el símbolo), si es absolutamente necesario.
Yo diría "escribir código como es normal". Hay momentos en que la naturaleza dinámica de elisp te morderá, pero he descubierto que en la práctica eso es sorprendentemente raro.
Aquí hay un ejemplo de lo que estaba diciendo acerca de las variables setq y dynamic-bound (recientemente evaluadas en un buffer de scratch cercano):
(let ((a nil))
(list (let ((a nil))
(setq a ''value)
a)
a))
(value nil)
Las otras respuestas son buenas para explicar los detalles técnicos sobre cómo trabajar con alcance dinámico, así que aquí está mi consejo no técnico:
Solo hazlo
He estado jugando con Emacs lisp por más de 15 años y no sé si alguna vez me han picado los problemas debido a las diferencias entre el alcance léxico / dinámico.
Personalmente, no he encontrado la necesidad de cierres (los amo, simplemente no los necesito para Emacs). Y, generalmente trato de evitar las variables globales en general (si el alcance fue léxico o dinámico).
Así que sugiero saltar y escribir personalizaciones que se adapten a sus necesidades / deseos, es probable que no tenga ningún problema.
Siento tu dolor por completo Encuentro la falta de enlace léxico en emacs algo molesto, especialmente no poder usar cierres léxicos, que parece ser una solución que pienso mucho, proveniente de lenguajes más modernos.
Si bien no tengo más consejos sobre cómo solucionar las características deficientes que las respuestas anteriores aún no abarcaban, me gustaría señalar la existencia de una rama de emacs llamada `lexbind '', que implementa la vinculación léxica de forma retroactiva. manera compatible. En mi experiencia, los cierres léxicos todavía son un poco problemáticos en algunas circunstancias, pero esa rama parece tener un enfoque prometedor.
Simplemente no lo hagas
Emacs-24 le permite usar el alcance léxico. Solo corre
(setq lexical-binding t)
o agregar
;; -*- lexical-binding: t -*-
al comienzo de tu archivo.
Todo lo que se ha escrito aquí vale la pena. Yo agregaría esto: conozca Common Lisp , si nada más, lea sobre ello. CLTL2 presenta una vinculación léxica y dinámica, al igual que otros libros. Y Common Lisp los integra bien en un solo idioma.
Si lo "obtiene" después de una cierta exposición a Common Lisp, entonces las cosas serán más claras para usted para Emacs Lisp. Emacs 24 utiliza el ámbito léxico en mayor medida de forma predeterminada que las versiones anteriores, pero el enfoque de Common Lisp será aún más claro y más limpio (en mi humilde opinión). Finalmente, es definitivamente el caso que el alcance dinámico es importante para Emacs Lisp, por las razones que RMS y otros han enfatizado.
Así que mi sugerencia es llegar a saber cómo Common Lisp se ocupa de esto. Trata de olvidarte de Scheme, si ese es tu principal modelo mental de Lisp: te limitará más que ayudarte a comprender el alcance, los personajes, etc. en Emacs Lisp. Emacs Lisp, como Common Lisp, es "sucio y de baja altura"; no es Scheme.
¿Hay algunas reglas simples que pueda seguir y saber exactamente qué sucede con el alcance sin ser mordido de alguna manera rara y difícil de depurar?
Lee la Referencia de Emacs Lisp , tendrás muchos detalles como este:
- Forma especial: setq [forma de símbolo] ... Esta forma especial es el método más común para cambiar el valor de una variable. Cada SÍMBOLO recibe un nuevo valor, que es el resultado de evaluar el FORMULARIO correspondiente. El enlace más local existente del símbolo se cambia .
Aquí hay un ejemplo :
(defun foo () (setq tata "foo"))
(defun bar (tata) (setq tata "bar"))
(foo)
(message tata)
===> "foo"
(bar tata)
(message tata)
===> "foo"
No es tan malo.
Los principales problemas pueden aparecer con ''variables libres'' en funciones.
(defun foo (a)
(* a b))
En la función anterior, a
es una variable local. b
es una variable libre. En un sistema con enlace dinámico como Emacs Lisp, b
buscará en el tiempo de ejecución. Ahora hay tres casos:
-
b
no está definido -> error -
b
es una variable local ligada por alguna llamada a función en el ámbito dinámico actual -> toma ese valor -
b
es una variable global -> toma ese valor
Los problemas pueden ser:
- un valor encuadernado (global o local) es ensombrecido por una llamada a la función, posiblemente no deseada
- una variable indefinida NO está sombreada -> error de acceso
- una variable global NO se sombrea -> recoge el valor global, que podría ser no deseado
En un Lisp con un compilador, la compilación de la función anterior podría generar una advertencia de que hay una variable libre. Normalmente, los compiladores Common Lisp lo harán. Un intérprete no proporcionará esa advertencia, uno solo verá el efecto en tiempo de ejecución.
Consejo :
- asegúrese de no usar accidentalmente variables libres
- asegúrese de que las variables globales tengan un nombre especial, de modo que sean fáciles de detectar en el código fuente, generalmente
*foo-var*
No escribir
(defun foo (a b)
...
(setq c (* a b)) ; where c is a free variable
...)
Escribir:
(defun foo (a b)
...
(let ((c (* a b)))
...)
...)
Vincula todas las variables que quieras usar y asegúrate de que no estén vinculadas en otro lugar.
Eso es básicamente eso.
Dado que GNU Emacs versión 24 vinculante léxico es compatible con su Emacs Lisp. Consulte: Enlace léxico, GNU Emacs Lisp Manual de referencia .