ventajas traductor resueltos programacion lenguaje ejercicios ejemplos desventajas descargar common caracteristicas lisp clojure scheme common-lisp paul-graham

traductor - Por favor explique algunos de los puntos de Paul Graham sobre Lisp



lisp traductor (4)

1) Un nuevo concepto de variables. En Lisp, todas las variables son efectivamente punteros. Los valores son los que tienen tipos, no variables, y asignar o vincular variables significa copiar punteros, no a qué apuntan.

(defun print-twice (it) (print it) (print it))

''es'' una variable. Se puede vincular a CUALQUIER valor. No hay restricciones ni ningún tipo asociado a la variable. Si llama a la función, el argumento no necesita copiarse. La variable es similar a un puntero. Tiene una forma de acceder al valor que está vinculado a la variable. No hay necesidad de reservar 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 ser consultados por su ''tipo''.

(type-of "abc") -> STRING

2) Un tipo de símbolo. Los símbolos difieren 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 utilizar 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. Entonces, los símbolos se pueden usar para nombrar variables o funciones. La lista de propiedades se usa generalmente para agregar metadatos a símbolos.

3) Una notación para el código que usa árboles de símbolos.

Lisp usa sus estructuras de datos básicas para representar el código.

La lista (* 3 2) puede ser tanto datos como 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 lenguaje siempre disponible. No hay una distinción real entre el tiempo de lectura, el tiempo de compilación y el tiempo de ejecución. Puede compilar o ejecutar código mientras lee, lee o ejecuta código mientras compila y lee o compila código en tiempo de ejecución.

Lisp proporciona las funciones LEER para leer los datos y el código del texto, CARGAR para cargar el código, EVAL para evaluar el código, COMPILAR para compilar el código e IMPRIMIR para escribir los datos y el código para el texto.

Estas funciones están siempre disponibles. Ellos no se van. Pueden ser parte de cualquier programa. Eso significa que cualquier programa puede leer, cargar, evaluar o imprimir código, siempre.

¿Cómo son diferentes en idiomas como C o Java?

Esos idiomas no proporcionan símbolos, código como datos o evaluación de datos en tiempo de ejecución como código. Los objetos de datos en C suelen estar sin tipo.

¿Alguna otra lengua que no sean los idiomas de la familia LISP tiene alguno de estos constructos 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.

Necesito ayuda para entender algunos de los puntos de What Made Lisp de Paul Graham Different .

  1. Un nuevo concepto de variables. En Lisp, todas las variables son efectivamente punteros. Los valores son los que tienen tipos, no variables, y asignar o vincular variables significa copiar punteros, no a qué apuntan.

  2. Un tipo de símbolo. Los símbolos difieren de las cadenas en que puede probar la igualdad comparando un puntero.

  3. Una notación para el código que usa árboles de símbolos.

  4. El lenguaje completo siempre disponible. No hay una distinción real entre el tiempo de lectura, el tiempo de compilación y el tiempo de ejecución. Puede compilar o ejecutar código mientras lee, lee o ejecuta código mientras compila y lee o compila código en tiempo de ejecución.

¿Qué significan estos puntos? ¿Cómo son diferentes en idiomas como C o Java? ¿Alguna otra lengua que no sea la de la familia Lisp tiene alguna de estas construcciones ahora?


La explicación de Matt está perfectamente bien, y toma una foto en comparación con C y Java, lo que no haré, pero por alguna razón realmente disfruto discutir este mismo tema de vez en cuando, así que aquí está mi foto en una respuesta.

En los puntos (3) y (4):

Los puntos (3) y (4) en su lista parecen ser los más interesantes y relevantes en este momento.

Para comprenderlos, es útil tener una idea clara de lo que sucede con el código Lisp, en forma de una secuencia de caracteres escritos por el programador, en camino a su ejecución. 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 imprime aFOObFOOcFOO . Tenga en cuenta que Clojure podría decirse que no satisface completamente el cuarto punto de su lista, ya que el tiempo de lectura no está realmente abierto al código del usuario; Sin embargo, discutiré lo que significaría que esto sea de otra manera.

Entonces, supongamos que tenemos este código en un archivo en alguna parte 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. El bit interesante comienza en (println y termina en el ) a la derecha. Esto se lee / analiza como uno esperaría, pero ya surge un punto importante: el resultado no es una representación especial de AST específica del compilador: es simplemente una estructura de datos Clojure / Lisp regular , 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 que corresponde al literal #"/d+" (más sobre esto a continuación). Algunos Lisp añaden 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.

El lenguaje completo en tiempo de compilación:

Después de este punto, todo lo que trata el compilador (esto también sería cierto para un intérprete Lisp; el código Clojure siempre se compila) es estructuras de datos Lisp que los programadores Lisp están acostumbrados a manipular. En este punto, se hace evidente una posibilidad maravillosa: ¿por qué no permitir que los programadores Lisp escriban funciones Lisp que manipulen los datos Lisp que representan los programas Lisp y los datos transformados de salida que representan los programas transformados, para ser utilizados en lugar de los originales? En otras palabras, ¿por qué no permitir que los programadores de Lisp registren sus funciones como complementos de compilación, llamados macros en Lisp? Y, de hecho, cualquier sistema Lisp decente tiene esta capacidad.

Por lo tanto, las macros son funciones regulares de Lisp 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 código que se permite ejecutar macros (en particular, el código que ejecutan a menudo se escribe con el uso liberal de la facilidad macro), se puede decir que "todo el idioma está disponible en tiempo de compilación" ".

El idioma completo en el momento de la lectura:

Regresemos a ese literal de expresiones regulares #"/d+" . 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 oiga la primera mención del nuevo código que se está preparando para la compilación. ¿Como sucedió esto?

Bueno, la forma en que Clojure se implementa actualmente, la imagen es algo diferente de lo que Paul Graham tenía en mente, aunque todo es posible con un truco ingenioso . En Common Lisp, la historia sería un poco más clara conceptualmente. Sin embargo, los conceptos básicos son similares: el lector Lisp 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 caracteres 123 convierten en el número 123 etc. El punto importante viene ahora: esta máquina de estados puede ser modificada por el código de usuario . (Como se señaló anteriormente, eso es completamente cierto en el caso de CL, para Clojure, se requiere un truco (desalentado y no usado en la práctica). Pero estoy divagando, es el artículo de PG del que se supone que estoy elaborando, así que ...)

Entonces, si usted es un programador de Common Lisp y le agrada la idea de los literales de vectores al estilo de Clojure, puede simplemente conectar al lector una función para reaccionar apropiadamente a alguna secuencia de caracteres - [ o #[ posiblemente - y tratarlo como el comienzo de un vector literal que termina en la coincidencia ] . Dicha función se denomina macro de lector y al igual que una macro normal, puede ejecutar cualquier tipo de código Lisp, incluido el código que se ha escrito con notación funky habilitada por las macros de lector previamente registradas. Así que está todo el idioma a la hora de leer para usted.

Envolviendolo:

En realidad, lo que se ha demostrado hasta ahora es que uno puede ejecutar funciones regulares Lisp en tiempo de lectura o compilación; el primer paso que uno debe dar para comprender cómo la lectura y la compilación son posibles en el tiempo de lectura, compilación o ejecución es darse cuenta de que la lectura y la compilación las realizan las funciones de Lisp. Simplemente puede llamar a 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 lenguaje allí mismo, todo el tiempo.

Observe cómo el hecho de que Lisp satisfaga el punto (3) de su lista es esencial para la forma en que logra satisfacer el punto (4): el sabor particular de las macros proporcionadas por Lisp depende en gran medida del código representado por los datos Lisp regulares. que es algo habilitado por (3). Dicho sea de paso, solo el aspecto "arbolado" del código es realmente crucial aquí - posiblemente podrías tener un Lisp escrito usando XML.


Los puntos (1) y (2) también se adaptarían a Python. Tomando un ejemplo simple "a = str (82.4)", el intérprete primero crea un objeto de coma flotante con el valor 82.4. Luego llama a un constructor de cadenas que luego devuelve una cadena con el valor ''82 .4 ''. La ''a'' en el lado izquierdo es simplemente una etiqueta para ese objeto de cadena. El objeto flotante original fue recolectado como basura porque no hay más referencias al mismo.

En Scheme, todo se trata como un objeto de manera similar. No estoy seguro acerca de Common Lisp. Intentaré evitar pensar en términos de conceptos C / C ++. Me ralentizaron montones cuando estaba tratando de entender la hermosa simplicidad de Lisps.


Para los puntos (1) y (2), él 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 ofrece 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 las plantillas C ++, y manipular código con la misma lista básica operaciones que usa para datos.

(4) tomando C por ejemplo: el idioma es realmente dos sub idiomas diferentes: cosas como if () y while (), y el preprocesador. Utiliza el preprocesador para ahorrar tener que repetirse todo el tiempo, o para omitir el código con # if / # ifdef. Pero ambos idiomas están bastante separados, y no puede usar while () en el momento de la compilación como puede #if.

C ++ lo empeora 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. Además, en realidad se trata de un montón de intrusiones y trucos que utilizan plantillas y macros para las que el compilador no puede ofrecer 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. Utiliza el mismo material para generar código en tiempo de ejecución tal como lo aprendió en su primer día. Esto no es para sugerir que la metaprogramación es trivial, pero ciertamente es más sencillo con un lenguaje de primera clase y compatibilidad con el compilador.