compiler construction - ¿Puede un lenguaje compilado ser homoicónico?
compiler-construction lisp (10)
"Homoiconic" es una especie de construcción vaga. ''El código es datos'' es un poco más claro.
De todos modos, la primera frase en Wikipedia para Homoiconic no es tan mala. Dice que el lenguaje debe tener una representación de origen utilizando sus estructuras de datos . Si olvidamos ''cadenas'' como representación de origen (eso es trivial y no es tan útil tener un concepto útil ''homoicónico''), entonces Lisp tiene listas, símbolos, números, cadenas, etc. que se utilizan para representar el código fuente. La interfaz de la función EVAL determina en qué tipo de representación de origen está trabajando el idioma. En este caso, Lisp, no se trata de cadenas. EVAL espera la variedad habitual de estructuras de datos y las reglas de evaluación de Lisp determinan que una cadena se evalúa a sí misma (y, por lo tanto, no se interpretará como una expresión de programa, sino solo datos de cadenas). Un número también se evalúa a sí mismo. Una lista (sin 3.0) es una lista de un símbolo y un número. Las reglas de evaluación dicen que esta lista con un símbolo que denota una función como el primer objeto se evaluará como una aplicación de función. Existen algunas reglas de evaluación como esta para datos, operadores especiales, aplicaciones de macro y aplicaciones de función. Eso es.
Para dejarlo claro: en Lisp, la función EVAL se define sobre las estructuras de datos de Lisp . Espera una estructura de datos, la evalúa de acuerdo con sus reglas de evaluación y devuelve un resultado, nuevamente utilizando sus estructuras de datos.
Esto coincide con la definición de homoiconic: el código fuente tiene una representación nativa utilizando los tipos de datos de Lisp.
Ahora, la parte interesante es esta: no importa cómo se implementa EVAL . Lo único que importa es que acepta el código fuente utilizando las estructuras de datos Lisp, que ejecuta el código y que devuelve un resultado.
Entonces es perfectamente legal que EVAL use un compilador .
(EVAL code) = (run (compile-expression code))
Así es como funcionan varios sistemas Lisp, algunos ni siquiera tienen un intérprete.
Entonces, ''Homoiconic'' dice que el código SOURCE tiene una representación de datos. NO dice que en el tiempo de ejecución este código fuente debe interpretarse o que la ejecución se basa en este código fuente.
Si se compila el código, no se necesita ni el compilador ni un intérprete en el tiempo de ejecución . Solo serían necesarios si el programa desea evaluar o compilar código en tiempo de ejecución, algo que a menudo no es necesario.
Lisp también proporciona una función primitiva READ , que traduce una representación externa (S-Expresiones) de datos en una representación interna de datos (datos Lisp). Por lo tanto, también se puede utilizar para traducir una representación externa del código fuente en una representación interna del código fuente. Lisp no usa un analizador especial para el código fuente, ya que el código es datos, solo hay LEER.
Por definición la palabra homoicónica significa:
Misma representación de código y datos.
En LISP, esto significa que usted podría tener una lista cotizada y evaluarla, por lo que (car list)
sería la función y (cdr list)
los argumentos. Esto puede ocurrir en compilación o en tiempo de ejecución, sin embargo, requiere un intérprete.
¿Es posible que los lenguajes compilados sin un intérprete en tiempo de compilación también puedan ser homoicónicos? ¿O es el concepto de homoiconicidad limitado a los intérpretes?
Compilar es solo una interpretación optimizada. Un intérprete toma una parte de los datos que representan el código y luego "hace" ese código: el significado del código se convierte en vías de ejecución y el flujo de datos a través de las entrañas del intérprete. Un compilador toma los mismos datos, los traduce a otra forma y luego los pasa a otro intérprete: uno implementado en silicon (CPU) o tal vez uno falso (máquina virtual).
Es por esto que algunas implementaciones de Lisp pueden no tener intérpretes. La función EVAL puede compilar el código y luego derivarse a él. EVAL y COMPILE no tienen que tener distintos modos de operación. (Clozure, Corman Lisp, SBCL son ejemplos de Lisps "solo compilador").
La parte de datos al principio es la clave para que el lenguaje sea homoicónico, no si la ejecución del código se optimiza o no mediante la compilación. "El código es datos" significa "el código fuente es datos" no "el código ejecutable es datos". (Por supuesto, el código ejecutable son datos, pero por datos nos referimos a la representación predominantemente preferida del código que queremos manipular).
El código de máquina en sí es homoicónico, así que sí.
Los datos o instrucciones son solo una cuestión de semántica (y quizás el segmento de la memoria en la que se encuentran).
El problema es que muchos procesadores separan las áreas de instrucción e información, y evitan activamente que los programas modifiquen su propio código. Este tipo de código solía llamarse "código degenerado" y se consideraba algo muy malo.
Los intérpretes (y máquinas virtuales) no tienen ese problema, ya que pueden tratar todo el programa como datos, con el único "código" siendo el intérprete.
En la forma más literal, C es homoicónica. Puede obtener acceso a la representación de una función usando &functionName
y ejecutar datos usando somePtrCastToFnPtr(SomeArgs)
. Sin embargo, esto es a nivel de código de máquina y sin algún tipo de soporte de biblioteca, será muy difícil trabajar con él. Algún tipo de compilador integrable (me parece recordar que LLVM puede hacer esto) lo haría más práctico.
Lisp normalmente se compila. Ha habido implementaciones con compiladores JIT en lugar de intérpretes.
Por lo tanto, no es necesario tener un intérprete (en el sentido de "no un compilador") para los lenguajes de código de datos.
Los lenguajes construidos sobre VM (.net clr, jre ect) pueden usar técnicas avanzadas que permiten la generación de códigos de vuelo. Uno de ellos es el tejido de li. Aunque, no es tan claro como la evaluación de ECMAScript / Lisp / Scheme ect pero puede hasta cierto punto emular tal comportamiento.
Para ver ejemplos, consulte Castle DynamicProxy y para ver ejemplos más interactivos, consulte LinqPAD, F # Interactive, Scala interactivo.
Me parece una pregunta extraña:
En primer lugar, la parte homoicónica es la interfaz presentada al programador. El punto de los idiomas es que abstraen una funcionalidad de nivel inferior que conserva la misma semántica que la presentación de nivel superior (aunque un medio diferente).
El punto de código de máquina de dsm es un buen punto, pero proporciona:
- La sintaxis y la semántica presentadas son homoicónicas.
- La traducción a un formulario de nivel inferior (código de máquina o interpretado o no) no elimina ninguna de las semánticas originales.
¿Por qué la implementación de nivel inferior importa aquí?
También:
lenguajes compilados sin un intérprete de tiempo de compilación
Sin algún programa que lo interprete, se requeriría ser nativo de la CPU, por lo tanto, el idioma nativo de la CPU debería ser homoicónico (o la máquina virtual que ejecuta el código).
Los idiomas sin interpretación en tiempo de compilación ... serían bastante limitados ... ya que no se compilarían en absoluto .
Pero no soy un experto, y tal vez me esté perdiendo el punto.
Sí; solo tiene que pegar una copia del compilador en el tiempo de ejecución de idioma. Chez Scheme es uno de los muchos compiladores finos que hacen precisamente eso.
sí. Lisp se puede compilar a un binario nativo