1 puntos por GN⁺ 2024-10-18 | 1 comentarios | Compartir por WhatsApp
  • El texto de Douglas Hofstadter de 1983 presenta Lisp, ampliamente usado en la investigación de IA, como un lenguaje elegante y flexible construido sobre un pequeño núcleo matemático
  • La interactividad de Lisp se muestra en el read-eval-print loop, donde el usuario ingresa una expresión, el intérprete la lee, la evalúa, imprime el resultado y luego espera la siguiente entrada
  • Los objetos de Lisp se componen principalmente de átomos (atom) y listas (list), y nil ocupa una posición excepcional al ser tanto una lista vacía como un átomo
  • quote, eval, car, cdr, cons, setq, set, lambda, def, cond permiten tratar código y datos con la misma estructura de listas, haciendo posible construir nuevas funciones de forma gradual
  • La composición de funciones, las expresiones condicionales, los efectos secundarios y el estilo applicative, así como el pensamiento recursivo, se conectan mediante ejemplos, y la función final power junto con la metáfora del “porpuquine” sugieren la recursión al estilo Lisp

El lugar de Lisp en la investigación de IA

  • En 1983, la IA era un campo de investigación que buscaba implementar en computadoras conductas como flexibilidad, sentido común, intuición, creatividad, autoconciencia y humor
  • En Estados Unidos había unas 2 mil personas involucradas profesionalmente en IA, y en el extranjero había una cantidad similar de investigadores
  • Los investigadores de IA diferían mucho sobre cuál era la mejor ruta hacia la IA, pero en la elección del lenguaje de programación casi todos usaban Lisp
  • El nombre Lisp viene de “list processing” y no es un acrónimo completo
  • El atractivo de Lisp está en que es un lenguaje “crisp” y “elegant”
    • Mientras muchos lenguajes incluyen muchas funciones arbitrarias, algunos como Lisp y Algol se organizan alrededor de un núcleo natural, como una rama de las matemáticas
    • El núcleo de Lisp tiene una “crystalline purity”, y esa pureza contribuye no solo a la estética sino también a la flexibilidad

De la lógica matemática a un lenguaje de procesamiento de listas

  • Las raíces profundas de Lisp están en la lógica matemática
    • Ideas que Thoralf Skolem, Kurt Godel y Alonzo Church dejaron en la lógica en las décadas de 1920 y 1930 terminaron incorporándose después en Lisp
  • La programación de computadoras en serio comenzó en la década de 1940, y los lenguajes de alto nivel como Lisp aparecieron en la década de 1950
  • El primer lenguaje de procesamiento de listas no fue Lisp sino IPL (Information Processing Language)
    • IPL fue desarrollado a mediados de los años 50 por Herbert Simon, Allen Newell y J. C. Shaw
  • John McCarthy creó Lisp entre 1956 y 1958 a partir de ideas previas, como un lenguaje algebraico de procesamiento de listas
    • Lisp se difundió rápidamente entre los jóvenes investigadores alrededor del MIT Artificial Intelligence Project
    • Fue implementado en la IBM 704 y se expandió a otros grupos de IA
    • Surgieron varios dialectos, pero compartían el elegante núcleo central

Lisp interactivo y el REPL

  • A diferencia de muchos otros lenguajes de alto nivel, Lisp se presenta como un lenguaje interactivo
  • El usuario ejecuta Lisp en una terminal, ve un prompt e ingresa expresiones
    • Ejemplo: si escribe (plus 2 2), se imprime 4 y aparece un nuevo prompt
  • El comportamiento central del intérprete de Lisp es el read-eval-print loop
    • Lee una expresión
    • Evalúa la expresión
    • Imprime un valor apropiado
    • Indica que está listo para leer la siguiente expresión
  • Gracias a esta estructura, el usuario puede ingresar un “deseo” a la vez y ver el resultado de inmediato
  • En muchos lenguajes no interactivos hay que escribir primero el programa completo y luego ejecutarlo, de modo que muchas etapas deben encajar perfectamente, y corregir errores es más indirecto y costoso
  • Lisp permite desarrollo y depuración graduales al ejecutar una expresión a la vez

Notación polaca y paréntesis

  • Las expresiones aritméticas en Lisp usan notación polaca, donde el operador va antes de los operandos
    • Ejemplo: (times (plus 6 3) (difference 6 3)) se evalúa como 27
  • Esta notación fue creada antes de las computadoras por el lógico polaco Jan Lukasiewicz
  • En las expresiones Lisp aparecen muchos paréntesis
    • Es común que las expresiones largas terminen con varios paréntesis de cierre
    • Al principio puede intimidar, pero una vez que uno se acostumbra, la estructura lógica se vuelve intuitiva
  • Los ejemplos se presentan en forma de pretty printing, usando indentación para mostrar la estructura

Átomos, listas y nil

  • El corazón de Lisp es la estructura manipulable, y todos los programas funcionan creando, modificando y destruyendo estructuras
  • Hay dos tipos de estructura
    • Átomo (atom): objeto básico que ya no se puede dividir
    • Lista (list): estructura formada por varios elementos en un orden determinado
  • Todo objeto de Lisp es un átomo o una lista, con una sola excepción: nil
    • nil es a la vez átomo y lista
    • nil representa la lista vacía
    • () y nil significan lo mismo, aunque nil se usa más
  • Los elementos de una lista pueden ser átomos o listas
    • Ejemplo: (zonk blee strill (croak flonk)) es una lista de cuatro elementos, y el último vuelve a ser una lista de dos elementos
  • Las propias sentencias de Lisp también son listas
    • (plus 2 2) también es una lista
    • El intérprete de Lisp puede manipular listas y átomos para crear y evaluar nuevos “comandos”

Valores, quote y eval

  • Los átomos pueden tener valores
    • Un átomo numérico tiene como valor permanente a sí mismo
    • El valor de nil es nil
    • t también es un átomo especial cuyo valor permanente es él mismo
  • setq asigna un valor a un átomo
    • (setq pie 4) hace que el valor de pie sea 4
    • (setq pie (plus 2 2)) también hace que el valor de pie sea 4
  • El valor de un átomo no tiene que ser un número; puede ser cualquier objeto de Lisp
    • Puede ser otro átomo o una lista
  • quote evita la evaluación y hace que una lista o átomo se trate tal cual como dato
    • (setq pie (plus 2 2)) evalúa la expresión interna y guarda 4 en pie
    • (setq pi '(plus 2 2)) guarda en pi la propia lista (plus 2 2)
  • eval vuelve a evaluar una expresión almacenada como valor
    • Si el valor de pi es (plus 2 2), entonces (eval pi) devuelve 4
    • El intérprete normalmente evalúa solo un nivel, pero con eval se puede pedir una evaluación adicional

car, cdr, cons

  • Toda lista que no sea nil tiene al menos un elemento, y al primero se le llama car
    • El car de (eval pi) es eval
    • El car de (plus 2 2) es plus
  • El car no tiene por qué ser el nombre de una función
    • El car de ((1)(2 2) (3 3 3)) es (1)
  • La lista que queda después de quitar el car es el cdr
    • El cdr de (a b c d) es (b c d)
    • Luego se reduce a (c d), (d) y nil
  • nil no tiene ni car ni cdr, y si se intenta tomar su car o cdr debe producirse un error
  • cons crea una nueva lista añadiendo un nuevo car delante de una lista existente
    • Si x es (cake cookie), entonces (cons 'pie x) crea (pie cake cookie)
    • En este caso, x no se modifica
  • El resultado cambia según haya o no quote
    • Si el valor de pie es 4, (cons pie x) devuelve (4 cake cookie)
    • (cons 'pie x) pone al frente el propio nombre del átomo pie

La diferencia entre setq y set

  • El intérprete de Lisp no siempre evalúa sus argumentos, y setq es una excepción representativa
  • El primer argumento de setq no se evalúa y se trata directamente como nombre de variable
    • La q de setq significa quote, así que el primer argumento se trata como si estuviera citado
  • set es parecido a setq, pero sí evalúa también el primer argumento
    • Si el valor de x es k, entonces (set x 7) no cambia a x sino que cambia el valor de k a 7
  • Se da como ejemplo la siguiente secuencia
    • (setq a 'b)
    • (setq b 'c)
    • (setq c 'a)
    • (set a c)
    • (set c b)
  • Después de ejecutarla, los valores de a, b y c pasan a ser todos a
  • set no se usa mucho, y este tipo de confusión tampoco es muy frecuente

Definición de funciones y lambda

  • Una gran fuerza de la programación está en definir nuevas operaciones compuestas a partir de operaciones existentes, y repetir esto para construir un repertorio de operaciones cada vez más complejo
  • En Lisp se definen nuevas funciones usando funciones ya conocidas
  • rac es un ejemplo de función que devuelve el último elemento de una lista
    • Para obtener el último elemento, se puede invertir la lista y tomar su car
    • Definición: (def rac (lambda (lyst) (car (reverse lyst))))
  • (lyst) después de lambda representa el parámetro de la función, es decir, la variable ficticia
  • Después de definirla con def, rac pasa a ser una función utilizable igual que car
    • (rac '(your brains)) devuelve brains
  • La función readers-digest-condensed-version construye una lista breve que contiene solo el primer y el último elemento de una lista larga
    • Si se toma toda Finnegans Wake de James Joyce como una lista de palabras, el ejemplo da como resultado (riverrun the)
  • rejoyce, una operación inversa, es una función imaginaria que generaría una novela cuyo comienzo y final quedan fijados por dos palabras, y se deja como ejercicio para el lector

Valor de retorno, efectos secundarios y estilo applicative

  • Algunas personas consideran deseables y posibles dos objetivos en Lisp y lenguajes relacionados
    • Que toda sentencia devuelva un valor
    • Que los efectos de una sentencia ocurran solo a través del valor devuelto
  • Los dialectos de Lisp tratados en el texto cumplen la primera condición, pero no necesariamente la segunda
  • (reverse x) no cambia a x, sino que crea y devuelve una nueva lista igual a x pero en orden inverso
    • Del mismo modo que (plus 2 2) no cambia el valor de 2
  • cons tampoco modifica la lista existente, sino que devuelve una nueva
  • En cambio, setq es un ejemplo de instrucción que deja un efecto secundario al cambiar un enlace de variable
    • Entre los efectos secundarios, además de cambiar enlaces de variables, también puede haber entrada/salida
  • Quienes apoyan la programación applicative prefieren únicamente la forma en que las funciones calculan y pasan valores sin efectos secundarios
  • En este estilo, el único enlace permitido es el lambda binding temporal que aparece durante la llamada a una función
    • Cuando termina el cálculo de la función, desaparece el enlace de la variable ficticia
  • El narrador del texto considera elegante el estilo applicative, pero cree que no es práctico para construir programas grandes al estilo de la IA
  • En una versión extrema de la programación applicative estricta, incluso def sería inadmisible porque guarda definiciones permanentes de funciones en memoria

La expresión condicional cond y la toma de decisiones

  • Para que Lisp haga cosas más interesantes, debe poder tomar decisiones según lo que ocurra en el proceso intermedio, y para eso hacen falta expresiones condicionales
  • Ejemplo: (cond ((eq x 1) 'land) ((eq x 2) 'sea))
    • Si x es 1, devuelve land
    • Si x es 2, devuelve sea
    • En cualquier otro caso devuelve nil
  • eq es una función de Lisp que devuelve t si los valores de dos argumentos son iguales y nil si no lo son
  • La sentencia cond comienza con el nombre de función cond y tiene varias cláusulas
    • Cada cláusula es una lista de dos elementos compuesta por una condición y un resultado
    • Las condiciones se revisan en orden, y cuando se encuentra la primera que devuelve un valor distinto de nil, se evalúa su resultado y ese se devuelve como valor del cond completo
    • Las cláusulas posteriores ya no se revisan
  • Si al final se agrega una cláusula catch-all con t como condición, se puede devolver un resultado por defecto en lugar de nil
    • Se presenta un cond que devuelve air cuando fallan tanto la condición de land como la de sea

Potencias y estructura recursiva

  • Hacia el final del texto aparecen definiciones de funciones con un patrón muy claro
    • square es k * k
    • cube es k * square(k)
    • 4th-power es k * cube(k)
    • Y así sucesivamente
  • Se plantea la pregunta de si es posible definir de una vez una función de dos parámetros, power, que abarque todo ese patrón
    • (power 9 3) debe dar 729
    • (power 7 4) debe devolver 2,401
  • Todas las herramientas necesarias ya se presentaron en el texto, y se apela al ingenio del lector
  • La historia final del porpuquine glazunkiano trata la estructura recursiva como una alegoría
    • Las púas del porpuquine son porpuquines más pequeños
    • En Outer Glazunkia siempre hay 9 púas, y en Inner Glazunkia siempre hay 7 púas
    • Un porpuquine de 0 pulgadas no tiene púas, y así se detiene la regresión infinita
  • El “buying power” o “power” de este animal se conecta con la cantidad de pequeñas narices de porpuquines de 0 pulgadas que contiene, cerrando el texto como una metáfora vinculada al problema de power presentado antes

Contexto de esta reimpresión del original

  • La introducción del Gist aclara que este texto es una presentación de Lisp escrita por Douglas Hofstadter y encontrada en un número atrasado de Scientific American de mediados de los años 80
  • Los ejemplos todavía pueden ejecutarse en Emacs si se instalan algunos alias
    • plus+
    • quotient/
    • times*
    • difference-
  • El cierre indica que, si disfrutaste esta peculiar guía de Lisp de Hofstadter, puedes encontrar más textos parecidos en su libro Metamagical Themas

1 comentarios

 
GN⁺ 2024-10-18
Comentarios de Hacker News
  • Me confundieron los nombres de las funciones "oval" y "snot" en el ejemplo, pero unos segundos después me di cuenta de que eran errores tipográficos de "eval" y "snoc", respectivamente.
    El código original debería leerse como (cond ((eq (eval pi) pie) (eval (snoc pie pi))) (t (eval (snoc (rac pi) pi)))).
    Al descargar y revisar el artículo original de Scientific American en https://www.jstor.org/stable/24968822, confirmé que, efectivamente, oval/snot eran errores tipográficos de eval/snoc.

    • Corrección de la corrección: "oval" y "snot" sí son errores tipográficos de "eval" y "snoc".
      Y snoc parece ser cons al revés, mientras que rac parece ser car al revés.
    • También podría ser un error de OCR.
  • Este texto vuelve a mostrar que una causa clave de la poca popularidad de Lisp fue la forma en que se explicaba Lisp.
    Estos textos sobre Lisp no me enseñaban, cuando era chico, nada que pudiera usar en la práctica, ni mostraban cuánto más fácil se volvía alguna tarea X en comparación con ensamblador/C/Pascal, etc.
    Si fuera yo ahora, habría mostrado algo como: “¿Un corrector ortográfico a medida que tomó 7 meses en ensamblador? En Lisp, en una microcomputadora con memoria conmutada por bancos, es algo casi trivial, y hasta el garbage collection puede hacerse determinista en CPUs pésimas”.
    Tantos textos y manuales de Lisp repetían listas, recursión e IA, pero no mostraban cómo hacer cosas útiles, que me parece una pena todo ese tiempo programando como con pinzas, granos de arroz y pegamento.

    • Common Lisp: A Gentle Introduction to Symbolic Computation puede ser útil en este contexto: https://www.cs.cmu.edu/~dst/LispBook/
    • También está Practical Common Lisp: https://gigamonkeys.com/book/
    • En artículos posteriores de Hofstadter había ejemplos más interesantes.
      En la primera revista Byte que vi de chico había código en Lisp para diferenciación simbólica y simplificación algebraica; me costaba seguirlo, pero sentía que tenía algo atractivo.
      No habría sido más fácil en Basic, y más tarde vi que ese código no era precisamente excelente, pero no fue sino hasta encontrar XLisp en una PC de fines de los 80 y SICP que realmente me enganché con Lisp.
  • Me encanta cómo escribe Hofstadter. Captura muy bien lo que sentí al descubrir Lisp.
    Era un niño aprendiendo a programar en los 80, y en secundaria y al inicio de la universidad ya había hecho un poco de BASIC, Fortran, Pascal y COBOL; aunque había diferencias, en el fondo tenían cosas en común.
    Pero mi primer curso de ciencias de la computación en UC Berkeley se dictó en Scheme, un dialecto de Lisp, y fue un shock total.
    Como dice Hofstadter, se sentía como lo más cercano a las matemáticas; me recordaba mucho a las clases de teoría matemática, y fue el primer lenguaje hermoso que descubrí.
    Me gustó especialmente esta cita: “Lisp y Algol fueron construidos alrededor de un núcleo tan natural como una rama de las matemáticas. En el corazón de Lisp hay una pureza cristalina que no solo apela al sentido estético, sino que también lo hace mucho más flexible que la mayoría de los lenguajes”.

    • Me da curiosidad si probaste Haskell. Para mí se siente mucho más cercano a las matemáticas porque está hecho de definiciones, no de procedimientos, y por fuera también se ve como matemáticas.
    • A riesgo de desviarme un poco del texto original, creo que llamarlo “matemáticas” puede terminar enturbiando la idea.
      Incluso desde la perspectiva de alguien que quería ser topólogo algebraico, Scheme se parece más a una expresión elegante y mínima del estilo de programación extendido entre los lenguajes con tipos dinámicos y garbage collection.
      Se siente como “teoría” en el sentido de que parece completo, y de que si uno piensa la solución de un problema en Scheme y luego la traslada a otro lenguaje de tipos dinámicos, puede llegar a una solución elegante.
      Scheme era un lenguaje simplificado y depurado frente al Lisp tradicional de la época, con aspectos como el alcance léxico y un único espacio de nombres para funciones y variables.
  • En 1983, cuando el campo llevaba unos 20 años desde su inicio, ya se hablaba de una IA que programara computadoras para tener “flexibilidad, sentido común, perspicacia, creatividad, autoconciencia y humor”.
    Es bastante gracioso que esa lista se lea como una lista de cosas que los LLM realmente no hacen bien.
    Aun así, al menos hubo un avance distinto de cero en esa dirección.

    • Comparados con la gente común que conozco, los LLM parecen mostrar sentido común, perspicacia, creatividad, autoconciencia y humor al nivel de una persona promedio.
      Tal vez siento que muestran más, pero decirlo así hasta a mí me suena un poco raro.
    • El grupo de investigación de Hofstadter tiene una larga historia de intentar abordar estos problemas.
      Aunque muchas cosas todavía no hayan dado frutos, vale la pena leerlo por lo interesante.
  • Los dos textos complementarios mencionados junto con este artículo aparecen en los capítulos 17 a 19 del libro de Hofstadter Metamagical Themas, que también incluye otros artículos de la misma columna en Scientific American.
    [0]: https://www.goodreads.com/book/show/181239.Metamagical_Thema...
    El título del libro viene del título de la columna, que era un anagrama de “Mathematical Games”, la columna que Martin Gardner escribía en Scientific American y que Hofstadter tomó a su cargo.

    • Me encanta este libro. Lo recomiendo muchísimo a quienes disfrutan el clásico de Hofstadter Gödel, Escher, Bach.
    • Este libro es realmente hermoso tanto para leer como para mirar, y tiene muchas ilustraciones interesantes.
  • El artículo dice que aplicar car o cdr a nil debería producir un error, como dividir entre 0, pero en muchos Lisp modernos ya no es así
    En el Lisp original definido por John McCarthy, CAR y CDR no estaban definidos para NIL: <https://dl.acm.org/doi/pdf/10.1145/367177.367199>
    Pero Common Lisp y Emacs Lisp definen (car nil) y (cdr nil) como nil: <https://www.lispworks.com/documentation/HyperSpec/Body/f_car...>, <https://www.gnu.org/software/emacs/manual/html_node/elisp/Li...>

    • Me dio curiosidad saber cómo era en Maclisp, así que entré por telnet al ITS público de Lars Brinkhoff y lo comprobé
      En LISP 2156, (status lispversion) devolvió /2156, y (car nil) y (cdr nil) devolvieron ambos NIL
    • Sorprende que detalles de implementación tan concretos como car y cdr hayan sido centrales en el nacimiento de un lenguaje diseñado matemáticamente
      Los operadores más básicos y famosos de “List Processor” no estaban hechos para operar sobre listas, sino sobre cons, un elemento específico de la representación de máquina que Lisp usa para construir estructuras de datos
      Un cons no siempre se interpreta como lista, y una lista muy importante, el caso base de las funciones recursivas sobre listas, ni siquiera se representa con un cons
      Incluso 60 años después, la mayoría de los programas Lisp están llenos de operaciones sobre cons, y quizá un nombre más preciso habría sido “Cons Processor”
      Esto recuerda que Lisp nació en una época en que el lenguaje y la implementación tenían que encajar estrechamente, y por eso el logro de haber basado un lenguaje de computación en la lógica matemática se siente aún más asombroso
    • Scheme no es así. Tomar el CAR o CDR de nil produce un error
    • Lamentablemente, en Scheme el car/cdr de nil es un error, así que para principiantes como yo el código se siente bastante poco ergonómico
      Aun así, prefiero Guile Scheme a Common Lisp, por lo que me da pena
    • Me pregunto si existe un término para la decisión de diseño de un lenguaje en la que aplicar algo a nil no produce error y vuelve a devolver nil
      También me viene a la mente SQL, y personalmente lo considero una mala decisión, así que si no hay otro término me gustaría llamarlo “bleeding nils/NULLs”
      Es especialmente peor si nil no es igual a false en comparaciones booleanas
      En Ruby y Elixir, nil se trata como false, y Elixir ofrece por separado and, que solo acepta booleanos puros, y &&, que trata nil como false
      Este diseño puede hacer que el código se vea limpio al principio, pero un nil mal manejado que en realidad es un error puede aparecer en una parte completamente distinta varias llamadas más abajo en la pila, haciendo que la depuración sea mucho más difícil
  • La información sobre Lisp en este artículo ya la conocía, pero igual me pareció una lectura entretenida. Hofstadter tiene un encanto especial con las palabras
    En particular, me hizo gracia el chiste de crear rejoyce, la operación inversa de readers-digest-condensed-version, de modo que al ejecutar (rejoyce 'Stately 'Yes) un hada de Lisp generaría desde cero todo Ulysses como si lo hubiera escrito James Joyce
    Tomó algo de tiempo, pero al final llegamos a ese punto; y aunque la IA de 2024 no sea exactamente igual a lo que él imaginó en 1983, reproducir texto a partir de una semilla breve es una tarea que encaja bastante bien con la IA actual

  • Creo que Lisp sigue siendo uno de los principales lenguajes capaces de expresar el concepto de bucle extraño que abordó Hofstadter.
    Lisp no es el único lenguaje homoicónico, pero está entre los más grandes, de los que la gente realmente sabe usar, en los que eval no recibe una cadena que tenga que parsear.
    No me gusta que la gente equipare Lisp con toda la programación funcional.
    No es que me disguste la programación funcional, pero el carácter simbólico de Lisp es mucho más interesante, y me resulta infinitamente divertido que también sea muy fácil crear código impulsado por secciones (go tag), al estilo de la programación con GOTO.

    • Otro lenguaje homoicónico propiamente funcional que tuvo una breve adopción mainstream en los 2000 fue XSLT.
      Con XSLT y XML se usaban bastante capacidades de metaprogramación, como generar XSLT en vez de escribir directamente código repetitivo.
      Sin embargo, su sintaxis era un problema todavía mayor que la de Lisp.
      No es fácil crear un lenguaje con una sintaxis cómoda para el uso diario y que, al mismo tiempo, no sea difícil de manipular como árbol de sintaxis abstracta; Lisp es uno de los pocos ejemplos relativamente exitosos.
    • En particular, cuando se llega a la programación basada en continuaciones de Scheme, la frontera entre programación funcional y no funcional se vuelve tan difusa que casi deja de tener sentido.
    • Me da curiosidad qué es, en la práctica, el soporte homoicónico/simbólico profundo que un programador de 2024 obtiene de Lisp y que no se logra bien con functools de Python: https://docs.python.org/3/library/functools.html
      Estoy construyendo AGI simbólica sin Lisp y quisiera escuchar pistas de expertos.
      Lo que entiendo de las funcionalidades del lado de Python son funciones que aplican otras funciones a objetos iterables, como filter(), map() y reduce(); wrappers que enrutan llamadas, como @singledispatch; funciones que dan conveniencias de control de flujo o rendimiento, como @cache y partial(); y capacidades para envolver funciones arbitrariamente, como wraps().
      La mayoría de esto parece ser más bien una serie de conveniencias para llamar funciones de maneras inusuales, y no parece llegar al nivel de los elogios a Lisp que tratan la “introspección” como una preocupación de primera clase.
      Quisiera saber qué ofrece realmente Lisp que no esté en esas funcionalidades.
    • En parte también se debe a que mucha gente conoce Lisp solo a través del mundo académico, y a que los académicos que enseñan Lisp no tienen interés en desarrollar cosas reales con Lisp.
      Lo usan como medio para transmitir conceptos, y esos conceptos por lo general giran en torno a la recursión funcional.
      Scheme y la cultura que lo rodea también tienen su parte.
      Aunque Scheme no es solo un lenguaje funcional, enfatiza más la programación pura que las familias anteriores de Lisp, su lenguaje base ofrece estructuras de recursión de cola en lugar de estructuras iterativas, y exige a las implementaciones optimización de llamadas de cola.
  • Incluso en Common Lisp, si se define defalias como macro, se puede usar como el defalias de Emacs: https://stackoverflow.com/questions/24252539/defining-aliase...

  • Me gustó el artículo sobre Lisp de Hofstadter en Metamagical Themas, y adapté y ordené el código del último artículo de esa serie para Clojure, para un grupo de estudio en el trabajo.
    [1] http://johnj.com/posts/oodles/

    • También me pregunto si existe una versión traducida/adaptada a un Lisp moderno.