Ya no necesitas investigar más por todo internet porque estás al espacio indicado, contamos con la respuesta que deseas sin problema.
Solución:
La explicación de Matt está perfectamente bien, y él intenta una comparación con C y Java, lo cual no haré, pero por alguna razón realmente disfruto discutiendo este tema de vez en cuando, así que aquí está mi oportunidad. en una respuesta.
En los puntos (3) y (4):
Los puntos (3) y (4) de su lista parecen los más interesantes y aún relevantes ahora.
Para comprenderlos, es útil tener una idea clara de lo que sucede con el código Lisp, en forma de un flujo de caracteres tecleados por el programador, en camino a ser ejecutado. Usemos un ejemplo concreto:
;; a library import for completeness,
;; we won't concern ourselves with it
(require '[clojure.contrib.string :as str])
;; this is the interesting bit:
(println (str/replace-re #"d+" "FOO" "a123b4c56"))
Este fragmento de código Clojure se imprime aFOObFOOcFOO
. Tenga en cuenta que Clojure posiblemente no satisface completamente el cuarto punto de su lista, ya que el tiempo de lectura no está realmente abierto al código de usuario; Sin embargo, discutiré lo que significaría que esto fuera de otra manera.
Entonces, supongamos que tenemos este código en un archivo en algún lugar y le pedimos a Clojure que lo ejecute. Además, supongamos (en aras de la simplicidad) que hemos superado la importación de la biblioteca. Lo interesante comienza en (println
y termina en el )
muy a la derecha. Esto es lexed / parsed como cabría esperar, pero ya surge un punto importante: el resultado no es una representación AST específica del compilador especial, es solo una estructura de datos normal de Clojure / Lisp, es decir, una lista anidada que contiene un montón de símbolos, cadenas y, en este caso, un único objeto de patrón regex compilado correspondiente a la #"d+"
literal (más sobre esto a continuación). Algunos Lisps agregan sus propios pequeños giros a este proceso, pero Paul Graham se refería principalmente a Common Lisp. En los puntos relevantes para su pregunta, Clojure es similar a CL.
Todo el lenguaje en tiempo de compilación:
Después de este punto, todo el compilador se ocupa (esto también sería true para un intérprete Lisp; El código de Clojure siempre se compila) son estructuras de datos Lisp que los programadores Lisp están acostumbrados a manipular. En este punto se hace evidente una maravillosa posibilidad: ¿por qué no permitir que los programadores Lisp escriban funciones Lisp que manipulen datos Lisp que representen programas Lisp y generen datos transformados que representen programas transformados, para ser utilizados en lugar de los originales? En otras palabras, ¿por qué no permitir que los programadores Lisp registren sus funciones como complementos del compilador, llamados macros en Lisp? Y, de hecho, cualquier sistema Lisp decente tiene esta capacidad.
Entonces, las macros son funciones Lisp regulares que operan en la representación del programa en tiempo de compilación, antes de la fase de compilación final cuando se emite el código objeto real. Dado que no hay límites en los tipos de macros de código que se permiten ejecutar (en particular, el código que ejecutan a menudo se escribe con un uso liberal de la macro facilidad), se puede decir que “todo el lenguaje está disponible en el momento de la compilación”.
Todo el idioma en el momento de la lectura:
Volvamos a eso #"d+"
regex literal. Como se mencionó anteriormente, esto se transforma en un objeto de patrón compilado real en el momento de la lectura, antes de que el compilador escuche la primera mención de que se está preparando un nuevo código para la compilación. ¿Como sucedió esto?
Bueno, la forma en que Clojure está implementado actualmente, la imagen es algo diferente a lo que Paul Graham tenía en mente, aunque todo es posible con un truco inteligente. En Common Lisp, la historia sería un poco más limpia conceptualmente. Sin embargo, los conceptos básicos son similares: el Lisp Reader es una máquina de estado que, además de realizar transiciones de estado y eventualmente declarar si ha alcanzado un “estado de aceptación”, escupe las estructuras de datos Lisp que representan los caracteres. Así los personajes 123
conviértete en el número 123
etc. El punto importante viene ahora: esta máquina de estado puede ser modificada por código de usuario. (Como se señaló anteriormente, eso es completamente true en el caso de CL; para Clojure, se requiere un truco (desaconsejado y no utilizado en la práctica). Pero estoy divagando, es el artículo de PG en el que se supone que debo estar elaborando, así que …)
Entonces, si usted es un programador de Common Lisp y le gusta la idea de literales vectoriales de estilo Clojure, puede simplemente conectar al lector una función para reaccionar adecuadamente a alguna secuencia de caracteres: [
or #[
possibly — and treat it as the start of a vector literal ending at the matching ]
. Esta función se llama lector macro y como un regular macro, puede ejecutar cualquier tipo de código Lisp, incluido el código que ha sido escrito con notación funky habilitada por macros de lectura previamente registradas. Así que tienes todo el idioma a la hora de leer.
Envolviendolo:
En realidad, lo que se ha demostrado hasta ahora es que se pueden ejecutar funciones Lisp regulares en tiempo de lectura o compilación; El único paso que hay que dar a partir de aquí para comprender cómo la lectura y la compilación son en sí mismas posibles en tiempo de lectura, compilación o ejecución es darse cuenta de que la lectura y la compilación las realizan funciones Lisp. Puedes simplemente llamar read
o eval
en cualquier momento para leer en Lisp datos de secuencias de caracteres o compilar y ejecutar código Lisp, respectivamente. Ese es todo el idioma allí mismo, todo el tiempo.
Tenga en cuenta que el hecho de que Lisp satisfaga el punto (3) de su lista es esencial para la forma en que se las arregla para satisfacer el punto (4): el sabor particular de las macros proporcionadas por Lisp se basa en gran medida en que el código esté representado por datos Lisp regulares, que es algo habilitado por (3). Por cierto, sólo el aspecto “arbóreo” del código es realmente crucial aquí – posiblemente podría tener un Lisp escrito usando XML.
1) Un nuevo concepto de variables. En Lisp, todas las variables son efectivamente punteros. Los valores son lo que tienen tipos, no variables, y asignar o vincular variables significa copiar punteros, no lo que apuntan.
(defun print-twice (it)
(print it)
(print it))
‘eso’ es una variable. Puede vincularse a CUALQUIER valor. No hay restricción ni tipo asociado con la variable. Si llama a la función, no es necesario copiar el argumento. La variable es similar a un puntero. Tiene una forma de acceder al valor que está vinculado a la variable. No hay necesidad de reserva memoria. Podemos pasar cualquier objeto de datos cuando llamamos a la función: cualquier tamaño y cualquier tipo.
Los objetos de datos tienen un ‘tipo’ y todos los objetos de datos pueden consultarse por su ‘tipo’.
(type-of "abc") -> STRING
2) Un tipo de símbolo. Los símbolos se diferencian de las cadenas en que puede probar la igualdad comparando un puntero.
Un símbolo es un objeto de datos con un nombre. Por lo general, el nombre se puede usar para encontrar el objeto:
|This is a Symbol|
this-is-also-a-symbol
(find-symbol "SIN") -> SIN
Dado que los símbolos son objetos de datos reales, podemos probar si son el mismo objeto:
(eq 'sin 'cos) -> NIL
(eq 'sin 'sin) -> T
Esto nos permite, por ejemplo, escribir una oración con símbolos:
(defvar *sentence* '(mary called tom to tell him the price of the book))
Ahora podemos contar el número de THE en la oración:
(count 'the *sentence*) -> 2
En Common Lisp, los símbolos no solo tienen un nombre, sino que también pueden tener un valor, una función, una lista de propiedades y un paquete. Por tanto, los símbolos se pueden utilizar para nombrar variables o funciones. La lista de propiedades se usa generalmente para agregar metadatos a los símbolos.
3) Una notación para código que usa árboles de símbolos.
Lisp usa sus estructuras de datos básicas para representar código.
La lista (* 3 2) puede ser tanto de datos como de código:
(eval '(* 3 (+ 2 5))) -> 21
(length '(* 3 (+ 2 5))) -> 3
El árbol:
CL-USER 8 > (sdraw '(* 3 (+ 2 5)))
[*|*]--->[*|*]--->[*|*]--->NIL
| | |
v v v
* 3 [*|*]--->[*|*]--->[*|*]--->NIL
| | |
v v v
+ 2 5
4) Todo el idioma siempre disponible. No existe una distinción real entre tiempo de lectura, tiempo de compilación y tiempo de ejecución. Puede compilar o ejecutar código mientras lee, leer o ejecutar código mientras compila y leer o compilar código en tiempo de ejecución.
Lisp proporciona las funciones READ para leer datos y código de texto, LOAD para cargar código, EVAL para evaluar código, COMPILE para compilar código e PRINT para escribir datos y código en texto.
Estas funciones están siempre disponibles. No se van. Pueden formar parte de cualquier programa. Eso significa que cualquier programa puede leer, cargar, evaluar o imprimir código, siempre.
¿En qué se diferencian en lenguajes como C o Java?
Esos lenguajes no proporcionan símbolos, código como datos o evaluación en tiempo de ejecución de datos como código. Los objetos de datos en C generalmente no están tipificados.
¿Hay algún otro idioma además de los de la familia LISP que tienen alguna de estas construcciones ahora?
Muchos idiomas tienen algunas de estas capacidades.
La diferencia:
En Lisp, estas capacidades están diseñadas en el lenguaje para que sean fáciles de usar.
Para los puntos (1) y (2), está hablando históricamente. Las variables de Java son prácticamente las mismas, por lo que debe llamar a .equals () para comparar valores.
(3) está hablando de expresiones-S. Los programas Lisp están escritos en esta sintaxis, que proporciona muchas ventajas sobre la sintaxis ad-hoc como Java y C, como capturar patrones repetidos en macros de una manera mucho más limpia que las macros C o plantillas C ++, y manipular código con la misma lista de núcleos. operaciones que utiliza para los datos.
(4) tomando C por ejemplo: el lenguaje es realmente dos sub-lenguajes diferentes: cosas como if () y while (), y el preprocesador. Utiliza el preprocesador para evitar tener que repetir todo el tiempo o para omitir el código con # if / # ifdef. Pero ambos lenguajes están bastante separados y no puede usar while () en tiempo de compilación como puede hacer #if.
C ++ hace que esto sea aún peor con las plantillas. Consulte algunas referencias sobre la metaprogramación de plantillas, que proporciona una forma de generar código en tiempo de compilación, y es extremadamente difícil para los no expertos entender. Además, es realmente un montón de trucos y trucos que utilizan plantillas y macros para los que el compilador no puede proporcionar soporte de primera clase; si comete un error de sintaxis simple, el compilador no puede darle un mensaje de error claro.
Bueno, con Lisp, tienes todo esto en un solo idioma. Usas las mismas cosas para generar código en tiempo de ejecución que aprendiste en tu primer día. Esto no sugiere que la metaprogramación sea trivial, pero ciertamente es más sencillo con un lenguaje de primera clase y soporte para compiladores.
Aquí tienes las comentarios y calificaciones
Recuerda algo, que tienes autorización de valorar este ensayo si te fue preciso.