assembly lisp machine-code self-modifying homoiconicity

assembly - Código auto modificador homocónico y "no restringido"+¿El lisp es realmente auto modificador?



machine-code self-modifying (4)

En la primera versión (+ 1 2 3) está el código en bruto, mientras que en la segunda versión son datos. Al asumir la verdad de esta afirmación, se puede argumentar que Lisp ni siquiera es homicónico. El código tiene la misma representación que los datos en el sentido de que ambos son listas / árboles / expresiones S. Pero el hecho de que tenga que marcar explícitamente cuáles de estas listas / árboles / expresiones S son código y cuáles datos para mí parece decir que Lisp no es homicónico después de todo.

Esto no es verdad. En la primera versión, la lista (+ 1 2 3) , que es información, se envía al intérprete para que se ejecute, es decir, para que se interprete como código . El hecho de que tenga que marcar s-expresiones como código o datos en un contexto específico no hace Lisp no-homicónica.

El objetivo de la homoiconicidad es que todos los programas sean datos, no que todos los datos sean programas, por lo que aún existe una diferencia entre los dos. En Lisp, (1 2 3) es una lista válida, pero no un programa válido, ya que un entero no es una función.

[Si miramos el otro gran lenguaje de programación homoicónico, Prolog, entonces vemos el mismo fenómeno: podemos construir una estructura de datos foo(X, 1, bar) , pero sin una definición de foo , no podemos ejecutarlo. Además, las variables no pueden ser nombres de predicados o hechos, por lo que X. nunca es un programa válido.]

Lisp se auto modifica en gran medida. Por ejemplo, a continuación se explica cómo cambiar la definición de una función:

[1]> (defun foo (x) (+ x 1)) FOO [2]> (defun bar (x) (+ x 2)) BAR [3]> (setf (symbol-function ''foo) #''bar) #<FUNCTION BAR (X) (DECLARE (SYSTEM::IN-DEFUN BAR)) (BLOCK BAR (+ X 2))> [4]> (foo 3) 5

Explicación: en [1] , definimos la función foo como la función add-1. En [2] , definimos que bar es la función add-2. En [3] , reiniciamos foo a la función add-2. En [4] , vemos que hemos modificado con éxito foo .

Seré más directo al admitir que mi conocimiento de Lisp es extremadamente mínimo. Sin embargo, estoy muy interesado en el lenguaje y tengo el plan de comenzar a aprenderlo seriamente en el futuro cercano. Mi comprensión de estos temas es, sin duda, defectuosa, así que si digo algo que es falsamente erróneo, por favor comenten y corríjanme en lugar de declinar.

Idiomas verdaderamente homocónicos y auto modificables

Estoy buscando ejemplos de lenguajes de programación que soporten tanto la Homoiconicidad (el código tiene la misma representación que los datos) como la auto modificación sin restricciones (significado sin restricciones que puede cambiar cada aspecto de su código en ejecución, no simplemente emitir nuevo código o cambiar los punteros de función / delegados)

Solo hay tres ejemplos que he encontrado hasta ahora que se ajustan a este criterio:

  1. Codigo de maquina. Homoiconic en que todo es un número. Sin restricciones modificables, ya que incluye punteros, que se pueden usar para manipular cualquier dirección de memoria, independientemente de si esa dirección contiene código o datos.
  2. Malbolge. Mismo razonamiento que el código de máquina. Cada instrucción se modifica después de ser ejecutada
  3. ADN. No es un lenguaje de programación, pero sigue siendo interesante. No se auto modifica en el mismo sentido que el código de máquina; Donde las instrucciones reales + datos se modifican en su lugar. Sin embargo, se auto replica y puede mutar / evolucionar de acuerdo con su estado anterior (con efectos secundarios como la radiación atornillándolo de vez en cuando). Esta es solo una forma indirecta de auto modificación de todos modos. En resumen, el ADN puede auto-modificarse, pero lo hace al reproducirse en su totalidad junto con las mutaciones relevantes. Una cadena física de ADN es "inmutable".

Por qué Lisp no está en esta lista

Lisp no está en esa lista porque me parece que Lisp es solo casi Homoiconic, y solo admite la auto modificación restringida. Puedes hacer algo como

(+ 1 2 3)

que hará lo mismo que

(eval ''(+ 1 2 3))

En la primera versión (+ 1 2 3) está el código en bruto, mientras que en la segunda versión son datos. Al asumir la verdad de esta afirmación, se puede argumentar que Lisp ni siquiera es homicónico. El código tiene la misma representación que los datos en el sentido de que ambos son listas / árboles / expresiones S. Pero el hecho de que tenga que marcar explícitamente cuáles de estas listas / árboles / expresiones S son código y cuáles datos para mí parece decir que Lisp no es homicónico después de todo. Las representaciones son extremadamente similares, pero difieren en el pequeño detalle que tiene que decir realmente si se trata de código o datos. Esto de ninguna manera es algo malo (de hecho, cualquier otra cosa sería una locura), pero resalta una diferencia entre Lisp y el código máquina. En el código máquina no es necesario marcar explícitamente qué números son instrucciones, cuáles son punteros y cuáles son datos. Todo es simplemente un número hasta que realmente se requiera una interpretación, en ese punto podría ser cualquiera de esas cosas.

Es un caso aún más fuerte contra la auto modificación sin restricciones. Claro, puedes tomar una lista que represente algún código y manipularlo. Por ejemplo, cambiar

''(+ 1 2 3)

a

''(+ 1 4 3)

Y luego lo ejecuta a través de eval . Pero cuando haces esto, estás compilando código y ejecutándolo. No está modificando el código existente, solo está emitiendo y ejecutando código nuevo. C # puede hacer exactamente lo mismo utilizando árboles de expresiones, incluso si tiene un formato menos conveniente (que surge debido a que el código C # tiene una representación diferente de su AST, en oposición a Lisp, que es su propio AST). ¿Se puede tomar un archivo fuente completo y comenzar a modificar ese archivo fuente completo mientras se está ejecutando, y los cambios realizados en el archivo fuente tienen efectos en tiempo real sobre el comportamiento del programa?

A menos que haya alguna forma de hacerlo, Lisp no es ni homicónico ni auto modificador. (Para posponer un argumento sobre las definiciones, Lisp no es homoicónico ni auto modificador en la misma medida en que lo es el código máquina ) .

Formas de hacer Lisp Homoiconic / Sin restricciones auto-modificables

Puedo ver 3 maneras posibles de hacer Lisp como homoicónico / auto-modificable como código de máquina.

  1. Arquitectura no Von-Neumann. Si alguien pudiera inventar alguna máquina hipotética asombrosa donde la representación de programas de nivel más bajo sea un AST que pueda ejecutarse directamente (no es necesaria una compilación adicional). En una máquina así, un AST representaría tanto las instrucciones ejecutables como los datos. Lamentablemente, el problema no se habrá resuelto, porque la AST todavía tiene que ser código o datos. La presencia de una función eval no cambia esto. En el código de máquina, puede alternar entre el código y los datos todo lo que quiera. Mientras que con eval y Lisp una vez que ha "evadido" una lista de datos a código y la ha ejecutado, no hay forma de volver a recuperar esa lista como datos. De hecho, esa lista se ha ido para siempre y ha sido reemplazada por su valor. Nos perderíamos algo crucial, que resulta ser punteros.
  2. Listar etiquetas. Si fuera un requisito que cada lista también tenga una etiqueta única, sería posible realizar auto-modificación indirecta ejecutando funciones en una lista con una etiqueta determinada. Combinado con las continuaciones esto finalmente permitiría el código de auto modificación en el mismo sentido en que lo tiene el código de máquina. Las etiquetas son equivalentes a las direcciones de memoria del código de máquina. Como ejemplo, considere un programa Lisp donde el nodo superior del AST tiene la etiqueta "principal". Dentro de la línea principal, puede ejecutar una función que tome una etiqueta, un Entero, un Átomo, y copie el átomo a la Lista con una etiqueta que coincida con la proporcionada a la función, en el índice especificado por el Entero. Luego solo llame con la continuación actual en main. Ahí lo tienes, código de auto modificación.
  3. Lisp Macros. No me he tomado el tiempo para entender las macros de Lisp, y de hecho pueden hacer exactamente lo que estoy pensando.

El punto 1. combinado con 2. produciría un Lisp totalmente auto modificador. Siempre que la máquina mágica Lisp descrita pueda ser producida. 2. solo podría producir un Lisp auto modificador, sin embargo, la implementación en una arquitectura Von Neumann podría ser extremadamente ineficiente.

Las preguntas

  1. ¿Hay algún otro idioma aparte del código de máquina, adn y malbolge que pueda modificarse por completo y sea homicónico?
  2. (NO moleste en responder si hizo un tl; dr en el texto anterior) . ¿El lisp es realmente homoicónico + auto modificador? Si tú lo dices, ¿puedes citar exactamente en qué parte de mi argumento me extravío?

Apéndice

Idiomas con auto modificación no restringida pero sin homiconicidad

  1. Montaje. El código utiliza palabras en lugar de números, por lo que pierde homiconicidad, pero todavía tiene punteros, lo que conserva el control total sobre la memoria y permite la auto modificación sin restricciones.
  2. Cualquier lenguaje que use punteros sin formato. Por ejemplo C / C ++ / Objective C. Mismo argumento que Assembly
  3. Lenguajes JIT que incluyen punteros virtuales. Por ejemplo C # /. Net ejecutándose en un contexto inseguro. Mismo argumento que la Asamblea.

Otros conceptos e idiomas que pueden ser de algún modo relevantes / interesantes: Lisp, Ruby, Snobol, Forth y su metaprogramación en tiempo de compilación, Smalltalk y su reflejo, el cálculo lambda no tipeado con su propiedad de que todo es una función (lo que implica que asumimos que podría inventar una máquina que ejecute el cálculo lambda directamente, el cálculo lambda sería homoicónico y el código de máquina Von Neumann no lo sería cuando se ejecutara en dicha máquina. [Y el teorema de Godels sería ejecutable. Jaja, pensamiento aterrador: P])


Las macros pueden evitar las citas, si eso es lo que buscas:

> (defmacro foo (x) (cdr x)) > (foo (+ - 5 2)) 3

¿Es (+ - 5 2) código o datos? En el momento de la escritura parece datos. Después del tiempo de macro expansión parece un código. Y si la definición de foo estuviera en otra parte, podría ser considerada (incorrectamente) como una función, en cuyo caso (+ - 5 2) se considerará como un código que se comporta como datos que se comportan como código.


Lisp es una familia de lenguajes de programación. Los miembros de esta familia son muy diferentes en sus capacidades y técnicas de implementación. Hay dos interpretaciones diferentes de esto:

  • Lisp es una familia de idiomas que comparten un conjunto de características superpuestas. Esto incluye todo, desde el primer Lisp hasta Maclisp, Scheme, Logo, Common Lisp, Clojure y cientos de idiomas diferentes y sus implementaciones.

  • Lisp también tiene una rama principal de idiomas que también tienen en su mayoría ''Lisp'' en su nombre: Lisp, MacLisp, Common Lisp, Emacs Lisp, ...

Se han invertido muchas investigaciones con el tiempo para mejorar los lenguajes (hacerlo más "funcional" o hacerlo más orientado a objetos, o ambos) y para mejorar las técnicas de implementación.

Common Lisp, por ejemplo, admite la compilación, varias optimizaciones y más, para permitir a los desarrolladores usarlo en proyectos grandes donde se necesita un equilibrio entre flexibilidad y características. Una función compilada es el código máquina y ya no es una estructura de datos compuesta de listas, símbolos, cadenas y números.

Common Lisp permite implementaciones para crear código estático. Al mismo tiempo, deja un lugar para las modificaciones controladas del tiempo de ejecución (por ejemplo, mediante el uso de un compilador en tiempo de ejecución, cargando código, evaluando el código, reemplazando el código, ...).

OTOH si tiene una implementación de Lisp con un intérprete y, además, el intérprete puede usar estructuras de datos Lisp para representar el origen, entonces puede cambiar los programas en ejecución, por ejemplo, cambiando la estructura de la lista. Hay implementaciones de dialectos de Lisp que permiten eso. Una característica típica es el uso de macros, que se pueden calcular en tiempo de ejecución mediante dicho sistema. Algunos otros Lisps tienen los llamados Fexprs, que son un mecanismo similar (pero que generalmente no se pueden compilar con eficacia).

En una aplicación basada en Common Lisp (por ejemplo, un sistema CAD escrito en Lisp) donde gran parte de la información de origen ha sido eliminada por una herramienta de entrega, esto no sería posible. Uno tendría un solo ejecutable de código de máquina, que tiene gran parte de la flexibilidad de tiempo de ejecución eliminada.

La homonicidad tampoco es un concepto muy bien definido. Para Lisp me gusta más que decir que el código fuente se puede convertir en datos, porque el código fuente usa la representación externa de s-expressions, que es un formato de serialización de datos. Pero no todas las expresiones s son un programa Lisp válido. También una representación interna del código fuente como datos Lisp no es ''icónico'' de ninguna manera. Lisp tiene el código fuente como s-expresiones externas y después de leer el código fuente, tiene una representación interna como datos Lisp. LEER lo lee, IMPRIMIR lo imprime y EVAL lo evalúa.

También hay otros enfoques en Lisp para proporcionar acceso al Lisp que está ejecutando su programa y al programa:

  • el protocolo metaobjeto en CLOS (el sistema de objetos Common Lisp) es un ejemplo

  • 3Lisp proporciona una torre infinita de intérpretes. Hay un intérprete Lisp ejecutando su programa. Este intérprete de Lisp lo ejecuta otro intérprete de Lisp, que se está ejecutando nuevamente en otro, y ese también ...


Me encuentro con un fanboy aquí, y lo he dicho antes, pero lea On Lisp de Paul Graham si quiere aprender sobre Lisp Macros. Son un gran problema en términos de permitir modificaciones que de otro modo serían inviables. Además, creo que es importante distinguir aquí entre Lisp la familia del lenguaje, un Lisp dado y una implementación dada de Lisp.

Supongo que el principal problema que tomo con su argumento viene en el primer párrafo después de "¿Por qué Lisp no está en esta lista?", Y tiene que ver con el REPL en Lisp. Cuando ingresa el exp (+ 1 2 3) en el intérprete, de hecho está llamando a la función EVAL en la lista (+ 1 2 3). El "código en bruto" que describe es, de hecho, "datos", que se alimenta a otro código de ceceo, solo son datos en un contexto específico.