En la programación de computadoras , la homoiconicidad (de las palabras griegas homo, que significa "lo mismo" e icono que significa "representación") es una propiedad de algunos lenguajes de programación . Un lenguaje es homoicónico si un programa escrito en él puede manipularse como datos usando el lenguaje y, por lo tanto, la representación interna del programa puede inferirse simplemente leyendo el programa en sí. Esta propiedad a menudo se resume diciendo que el lenguaje trata el "código como datos".
En un lenguaje homoicónico, la representación principal de los programas es también una estructura de datos en un tipo primitivo del propio lenguaje. Esto hace que la metaprogramación sea más fácil que en un lenguaje sin esta propiedad: la reflexión en el lenguaje (examinar las entidades del programa en tiempo de ejecución ) depende de una estructura única y homogénea, y no tiene que manejar varias estructuras diferentes que aparecerían en una sintaxis compleja. Los lenguajes homoicónicos generalmente incluyen soporte completo de macros sintácticas , lo que permite al programador expresar transformaciones de programas de una manera concisa.
Un ejemplo comúnmente citado es Lisp , que fue creado para permitir manipulaciones fáciles de listas y donde la estructura está dada por expresiones S que toman la forma de listas anidadas y pueden ser manipuladas por otro código Lisp. [1] Otros ejemplos son los lenguajes de programación Clojure (un dialecto contemporáneo de Lisp), Rebol (también su sucesor Red ), Refal , Prolog y, más recientemente, Julia .
Historia
La fuente original es el artículo Macro Instruction Extensions of Compiler Languages , [2] según el primer e influyente artículo TRAC, A Text-Handling Language : [3]
Uno de los principales objetivos del diseño fue que el script de entrada de TRAC (lo que escribe el usuario) debe ser idéntico al texto que guía la acción interna del procesador TRAC. En otras palabras, los procedimientos TRAC deben almacenarse en la memoria como una cadena de caracteres exactamente como los escribió el usuario en el teclado. Si los propios procedimientos TRAC evolucionan nuevos procedimientos, estos nuevos procedimientos también deben indicarse en el mismo guión. El procesador TRAC en su acción interpreta este script como su programa. En otras palabras, el programa traductor TRAC (el procesador) convierte efectivamente la computadora en una nueva computadora con un nuevo lenguaje de programa: el lenguaje TRAC. En cualquier momento, debería ser posible mostrar información sobre el programa o el procedimiento en la misma forma en que el procesador TRAC actuará sobre ella durante su ejecución. Es deseable que la representación del código de carácter interno sea idéntica o muy similar a la representación del código externo. En la implementación actual de TRAC, la representación de caracteres internos se basa en ASCII . Debido a que los procedimientos y el texto de TRAC tienen la misma representación dentro y fuera del procesador, se aplica el término homoicónico, de homo que significa lo mismo y de icono que significa representación.
[...]
Siguiendo la sugerencia de McCullough, WS , basada en la terminología debida a Peirce, CS s McIlroy. MD, "Extensiones de macroinstrucciones de lenguajes del compilador", Comm. ACM, pág. 214–220; Abril de 1960.
Alan Kay usó y posiblemente popularizó el término "homoicónico" a través de su uso del término en su tesis doctoral de 1969: [4]
Un grupo notable de excepciones a todos los sistemas anteriores son LISP [...] interactivo y TRAC. Ambos están orientados funcionalmente (una lista, la otra cadena), ambos hablan con el usuario en un idioma y ambos son "homoicónicos" en el sentido de que sus representaciones internas y externas son esencialmente las mismas. Ambos tienen la capacidad de crear dinámicamente nuevas funciones que luego pueden ser elaboradas a gusto de los usuarios. Su único gran inconveniente es que los programas escritos en ellos se parecen a la carta del rey Burniburiach a los sumerios escrita en cuniforme babilónico. [...]
Usos y ventajas
Una ventaja de la homoiconicidad es que extender el lenguaje con nuevos conceptos generalmente se vuelve más simple, ya que los datos que representan el código se pueden pasar entre la meta y la capa base del programa. El árbol de sintaxis abstracta de una función puede componerse y manipularse como una estructura de datos en la metacapa y luego evaluarse . Puede ser mucho más fácil entender cómo manipular el código, ya que puede entenderse más fácilmente como datos simples (ya que el formato del lenguaje en sí es como un formato de datos).
Una demostración típica de homoiconicidad es el evaluador meta-circular .
Métodos de implementación
Todos los sistemas de arquitectura de Von Neumann , que incluyen la gran mayoría de las computadoras de propósito general en la actualidad, pueden describirse implícitamente como homoicónicos debido a la forma en que el código de máquina sin procesar se ejecuta en la memoria, siendo el tipo de datos bytes en la memoria. Sin embargo, esta característica también se puede abstraer al nivel del lenguaje de programación.
Lenguajes como Lisp y sus dialectos, [5] como Scheme , [6] Clojure y Racket emplean expresiones S para lograr homoiconicidad.
Otros lenguajes que se consideran homoicónicos incluyen:
En Lisp
Lisp usa expresiones S como una representación externa de datos y código. Las expresiones S se pueden leer con la función Lisp primitiva READ
. READ
devuelve datos Lisp: listas, símbolos , números, cadenas. La función Lisp primitiva EVAL
usa código Lisp representado como datos Lisp, calcula efectos secundarios y devuelve un resultado. El resultado será impreso por la función primitiva PRINT
, que crea una expresión S externa a partir de los datos Lisp.
Lisp data, una lista que utiliza diferentes tipos de datos: (sub) listas, símbolos, cadenas y números enteros.
(( : nombre "john" : 20 años ) ( : nombre "mary" : 18 años ) ( : nombre "alice" : 22 años ))
Código Lisp. El ejemplo usa listas, símbolos y números.
( * ( sen 1,1 ) ( cos 2,03 )) ; en infijo: sin (1.1) * cos (2.03)
Cree la expresión anterior con la función Lisp primitiva LIST
y establezca la variable EXPRESSION
en el resultado
( setf expresión ( lista '* ( lista ' sin 1.1 ) ( lista 'cos 2.03 )) ) -> ( * ( SIN 1.1 ) ( COS 2.03 )) ; Lisp devuelve e imprime el resultado( tercera expresión ) ; el tercer elemento de la expresión -> ( COS 2.03 )
Cambiar el COS
término aSIN
( setf ( primera ( tercera expresión )) 'SIN ) ; La expresión ahora es (* (SIN 1.1) (SIN 2.03)).
Evaluar la expresión
( expresión de evaluación ) -> 0,7988834
Imprime la expresión en una cadena
( expresión de impresión a cadena ) -> "(* (SIN 1.1) (SIN 2.03))"
Leer la expresión de una cadena
( lectura-de-cadena "(* (SIN 1.1) (SIN 2.03))" ) -> ( * ( SIN 1.1 ) ( SIN 2.03 )) ; devuelve una lista de listas, números y símbolos
En Prolog
1 ? - X es 2 * 5. X = 10.2 ? - L = ( X es 2 * 5 ) , write_canonical ( L ) . es ( _, * ( 2 , 5 )) L = ( X es 2 * 5 ) .3 ? - L = ( diez ( X ) : - ( X es 2 * 5 )) , write_canonical ( L ) . : - ( diez ( A ) , es ( A, * ( 2 , 5 ))) L = ( diez ( X ) : -X es 2 * 5 ) .4 ? - L = ( diez ( X ) : - ( X es 2 * 5 )) , afirmar ( L ) . L = ( diez ( X ) : -X es 2 * 5 ) .5 ? - diez ( X ) . X = 10.6 ? -
En la línea 4 creamos una nueva cláusula. El operador :-
separa el encabezado y el cuerpo de una cláusula. Con assert/1*
lo agregamos a las cláusulas existentes (lo agregamos a la "base de datos"), para que podamos llamarlo más tarde. En otros lenguajes lo llamaríamos "crear una función durante el tiempo de ejecución". También podemos eliminar cláusulas de la base de datos con abolish/1
, o retract/1
.
* El número después del nombre de la cláusula es el número de argumentos que puede tomar. También se le llama aridad .
También podemos consultar la base de datos para obtener el cuerpo de una cláusula:
7 ? - cláusula ( diez ( X ) , Y ) . Y = ( X es 2 * 5 ) .8 ? - cláusula ( diez ( X ) , Y ) , Y = ( X es Z ) . Y = ( X es 2 * 5 ) , Z = 2 * 5.9 ? - cláusula ( diez ( X ) , Y ) , llamar ( Y ) . X = 10 , Y = ( 10 es 2 * 5 ) .
call
es análogo a la eval
función de Lisp .
En Rebol
El concepto de tratar el código como datos y su manipulación y evaluación se puede demostrar muy claramente en Rebol . (Rebol, a diferencia de Lisp, no requiere paréntesis para separar expresiones).
El siguiente es un ejemplo de código en Rebol (tenga en cuenta que >>
representa el indicador del intérprete; se han agregado espacios entre algunos elementos para facilitar la lectura):
>> repeat i 3 [ print [ i "hello" ] ]
1 hola2 hola3 hola
( repeat
de hecho, es una función incorporada en Rebol y no es una construcción de lenguaje o una palabra clave).
Al encerrar el código entre corchetes, el intérprete no lo evalúa, sino que simplemente lo trata como un bloque que contiene palabras:
[ repetir i 3 [ imprimir [ i "hola" ]]]
¡Este bloque tiene el tipo bloque! y además se puede asignar como el valor de una palabra usando lo que parece ser una sintaxis para la asignación, pero en realidad el intérprete lo entiende como un tipo especial ( set-word!
) y toma la forma de una palabra seguida de dos puntos:
>> ;; Asignar el valor del bloque a la palabra `block1`block1: [ repeat i 3 [ print [ i "hello" ] ] ]
== [repetir i 3 [imprimir [i "hola"]]]>> ;; Evaluar el tipo de la palabra `block1`type? block1
== bloquear!
El bloque aún se puede interpretar usando la do
función provista en Rebol (similar a evalLisp ).
Es posible interrogar los elementos del bloque y cambiar sus valores, alterando así el comportamiento del código si fuera a ser evaluado:
>> ;; El tercer elemento del bloqueblock1/3
== 3>> ;; Establezca el valor del tercer elemento en 5block1/3: 5
== 5>> ;; Mostrar el bloque cambiadoprobe block1
== [repetir i 5 [imprimir [i "hola"]]]>> ;; Evaluar el bloquedo block1
1 hola2 hola3 hola4 hola5 hola
Ver también
- Dimensiones cognitivas de las notaciones , principios de diseño para la sintaxis de los lenguajes de programación.
- Lenguaje de programación concatenativo
- Programación orientada al lenguaje
- Programación simbólica
- Código auto modificable
- LISP (lenguaje de programación) , quizás el ejemplo más conocido de lenguaje homoicónico
- La metaprogramación , una técnica de programación para la que la homoiconicidad es muy útil
- Reificación (informática)
Referencias
- ^ Wheeler, David A. "Expresiones S Lisp legibles" .
- ^ McIlroy, Douglas (1960). "Extensiones de macroinstrucciones de lenguajes del compilador". Comm. ACM . 3 (4): 214–220. doi : 10.1145 / 367177.367223 .
- ^ Mooers, CN ; Deutsch, LP (1965). "TRAC, un lenguaje de manejo de texto". Proceeding ACM '65 Proceedings of the 1965 20th national conference . págs. 229–246. doi : 10.1145 / 800197.806048 .
- ^ Kay, Alan (1969). El motor reactivo (PhD). Universidad de Utah.
- ^ a b c d e f g h i Lenguas homoicónicas
- ^ a b Idiomas homoicónicos (archivado) , en el blog True Blue en Oracle
- ^ "Lispy Elixir" . 8thlight.com .
Elixir, en la superficie, no es homoicónico. Sin embargo, la sintaxis en la superficie es solo una fachada para la estructura homoicónica debajo.
- ^ "Por qué creamos a Julia" . julialang.org .
Queremos un lenguaje que sea homoicónico, con verdaderas macros como Lisp, pero con notación matemática obvia y familiar como Matlab.
- ^ "metaprogramación" . docs.julialang.org .
Como Lisp, Julia representa su propio código como una estructura de datos del propio lenguaje.
- ^ "Metaprogramación en matemática" . Stack Exchange .
Mathematica es lenguaje [...] homoicónico (programas escritos en estructuras de datos propias - expresiones de Mathematica. Este es un paradigma de código como datos, como Lisp que usa listas para esto)
- ^ Shapiro, Ehud Y .; Sterling, León (1994). El arte de Prolog: técnicas avanzadas de programación . Prensa del MIT. ISBN 0-262-19338-8.
- ^ Ramsay, S .; Pytlik-Zillig, B. (2012). "Técnicas de generación de código para la interoperabilidad de colecciones XML" . Actas de la conferencia de Humanidades Digitales del dh2012 .
- ^ "Notas para expertos en lenguajes de programación" . Wolfram Language . Wolfram. 2017.
enlaces externos
- Definición de homoicónico en el C2 Wiki