2010-09-24 8 views
48

He aprendido Clojure anteriormente y realmente me gusta el idioma. 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.¿Cómo convivir con el alcance dinámico de Emacs Lisp?

Así que con las declaraciones de variables no sé qué cosas son seguras para hacer y cuáles son peligrosas. Por lo que he entendido, las variables establecidas con setq entran 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.

Mi principal preocupación es que mi código (utilizando setq o let) accidentalmente rompe algunas variables de la plataforma o código de terceros que llamo o que después de dicha llamada mis variables locales se equivocan accidentalmente. ¿Cómo puedo evitar esto?

¿Hay algunas reglas simples que pueda seguir y saber exactamente lo que sucede con el alcance sin ser mordido de alguna manera extraña y difícil de depurar?

+0

¡Respuestas impresionantes, gracias! – auramo

+0

¡Muy buena pregunta! –

Respuesta

10

En primer lugar, elisp tiene enlaces de funciones y variables por separado, 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 el alcance dinámico de un setq en una función que usted 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 esto hace caminando el cuerpo y cambiando todas las ocurrencias de las variables lexically let a un nombre gensymmed, eventualmente desenterrando el símbolo), si es absolutamente necesario a.

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.

He aquí un ejemplo de lo que estaba diciendo acerca setq y variables ligadas dinámicamente (evaluado recientemente en una cercana rayar buffer):

(let ((a nil)) 
    (list (let ((a nil)) 
      (setq a 'value) 
      a) 
     a)) 

(value nil) 
+0

Hmm, para mí setq parece fundamentalmente muy diferente al alcance léxico ... casi global. 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 la llamada mis variables se dañen accidentalmente. ¿Cómo puedo evitar esto? ¿Con las convenciones de nombres? ¿Es incluso un miedo relevante? – auramo

+1

En elisp, establece una nueva vinculación dinámica, todo lo que se hace a la variable dentro de este enlace se detiene cuando el final del alcance dinámico (cuando se ejecuta) pasa fuera del bloque de bloqueo. Los argumentos de funciones funcionan igual (se establece un nuevo enlace). – Vatine

+1

Además, mi ejemplo muestra dos ámbitos dinámicos, uno dentro de otro, con el interno modificando la variable enlazada dinámicamente y la vinculación "externa" no se ve afectada. – Vatine

13

¿Hay algunas reglas del pulgar simples que pueda simplemente siga y sepa exactamente lo que sucede con el alcance sin ser mordido de alguna manera extraña y difícil de depurar.

Leer Emacs Lisp Reference, tendrá muchos detalles como éste:

  • Formulario especial: setq [forma de símbolo] ... Esta forma especial es el método más común de cambiar un el valor de la variable. A cada SÍMBOLO se le asigna un nuevo valor, que es el resultado de la evaluación del FORMULARIO correspondiente. Se ha modificado el enlace local más existente del símbolo.

Aquí se muestra un ejemplo:

(defun foo() (setq tata "foo")) 

(defun bar (tata) (setq tata "bar")) 


(foo) 
(message tata) 
    ===> "foo" 


(bar tata) 
(message tata) 
    ===> "foo" 
+0

Gracias, su ejemplo y fragmento de la referencia mejoraron mi comprensión del asunto. He leído algunas partes de la referencia, pero de alguna manera nunca he asimilado este problema correctamente antes ... – auramo

43

No es tan malo.

Los principales problemas pueden aparecer con las 'variables libres' en las funciones.

(defun foo (a) 
    (* a b)) 

En la función anterior a es una variable local. b es una variable gratuita. En un sistema con enlace dinámico como Emacs Lisp, se buscará b en el tiempo de ejecución. En la actualidad hay tres casos:

  1. b no está definido -> error
  2. b es una variable local vinculado por algunos llaman función en el ámbito dinámica actual -> tomar ese valor
  3. b es una variable global -> Con esa cifra

Los problemas pueden ser entonces:

  • un valor límite (global o local) se ve ensombrecido por una llamada de función, posiblemente no deseada
  • una variable no definida NO está ensombrecida -> error en el acceso
  • una variable global no se ve ensombrecido -> 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.

consejos:

  • asegúrese de que usted no utiliza variables libres accidentalmente
  • asegurarse de que las variables globales tienen un nombre especial, por lo que son fáciles de detectar en el código fuente, por lo general *foo-var*

no escriba

(defun foo (a b) 
    ... 
    (setq c (* a b)) ; where c is a free variable 
    ...) 

Comentario:

(defun foo (a b) 
    ... 
    (let ((c (* a b))) 
    ...) 
    ...) 

Enlazar todas las variables que desea utilizar y que quieren asegurarse de que no están obligados a otra parte.

Eso es básicamente eso.

Dado que GNU Emacs versión 24 vinculante léxico es compatible con su Emacs Lisp. Ver: Lexical Binding, GNU Emacs Lisp Reference Manual.

+0

¡Gracias! Parece que tu consejo me impedirá confundir accidentalmente cosas en la pila de llamadas. Todavía estoy un poco preocupado por causar problemas en la pila de llamadas ... – auramo

+3

@auramo: si sigues ese consejo, tu código también debería funcionar bien en la pila de llamadas. –

2

Siento su 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 para solucionar las carencias que las respuestas anteriores aún no cubrían, me gustaría señalar la existencia de una rama de emacs llamada `lexbind ', que implementa la vinculación léxica de una manera compatible hacia atrás. 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.

+3

Para cualquier persona que llegue tarde, la rama lexbind se fusionó y se lanzó en Emacs 24. – phils

+0

@phils Creo que este es un anuncio lo suficientemente grande como para que esté justificado al comentar la pregunta principal. Ayudará a establecer el contexto histórico de esta pregunta. –

5

de alcance dinámico y léxica tienen diferentes comportamientos cuando se utiliza una pieza de código en un ámbito diferente al que se define en En la práctica, hay dos modelos que cubren los casos más 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 ya que las variables locales tienden a tener nombres cortos (a menudo una sola palabra), mientras que las variables globales tienden a tener nombres largos (a menudo con el prefijo packagename-). Muchas funciones estándar tienen nombres que son tentadores de usar como variables locales como list y point, pero las funciones y las variables viven en espacios de nombre separados, las funciones locales no se usan con mucha frecuencia.

  • Una función se define en un contexto léxico y se utiliza 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 es debido a la utilización de cl-x como un nombre de variable en mapcar* (del paquete cl). Tenga en cuenta que el paquete cl usa cl- como un 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.

P.S. La edad de 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 colgarse: te arriesgas a engancharte accidentalmente en un lugar que no conocías.

+0

Gracias, tocaste las convenciones de nomenclatura mientras otras personas se salteaban. ¡Estupendo! – auramo

+1

Estoy de acuerdo con todo lo que dijo, excepto tal vez "todavía no lo había hecho".Cuando RMS escribió GNU, Emacs Lisp Common Lisp ya existía desde hace un tiempo, del mismo modo Scheme. Emacs es anterior a ambos, pero no a Emacs basado en Lisp. El diseño y la creación de Emacs Lisp se produjeron después de Common Lisp y Scheme. – Drew

12

Además del último párrafo de la respuesta Gilles, aquí es cómo RMS argues in favor of dynamic scoping en un sistema ampliable:

Algunos diseñadores del lenguaje creen que unión dinámica se debe evitar, y paso de argumentos explícito, deben utilizado en lugar. 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 un argumento a B, que debería pasarlo como argumento a C.

Esto no se puede hacer en un sistema extensible , sin embargo, debido a que el autor de el 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 does no existe en el sistema estándar; es es parte de la extensión. Para utilizar paso de argumentos explícitos habría requerir la adición de un nuevo argumento a B, lo que significa volver a escribir B y todo que llama B. En el caso más común, B es el orden del editor despachador bucle, que se llama a partir de una terrible cantidad de lugares.

Lo que es peor, C también se debe pasar un argumento adicional . B no refiere a C por nombre (C no existía cuando se escribió B ). Probablemente encuentre un puntero en C en la tabla de envío de comando . Esto significa que la misma llamada que a veces llama a C podría igualmente llamar a cualquier comando de editor definición. Por lo tanto, todos los comandos de edición deben reescribirse para aceptar e ignorar el argumento adicional. ¡Por ahora, ninguno del sistema original es dejado!

Personalmente, creo que si hay un problema con Emacs-Lisp, no es el ámbito dinámico en sí, sino que es el valor predeterminado, y que no es posible lograr ámbito 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 nuevamente RMS:

No es necesario que alcance dinámico que es la única regla de ámbito proporcionada, sólo útil para que esté disponible.

+3

Derecha. Por lo general, una forma de lidiar con el 'problema de extensibilidad' es usar 'objetos'. En lugar de agregar campos a la lista de parámetros, los objetos obtienen campos adicionales. En lugar de pasar todos los argumentos de dibujo a una función, pase un objeto de contexto de dibujo. Agregar un nuevo argumento de dibujo es luego agregarlo a las ranuras de la clase del contexto de dibujo. Pero como Emacs Lisp no tenía un sistema de objetos, esa solución tampoco estaba disponible. Aunque RMS nuevo el sistema de objetos 'Flavors' para Lisp. –

4

Las otras respuestas son buenos para explicar los detalles técnicos sobre cómo trabajar con el ámbito dinámico, por lo que aquí es mi consejo no técnica:

Sólo hazlo

He estado jugando con Emacs lisp por más de 15 años y no sé que he alguna vez mordido por cualquier problema 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, en general, 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.

8

Todo lo que se ha escrito aquí vale la pena.Yo agregaría esto: conozca Common Lisp - si nada más, lea sobre él. 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 de que el alcance dinámico es importante para Emacs Lisp, por las razones que RMS y otros han enfatizado.

Así que mi sugerencia es 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.

+2

Acaba de salir Emacs 24.1: puede usar el alcance léxico para variables locales, eval tiene una opción léxica y las funciones interpretadas de ámbito léxico tienen una nueva forma - http://www.masteringemacs.org/articles/2011/12/12/ what-is-new-in-emacs-24-part-2/- en "Lisp Changes in Emacs 24.1" –

+0

El alcance léxico en Emacs Lisp sigue siendo limitado. Common Lisp es lo que recomiendo mirar. Debería ser (pero nunca lo será, por completo), el modelo de Emacs Lisp en términos de unir el soporte para la vinculación léxica y dinámica. – Drew

10

Como Peter Ajtai señaló:

emacs-24.1 puede activar el ámbito léxico sobre una base por archivo poniendo

;; -*- lexical-binding: t -*- 

en la parte superior de su archivo elisp.

2

Simplemente no.

Emacs-24 le permite usar el alcance léxico. Sólo tiene que ejecutar

(setq lexical-binding t)

o añadir

;; -*- lexical-binding: t -*-

al comienzo de su archivo.

Cuestiones relacionadas