lisp - online - Homoiconicidad, ¿cómo funciona?
emacs lisp (6)
¿Puede alguien sugerir artículos que expliquen el concepto de homoxiconicidad, especialmente con Clojure? ¿Por qué Clojure es homoicónico, pero es difícil hacerlo en otros lenguajes como Java?
Aquí hay un programa corto para hacer diferenciación simbólica. Este es un ejemplo de LISP manipulando su propio código. Trate de traducirlo a otro idioma para ver por qué LISP es bueno para este tipo de cosas.
;; The simplest possible symbolic differentiator
;; Functions to create and unpack additions like (+ 1 2)
(defn make-add [ a b ] (list ''+ a b))
(defn addition? [x] (and (=(count x) 3) (= (first x) ''+)))
(defn add1 [x] (second x))
(defn add2 [x] (second (rest x)))
;; Similar for multiplications (* 1 2)
(defn make-mul [ a b ] (list ''* a b))
(defn multiplication? [x] (and (=(count x) 3) (= (first x) ''*)))
(defn mul1 [x] (second x))
(defn mul2 [x] (second (rest x)))
;; Differentiation.
(defn deriv [exp var]
(cond (number? exp) 0 ;; d/dx c -> 0
(symbol? exp) (if (= exp var) 1 0) ;; d/dx x -> 1, d/dx y -> 0
(addition? exp) (make-add (deriv (add1 exp) var) (deriv (add2 exp) var)) ;; d/dx a+b -> d/dx a + d/dx b
(multiplication? exp) (make-add (make-mul (deriv (mul1 exp) var) (mul2 exp)) ;; d/dx a*b -> d/dx a * b + a * d/dx b
(make-mul (mul1 exp) (deriv (mul2 exp) var)))
:else :error))
;;an example of use: create the function x -> x^3 + 2x^2 + 1 and its derivative
(def poly ''(+ (+ (* x (* x x)) (* 2 (* x x))) 1))
(defn poly->fnform [poly] (list ''fn ''[x] poly))
(def polyfn (eval (poly->fnform poly)))
(def dpolyfn (eval (poly->fnform (deriv poly ''x))))
Casi parece ser obvio, pero las primeras fuentes podrían ser:
http://en.wikipedia.org/wiki/Homoiconicity
http://c2.com/cgi/wiki?DefinitionOfHomoiconic
La homogeneidad se explica en general y también puede encontrar las fuentes originales. Como se explica al usar el ejemplo de Lisp, no está tan lejos de Clojure.
Como señala Rainer Joswig, hay buenas razones para dudar de la utilidad de la idea de homoiconicidad y de si los Lisps son realmente Homoiconic.
La definición original de homoiconiticy se centra en una similitud entre las representaciones internas y externas de un idioma . El ejemplo canónico es Lisp, con sus s-expresiones.
Hay (al menos) dos problemas con esa definición y elección de ejemplo.
La primera objeción se refiere a la representación externa. En el caso de Lisp, suponemos que la representación externa es una expresión s. Sin embargo, en la mayoría de los entornos de programación prácticos, la representación real de las fuentes del programa es como archivos de texto que contienen cadenas de caracteres. Solo después de analizar este texto, la representación es realmente una s-expresión. En otras palabras: en entornos prácticos, la representación externa no es una expresión s, sino texto.
La segunda objeción se refiere a la representación interna. Las implementaciones prácticas de los intérpretes Lisp generalmente no funcionan en realidad directamente en las expresiones s internamente por razones de rendimiento. A pesar de que un Lisp podría definirse en términos de un análisis de casos en s-expressions, generalmente no se implementa como tal. Por lo tanto, la representación interna no es realmente una expresión s en la práctica.
De hecho, uno podría incluso plantear más preguntas sobre el concepto de homoiconicidad: para una máquina bien encapsulada, no podemos observar su funcionamiento interno por definición; en esa vista, hacer cualquier afirmación sobre la representación interna de la máquina no tiene sentido. De manera más general, la definición original tiene el problema de que la idea de que hay una única representación interna externa y una única interna del programa no coincide con la realidad. De hecho, hay toda una cadena de representaciones, incluidos los electrones en el cerebro del programador, los fotones emitidos desde la pantalla, el texto del programa, el código de máquina y los electrones que se mueven en la CPU.
Escribí sobre esto más extensamente en un artículo llamado No digas "Homoiconic"
Cuando estaba aprendiendo Lisp, la idea de homoiconicidad tenía sentido cuando aprendí que el ceceo se "compila" en dos fases, leyendo y compilando, y el código se representa con la misma estructura de datos para ambos:
- primero piensas en una s-expresión en tu cabeza
- luego escribes la expresión s como caracteres en un archivo
- entonces el lector traduce los caracteres en el archivo en s-expresiones. No está compilando el programa, solo construyendo estructuras de datos a partir de caracteres esto es parte de la fase de lectura.
- luego, el lector mira cada una de las expresiones y decide si son macro y si es así ejecuta la macro para producir otra s-expresión. entonces en este punto hemos pasado de s-expressions a characters a s-expressions, luego de s-expressions a diferentes s-expressions.
- estas expresiones s se compilan luego en archivos .class que pueden ser ejecutados por el jvm; este es el segundo de la fase de "compilación".
Así que es más o menos expresiones desde el cerebro hasta el archivo .class. incluso escribes s-expresiones que escriben s-expresiones. para que pueda decir que "el código es la información" o "el código es información" porque eso suena mejor.
Toda la idea de "homoiconicidad" es un poco confusa y no encaja bien en Lisp. Las representaciones internas y externas no son lo mismo en Lisp. La representación externa se basa en los caracteres de los archivos. La representación interna se basa en datos Lisp (números, cadenas, listas, matrices, ...) y no es textual. ¿Cómo es eso lo mismo que los personajes? Hay representaciones internas, que no tienen representaciones externas correspondientes (por ejemplo, código de compilación, cierres, ...).
La principal diferencia entre Lisp y muchos otros lenguajes de programación es que Lisp tiene una representación de datos simple para el código fuente, uno que no se basa en cadenas.
Obviamente, el código se puede representar como cadenas en lenguajes de programación basados en texto. Pero en Lisp, la fuente puede representarse en términos de estructuras primitivas de datos Lisp. La representación externa se basa en s-expressions, que es un modelo simple para representar datos jerárquicos como texto. El modelo interno es la representación se basa en listas, etc.
Eso es lo que obtiene el evaluador: representaciones internas. No 1 a 1 versión de la entrada textual, pero analizada.
El modelo básico:
- LEER traduce expresiones s externas en datos
- EVAL toma los formularios Lisp en forma de datos Lisp y los evalúa
- PRINT traduce los datos de Lisp en expresiones s externas
Tenga en cuenta que READ y PRINT funcionan para datos de Lisp arbitrarios, que tienen una representación impresa y un lector, y no solo para formularios Lisp. Los formularios son, por definición, expresiones válidas en el lenguaje de programación Lisp.
Antes de continuar con algunas cosas por las que quería agregar otra respuesta, aquí hay una referencia más: la parte relacionada con la homoiconicidad es bastante corta, ¡pero es Rich Hickey quien explica! Channel 9 tiene este lindo video con Rich Hickey y Brian Beckman hablando sobre Clojure. La simultaneidad es, comprensiblemente, el enfoque principal, pero la homoiconicidad obtiene su propio (breve) momento de pantalla, durante el cual Rich explica muy bien la interacción entre read
(la función que convierte la sintaxis concreta tal como la escribió el programador en la representación interna construida de listas, etc.) y eval
. Él tiene este lindo diagrama que muestra cómo eval
nunca sabe que el código que evalúa proviene de read
operar en un archivo de texto ... Arthur ya ha explicado la esencia detrás de eso, pero bueno, míralo de todos modos, ¡es un video muy bueno!
Un descargo de responsabilidad: mencionaré Java y Python como ejemplos debajo de la siguiente barra horizontal. Quiero dejar en claro que el siguiente es solo un esbozo de por qué creo que podría ser difícil hacer una Java o Python homoicónica, con estilo Lisp-habilitado para macros; es solo un ejercicio académico, sin embargo, y no quiero considerar la cuestión de si hay alguna razón para intentarlo en primer lugar. Además, no quiero implicar que la sintaxis de un lenguaje con macros de estilo Lisp debe contener delimitadores explícitos para estructuras de árbol ; Dylan (¿el Lisp sin paren?) Aparentemente proporciona un contraejemplo. Finalmente, uso la expresión macros de estilo Lisp porque solo estoy examinando macros de estilo Lisp. El idioma Forth, por ejemplo, tiene una función macro diferente que realmente no entiendo, excepto que lo conozco para permitir un código de aspecto travieso. Aparentemente, las extensiones de sintaxis se pueden implementar de varias maneras. Con esto fuera del camino ...
Me gustaría abordar la segunda parte de su pregunta: ¿cómo es que la mayoría de los lenguajes de programación se consideran no homicónicos? Tendré que tocar la semántica de Lisp en el proceso, pero dado que Nils ya ha proporcionado enlaces a buenas fuentes de información sobre el término "homoicónico" en sí mismo, Arthur ha descrito el ciclo de compilación de lectura -> macro expandir -> tal como se encontró en Clojure, voy a desarrollar eso en lo que sigue. Para empezar, permítanme citar un pasaje de Alan Kay (extraído del artículo de Wikipedia que también vincula a la fuente original):
[...] Interactive LISP y TRAC [...] ambos son "homoicónicos" en el sentido de que sus representaciones internas y externas son esencialmente las mismas.
(Esas [...] partes esconden mucho texto, pero la esencia no cambia).
Ahora, formémonos la pregunta: ¿cuál es la representación interna de Java de Java? ... Bueno, esto ni siquiera tiene sentido. El compilador de Java tiene una cierta representación interna de Java, a saber, un árbol de sintaxis abstracta; Para construir una "Java homoicónica", tendríamos que convertir esa representación AST en un objeto de primera clase en Java e idear una sintaxis que nos permitiera escribir AST directamente. Eso podría ser bastante difícil.
Python proporciona un ejemplo de un lenguaje no homicónico que es interesante ya que actualmente viene con un kit de herramientas de manipulación AST en forma del módulo ast
. Los documentos para ese módulo indican explícitamente que los AST de Python pueden cambiar entre versiones, lo que puede ser desalentador o no; Aún así, supongo que un programador industrioso podría tomar el módulo ast
, idear una sintaxis (tal vez basada en S-expression, tal vez basada en XML) para describir Python ASTs directamente y construir un analizador para esa sintaxis en Python regular usando ast
, tomando así un sólido primer paso hacia la creación de un lenguaje homoicónico con semántica de Python. (Creo que encontré un dialecto de compilación de Lisp en bytecode de Python hace algún tiempo ... Me pregunto si podría estar haciendo algo así en algún nivel?)
Incluso entonces, sigue existiendo el problema de extraer beneficios concretos de ese tipo de homoiconicidad. Se ve como una propiedad beneficiosa de los miembros de la familia de idiomas Lisp porque nos permite escribir programas que escriben otros programas, entre los cuales las macros son las más notables. Ahora, mientras que las macros están habilitadas de una manera por el hecho de que es tan fácil manipular la representación interna del código Lisp en Lisp, también están habilitadas de manera igualmente importante por el modelo de ejecución de Lisp : un programa Lisp es solo una colección de formas Lisp; estos son procesados por la función de eval
Lisp que es responsable de determinar los valores de las expresiones y causar los efectos secundarios apropiados en el momento correcto; la semántica de Lisp es exactamente la semántica de eval
. La cuestión de cómo funcionan las cosas internamente para preservar esta ilusión semántica a la vez que es razonablemente rápida es un detalle de implementación; un sistema Lisp tiene la obligación de exponer una función eval
al programador y actuar como si los programas Lisp estuvieran siendo procesados por esa función.
En los sistemas Lisp modernos, es parte del contrato de eval
que realiza una fase de preproceso adicional durante la cual las macros se expanden antes de evaluar el código (o compilar y ejecutar, según sea el caso). Esa instalación en particular no es una parte necesaria de un sistema Lisp, ¡pero es tan fácil conectarlo a este modelo de ejecución! Además, me pregunto si este no es el único modelo de ejecución que hace que el tipo de macro transformaciones Lisp sea manejable, lo que significaría que cualquier lenguaje que busque incorporar macros estilo Lisp tendría que adoptar un modelo de ejecución similar. Mi intuición me dice que este es realmente el caso.
Por supuesto, una vez que un lenguaje se anota en notación directamente paralela a sus AST y utiliza un modelo de ejecución tipo Lisp con una función / objeto evaluador, uno debe preguntarse si no es por casualidad otro dialecto de Lisp ... incluso si es La sintaxis paralela de AST pasa a estar basada en XML. estremecimiento