stringtranslate.com

Homoiconicidad

En programación informática , la homoiconicidad (de las palabras griegas homo-, que significa "lo mismo" e icon , 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 utilizando el lenguaje. [1] La representación interna del programa puede, por tanto, inferirse simplemente leyendo el propio programa. Esta propiedad suele resumirse diciendo que el lenguaje trata el código como datos .

En un lenguaje homoicónico, la representación primaria de los programas es también una estructura de datos en un tipo primitivo del propio lenguaje. [1] Esto hace que la metaprogramación sea más sencilla 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 única estructura homogénea, y no tiene que manejar varias estructuras diferentes que aparecerían en una sintaxis compleja. Los lenguajes homoicónicos suelen incluir soporte completo de macros sintácticas , lo que permite al programador expresar transformaciones de programas de forma concisa.

Un ejemplo comúnmente citado es Lisp , que fue creado para permitir manipulaciones de listas fáciles y donde la estructura está dada por S-expresiones que toman la forma de listas anidadas y pueden ser manipuladas por otro código Lisp. [2] Otros ejemplos son los lenguajes de programación Clojure (un dialecto contemporáneo de Lisp), Rebol (también su sucesor Red ), Refal , Prolog y posiblemente Julia (ver la sección “Métodos de implementación” para más detalles).

Historia

El término apareció por primera vez en relación con el lenguaje de programación TRAC , desarrollado por Calvin Mooers : [3]

Uno de los principales objetivos de diseño era que el código de entrada de TRAC (lo que escribe el usuario) fuera idéntico al texto que guía la acción interna del procesador TRAC. En otras palabras, los procedimientos TRAC se deberían almacenar en la memoria como una cadena de caracteres exactamente como los escribe el usuario en el teclado. Si los procedimientos TRAC desarrollan nuevos procedimientos, estos nuevos procedimientos también deberían estar enunciados en el mismo código. El procesador TRAC en su acción interpreta este código como su programa. En otras palabras, el programa traductor TRAC (el procesador) convierte efectivamente el ordenador en un nuevo ordenador con un nuevo lenguaje de programación: el lenguaje TRAC. En cualquier momento, debería ser posible mostrar información del programa o del 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 caracteres interno sea idéntica o muy similar a la representación del código externo. En la presente implementación 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, que significa homo, que significa lo mismo, e ícono, que significa representación.

La última frase anterior está anotada con la nota al pie 4, que da crédito por el origen del término: [a]

Siguiendo la sugerencia de McCullough WS, basada en la terminología de Peirce, CS

Los investigadores implicados en esta cita podrían ser el neurofisiólogo y cibernético Warren Sturgis McCulloch (nótese la diferencia entre el apellido y la nota) y el filósofo, lógico y matemático Charles Sanders Peirce . [5] Pierce utilizó de hecho el término "icono" en su Teoría Semiótica. Según Peirce, hay tres tipos de signos en la comunicación: el icono, el índice y el símbolo. El icono es la representación más simple: un icono se parece físicamente a aquello que denota.

Alan Kay utilizó y posiblemente popularizó el término "homoicónico" a través de su uso del término en su tesis doctoral de 1969: [6]

Un grupo notable de excepciones a todos los sistemas anteriores son Interactive LISP [...] y TRAC. Ambos están orientados funcionalmente (uno es una lista, el otro es una cadena), ambos hablan al usuario con un lenguaje 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 del usuario. Su único gran inconveniente es que los programas escritos en ellos parecen la carta del rey Burniburiach a los sumerios escrita en escritura cuneiforme babilónica. [...]

Usos y ventajas

Una ventaja de la homoiconicidad es que extender el lenguaje con nuevos conceptos suele ser más sencillo, ya que los datos que representan el código se pueden pasar entre la capa meta y la capa base del programa. El árbol de sintaxis abstracta de una función se puede componer y manipular como una estructura de datos en la capa meta, y luego evaluar . Puede ser mucho más fácil entender cómo manipular el código, ya que se puede entender más fácilmente como datos simples (ya que el formato del lenguaje en sí es un formato de datos).

Una demostración típica de homoiconicidad es el evaluador metacircular .

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 actuales, pueden describirse implícitamente como homoicónicos debido a la forma en que el código de máquina en bruto se ejecuta en la memoria, siendo el tipo de datos los bytes en la memoria. Sin embargo, esta característica también puede abstraerse al nivel del lenguaje de programación.

Lenguajes como Lisp y sus dialectos, [7] como Scheme , [8] Clojure y Racket emplean expresiones S para lograr homoiconicidad y se consideran las formas "más puras" de homoiconicidad, ya que estos lenguajes usan la misma representación tanto para datos como para código.

Otros lenguajes proporcionan estructuras de datos para manipular código de manera fácil y eficiente. Entre los ejemplos notables de esta forma más débil de homoiconicidad se incluyen Julia , Nim y Elixir .

Los idiomas que a menudo se consideran homoicónicos incluyen:

En Lisp

Lisp utiliza expresiones S como representación externa de datos y código. Las expresiones S se pueden leer con la función primitiva de Lisp READ. READdevuelve datos de Lisp: listas, símbolos , números, cadenas. La función primitiva de Lisp EVALutiliza código de Lisp representado como datos de 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 datos de Lisp.

Datos Lisp, una lista que utiliza diferentes tipos de datos: (sub)listas, símbolos, cadenas y números enteros.

(( :nombre "john" :edad 20 ) ( :nombre "mary" :edad 18 ) ( :nombre "alice" :edad 22 ))           

Código Lisp. El ejemplo utiliza listas, símbolos y números.

( * ( pecado 1.1 ) ( cos 2.03 )) ; en infijo: pecado(1.1)*cos(2.03)     

Cree la expresión anterior con la función Lisp primitiva LISTy establezca la variable EXPRESSIONen el resultado

( expresión setf ( lista '* ( lista 'sin 1.1 ) ( lista 'cos 2.03 )) ) -> ( * ( SIN 1.1 ) ( COS 2.03 )) ; Lisp retorna e imprime el resultado                 ( tercera expresión ) ; el tercer elemento de la expresión -> ( COS 2.03 )    

Cambiar el COStérmino aSIN

( setf ( first ( third expression )) '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 desde la 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 )) , escritura_canónica ( 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 la cabecera y el cuerpo de una cláusula. Con assert/1* la añadimos a las cláusulas existentes (la añadimos a la "base de datos"), para poder llamarla 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 que aparece después del nombre de la cláusula es el número de argumentos que puede aceptar. También se denomina 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 ) , llamada ( Y ) . X = 10 , Y = ( 10 es 2*5 ) .         

calles análoga a evalla función de Lisp.

En Rebol

El concepto de tratar el código como datos y la manipulación y evaluación del mismo 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 mensaje del intérprete; se han agregado espacios entre algunos elementos para facilitar la lectura):

>>repeat i 3 [ print [ i "hello" ] ]1 hola2 hola3 hola

( repeatde hecho, es una función incorporada en Rebol y no es una construcción del lenguaje ni 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 block! y además puede asignarse como el valor de una palabra utilizando lo que parece ser una sintaxis para asignación, pero que en realidad el intérprete entiende como un tipo especial ( set-word!) y toma la forma de una palabra seguida de dos puntos:

>>  ;; Asigna el valor del bloque a la palabra `block1`block1: [ repeat i 3 [ print [ i "hello" ] ] ]== [repetir i 3 [imprimir [i "hola"]]]>>  ;; Evalúa el tipo de la palabra `block1`type? block1== ¡bloqueo!

El bloque aún se puede interpretar utilizando la dofunción proporcionada en Rebol (similar a evalen Lisp ).

Es posible interrogar los elementos del bloque y cambiar sus valores, alterando así el comportamiento del código si fuera evaluado:

>>  ;; El tercer elemento del bloqueblock1/3== 3>>  ;; Establezca el valor del tercer elemento en 5block1/3: 5== 5>>  ;; Mostrar el bloque modificadoprobe block1== [repetir i 5 [imprimir [i "hola"]]]>>  ;; Evaluar el bloquedo block11 hola2 hola3 hola4 hola5 hola

Véase también

Notas

  1. ^ Las versiones anteriores de esta página de Wikipedia (2006-2023) fusionaron la nota 5 del artículo del TRAC mencionado anteriormente, lo que dio como resultado la afirmación errónea de que el término "homoicónico" tenía su origen en un artículo sobre procesamiento de macros de Douglas McIlroy. Ese artículo no menciona ninguna terminología remotamente similar; su influencia en el trabajo del TRAC es de una naturaleza diferente. Esta afirmación ha sido repetida desde entonces por algunas fuentes. [4]

Referencias

  1. ^ ab Ceravola, Antonello; Joublin, Frank (2021). "De JSON a JSEN a través de lenguajes virtuales". Open Journal of Web Technologies . 8 (1): 1–15. En un lenguaje homoicónico, la representación primaria de los programas es también una estructura de datos en un tipo primitivo del propio lenguaje.
  2. ^ Wheeler, David A. "Expresiones S de Lisp legibles".
  3. ^ Mooers, CN ; Deutsch, LP (1965). "TRAC, un lenguaje de manejo de texto". Actas ACM '65 Actas de la 20.ª conferencia nacional de 1965 . págs. 229–246. doi :10.1145/800197.806048.
  4. ^ Mannaert, Herwig; McGroarty, Chris; Gallant, Scott; De Cock, Koen; Gallogly, Jim; Raval, Anup; Snively, Keith (2022). "Hacia una metaprogramación colaborativa escalable: un estudio de caso para integrar dos entornos de metaprogramación" (PDF) . Revista internacional sobre avances en software . 15 : 128–140. ISSN  1942-2628.
  5. ^ "No digas "Homoicónico"". Expresiones de cambio . 1 de marzo de 2018.
  6. ^ Kay, Alan (1969). El motor reactivo (PhD). Universidad de Utah.
  7. ^ abcdefghi Lenguas homoicónicas
  8. ^ ab Lenguajes homoicónicos (archivados), en el blog True Blue de Oracle
  9. ^ "Lispy Elixir". 8thlight.com . Elixir, en apariencia, no es homoicónico. Sin embargo, la sintaxis superficial es solo una fachada para la estructura homoicónica subyacente.
  10. ^ "Por qué creamos Julia". julialang.org . Queremos un lenguaje que sea homoicónico, con macros reales como Lisp, pero con una notación matemática obvia y familiar como Matlab.
  11. ^ "metaprogramación". docs.julialang.org . Al igual que Lisp, Julia representa su propio código como una estructura de datos del lenguaje en sí.
  12. ^ Shapiro, Ehud Y.; Sterling, Leon (1994). El arte de Prolog: técnicas de programación avanzadas . MIT Press. ISBN 0-262-19338-8.
  13. ^ Ramsay, S.; Pytlik-Zillig, B. (2012). "Técnicas de generación de código para la interoperabilidad de colecciones XML". Actas de la conferencia sobre humanidades digitales dh2012 .
  14. ^ "Notas para expertos en lenguajes de programación". Wolfram Language . Wolfram. 2017.

Enlaces externos