online jdoodle ejecutar compiler compileonline autolisp scala programming-languages lisp read-eval-print-loop

scala - ejecutar - jdoodle lisp



¿Es Lisp el único idioma con REPL? (7)

Hay otros idiomas además de Lisp (ruby, scala) que dicen que usan REPL (Read, Eval, Print, Loop), pero no está claro si lo que significa REPL es el mismo que en Lisp. ¿En qué se diferencia Lisp REPL de no REP de Lisp?


¿En qué se diferencia Lisp REPL de no REP de Lisp?

Comparemos el REPL de Common Lisp con el IPython de Python.

Los dos puntos principales son:

  • Lisp es un lenguaje basado en imágenes. No es necesario reiniciar el proceso / REPL / la aplicación completa después de un cambio. Compilamos nuestra función de código por función (con advertencias del compilador, etc.).
  • No perdemos estado. Más aún, cuando actualizamos las definiciones de clase, nuestros objetos en el REPL también se actualizan, siguiendo las reglas que tenemos bajo control. De esa manera podemos recargar el código en un sistema en ejecución.

En Python, normalmente, inicias IPython o te colocan en ipdb. Usted define algunos datos hasta que pruebe su nueva función. Edita su fuente y desea volver a intentarlo, así que abandona IPython y comienza de nuevo todo el proceso. En Lisp (Common Lisp principalmente), en absoluto, todo es más interactivo.


Creo que es interesante comparar dos enfoques. Un bucle REPL básico en un sistema Lisp se vería así:

(loop (print (eval (read))))

Aquí hay dos implementaciones reales de Forth de un bucle REPL. No estoy dejando nada aquí, este es el código completo de estos bucles.

: DO-QUIT ( -- ) ( R: i*x -- ) EMPTYR 0 >IN CELL+ ! / set SOURCE-ID to 0 POSTPONE [ BEGIN / The loop starts here REFILL / READ from standard input WHILE INTERPRET / EVALUATE what was read STATE @ 0= IF ." OK" THEN / PRINT CR REPEAT ; : quit sp0 @ ''tib ! blk off [compile] [ begin rp0 @ rp! status query / READ run / EVALUATE state @ not if ." ok" then / PRINT again / LOOP ;

Lisp y Forth hacen cosas completamente diferentes, particularmente en la parte EVAL, pero también en la parte PRINT. Sin embargo, comparten el hecho de que un programa en ambos idiomas se ejecuta al incluir su código fuente en sus respectivos bucles, y en ambos casos el código es solo información (aunque en el caso de Forth es más parecido a la información también es código).

Sospecho que cualquiera que diga que solo LISP tiene un REPL es que el ciclo READ lee los DATOS, que son analizados por EVAL, y se crea un programa porque CODE también es DATOS. Esta distinción es interesante en muchos aspectos sobre la diferencia entre Lisp y otros idiomas, pero en lo que respecta a REPL, no importa en absoluto.

Consideremos esto desde el exterior:

  1. READ - devuelve entrada desde stdin
  2. EVAL - procesar dicha entrada como una expresión en el lenguaje
  3. IMPRIMIR - imprimir el resultado de EVAL
  4. LOOP - vuelve a LEER

Sin entrar en detalles de implementación, no se puede distinguir un REPL de Lisp de, por ejemplo, un REPL de Ruby. Como funciones, son las mismas.


Hay un bonito proyecto llamado respuesta multi-repl que expone varios REPL a través de Node.JS:

https://github.com/evilhackerdude/multi-repl

Si observa la lista de idiomas admitidos, es bastante claro que no solo Lisp tiene el concepto de REPL.

  • clj (clojure)
  • ghci (ghc)
  • ipython
  • irb (rubí)
  • js (spidermonkey)
  • nodo
  • pitón
  • sbcl
  • v8

De hecho, implementar una trivial en Ruby es bastante fácil:

repl = -> prompt { print prompt; puts(" => %s" % eval(gets.chomp!)) } loop { repl[">> "] }


Hay un número de personas que consideran que una REPL debe comportarse exactamente como lo hace en LISP, o no es una verdadera REPL. Más bien, lo consideran algo diferente, como un CLI (intérprete de línea de comandos). Honestamente, tiendo a pensar que si sigue el flujo básico de:

  • leer la entrada del usuario
  • evaluar esa entrada
  • imprimir la salida
  • regresa a la lectura

entonces es un REPL. Como se señaló, hay muchos idiomas que tienen la capacidad anterior.

Vea este hilo de reddit para un ejemplo de tal discusión.


La idea de un REPL proviene de la comunidad Lisp. Hay otras formas de interfaces interactivas de texto, por ejemplo, la interfaz de línea de comandos . Algunas interfaces textuales también permiten la ejecución de un subconjunto de algún tipo de lenguaje de programación.

REPL significa READ EVAL PRINT LOOP: (loop (imprimir (eval (leer)))).

En Lisp, el REPL no es un intérprete de línea de comandos (CLI). READ no lee los comandos y REPL no ejecuta los comandos. READ lee la entrada y la convierte en datos. Por lo tanto, la función READ puede leer todo tipo de expresiones-s, no solo el código Lisp.

LEER lee una s-expresión. Este es un formato de datos que también admite el código fuente de codificación. LEER devuelve datos Lisp.

EVAL toma el código fuente de Lisp en forma de datos de Lisp y lo evalúa. Los efectos secundarios pueden ocurrir y EVAL devuelve uno o más valores. Cómo se implementa EVAL, con un intérprete o un compilador, no está definido. Las implementaciones utilizan diferentes estrategias.

PRINT toma los datos de Lisp y los imprime como expresiones-s.

LOOP simplemente gira alrededor de esto. En la vida real, una REPL es más complicada e incluye manejo de errores y sub-bucles, los llamados bucles de interrupción. En el caso de un error, se obtiene solo otro REPL, con comandos de depuración agregados, en el contexto del error. El valor producido en una iteración también se puede reutilizar como entrada para la próxima evaluación.

Dado que Lisp utiliza tanto el código como los datos como los elementos funcionales, existen pequeñas diferencias con respecto a otros lenguajes de programación.

Los idiomas que son similares proporcionan también interfaces interactivas similares. Smalltalk, por ejemplo, también permite la ejecución interactiva, pero no utiliza un formato de datos para E / S como lo hace Lisp. Lo mismo para cualquier interfaz interactiva de Ruby / Python / ...

Pregunta:

Entonces, ¿qué tan significativa es la idea original de leer EXPRESIONES, evaluarlas e IMPRIMIR sus valores? ¿Es eso importante en relación con lo que hace otro idioma: leer texto, analizarlo, ejecutarlo, imprimir algo y, opcionalmente, imprimir un valor de retorno? A menudo, el valor de retorno no se utiliza realmente.

Así que hay dos respuestas posibles :

  1. a Lisp REPL es diferente a la mayoría de las otras interfaces interactivas textuales, ya que se basa en la idea de la E / S de datos de las expresiones-s y la evaluación de éstas.

  2. un REPL es un término general que describe interfaces interactivas textuales para implementaciones de lenguaje de programación o subconjuntos de ellas.

En implementaciones reales, los REPL de Lisp tienen una implementación compleja y brindan una gran cantidad de servicios, hasta presentaciones cliqueables (Symbolics, CLIM, SLIME) de objetos de entrada y salida. Las implementaciones avanzadas de REPL están disponibles, por ejemplo, en SLIME (un popular IDE basado en Emacs para Common Lisp), LispWorks y Allegro CL .

Ejemplo para una interacción REPL de Lisp :

Una lista de productos y precios:

CL-USER 1 > (setf *products* ''((shoe (100 euro)) (shirt (20 euro)) (cap (10 euro)))) ((SHOE (100 EURO)) (SHIRT (20 EURO)) (CAP (10 EURO)))

Un pedido, una lista de producto y cantidad:

CL-USER 2 > ''((3 shoe) (4 cap)) ((3 SHOE) (4 CAP))

El precio del pedido, * es una variable que contiene el último valor REPL. No contiene este valor como una cadena, sino los datos reales reales.

CL-USER 3 > (loop for (n product) in * sum (* n (first (second (find product *products* :key ''first))))) 340

Pero también puedes calcular el código Lisp:

Tomemos una función que suma los cuadrados de sus dos args:

CL-USER 4 > ''(defun foo (a b) (+ (* a a) (* b b))) (DEFUN FOO (A B) (+ (* A A) (* B B)))

El cuarto elemento es solo la expresión aritmética. * refiere al último valor:

CL-USER 5 > (fourth *) (+ (* A A) (* B B))

Ahora agregamos algo de código a su alrededor para vincular las variables a y b a algunos números. Estamos utilizando la función Lisp LIST para crear una nueva lista.

CL-USER 6 > (list ''let ''((a 12) (b 10)) *) (LET ((A 12) (B 10)) (+ (* A A) (* B B)))

Luego evaluamos la expresión anterior. De nuevo, * refiere al último valor.

CL-USER 7 > (eval *) 244

Hay varias variables que se actualizan con cada interacción REPL . Los ejemplos son * , ** y *** para los valores anteriores. También hay + para la entrada anterior. Estas variables tienen como valores no cadenas, sino objetos de datos. + contendrá el último resultado de la operación de lectura del REPL. Ejemplo:

¿Cuál es el valor de la variable *print-length* ?

CL-USER 8 > *print-length* NIL

Veamos cómo se lee e imprime una lista:

CL-USER 9 > ''(1 2 3 4 5) (1 2 3 4 5)

Ahora configuremos el símbolo anterior *print-length* a 3. ++ refiere a la segunda lectura anterior como datos. SET establece un valor de símbolos.

CL-USER 10 > (set ++ 3) 3

Entonces la lista de arriba se imprime de manera diferente. ** refiere al segundo resultado anterior: datos, no texto.

CL-USER 11 > ** (1 2 3 ...)


Supongo que se podría decir que el "REPL" de Scala es un "RCRPL": Leer, compilar, ejecutar, imprimir. Pero como el compilador se mantiene "caliente" en la memoria, es bastante rápido para las interacciones en curso; solo toma unos segundos para iniciar.