22

除了 Lisp(ruby、scala)之外,还有其他语言说他们使用 REPL(Read、Eval、Print、Loop),但不清楚 REPL 的含义是否与 Lisp 中的相同。Lisp REPL 与非 Lisp REPL 有何不同?

4

7 回答 7

39

REPL的想法来自 Lisp 社区。还有其他形式的文本交互界面,例如命令行界面。一些文本接口还允许执行某种编程语言的子集。

REPL 代表 READ EVAL PRINT LOOP: (loop (print (eval (read))))。

上述四个函数中的每一个都是原始的 Lisp 函数。

在 Lisp 中,REPL 不是命令行解释器 (CLI)。READ不读取命令,REPL 不执行命令。READ以 s-expression 格式读取输入数据并将其转换为内部数据。因此该READ函数可以读取所有类型的 s 表达式——不仅仅是 Lisp 代码。

READ 读取一个 s 表达式。这是一种也支持编码源代码的数据格式。READ 返回 Lisp 数据。

EVAL 以 Lisp 数据的形式获取 Lisp 源代码并对其进行评估。可能会发生副作用,并且 EVAL 返回一个或多个值。没有定义如何使用解释器或编译器实现 EVAL。实现使用不同的策略。

PRINT 获取 Lisp 数据并将其作为 s 表达式打印到输出流。

LOOP 只是围绕这个循环。在现实生活中,REPL 更为复杂,包括错误处理和子循环,即所谓的中断循环。如果出现错误,则在错误的上下文中获得另一个 REPL,并添加了调试命令。一次迭代中产生的值也可以用作下一次评估的输入。

由于 Lisp 同时使用代码即数据和函数元素,因此与其他编程语言略有不同。

相似的语言也将提供相似的交互界面。例如,Smalltalk 也允许交互式执行,但它不像 Lisp 那样使用 I/O 数据格式。任何 Ruby/Python/... 交互界面都一样。

问题:

那么阅读表达式、评估表达式并打印其价值的最初想法有多重要?这与其他语言所做的事情相比是否重要:阅读文本、解析文本、执行文本、可选地打印某些内容以及可选地打印返回值?通常没有真正使用返回值。

所以有两个可能的答案

  1. Lisp REPL 与大多数其他文本交互界面不同,因为它基于 s 表达式的数据 I/O 的想法并评估这些。

  2. REPL 是一个通用术语,用于描述与编程语言实现或其子集的文本交互接口。

Lisp 中的 REPL

在实际实现中,Lisp REPL 具有复杂的实现并提供大量服务,直至输入和输出对象的可点击表示(Symbolics、CLIM、SLIME)。例如,高级 REPL 实现在SLIME(一种流行的基于 Emacs 的 Common Lisp IDE)、McCLIMLispWorksAllegro CL中可用。

Lisp REPL 交互的示例

产品清单和价格:

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

订单,产品清单和数量:

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

订单价格*是一个包含最后一个 REPL 值的变量。它不包含此值作为字符串,而是真实的实际数据。

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

但你也可以计算 Lisp 代码:

让我们看一个函数,它将两个参数的平方相加:

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

第四个元素只是算术表达式。*指最后一个值:

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

现在我们在它周围添加一些代码来绑定变量ab一些数字。我们正在使用 Lisp 函数LIST来创建一个新列表。

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

然后我们评估上面的表达式。再次,*指的是最后一个值。

CL-USER 7 > (eval *)
244

每次REPL交互都会更新几个变量。示例是*,*****用于之前的值。也有+用于先前的输入。这些变量的值不是字符串,而是数据对象。+将包含 REPL 读取操作的最后结果。例子:

变量的值是*print-length*多少?

CL-USER 8 > *print-length*
NIL

让我们看看如何读取和打印列表:

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

现在让我们将上面的符号设置*print-length*为 3。++指的是第二个之前的输入读取,作为数据。SET设置符号值。

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

然后上面的列表打印不同。**指的是第二个先前的结果 - 数据,而不是文本。

CL-USER 11 > **
(1 2 3 ...)
于 2011-04-15T07:14:06.373 回答
12

鉴于 REPL 的概念只是读取、评估、打印和循环,因此有许多语言的 REPL 也就不足为奇了:

C/C++

C#/LINQ

二郎

Haskell(在 Windows 上)

爪哇

Javascript

朱莉娅

Perl

Python

红宝石

斯卡拉

Smalltalk——我是在 REPL 上学的!

于 2011-04-15T00:47:50.557 回答
5

我认为比较两种方法很有趣。Lisp 系统中的基本 REPL 循环如下所示:

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

下面是 REPL 循环的两个实际 Forth 实现。我在这里什么都没有留下——这是这些循环的完整代码。

: 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 和 Forth 做完全不同的事情,特别是在 EVAL 部分,但在 PRINT 部分也是如此。然而,他们共享一个事实,即两种语言的程序都是通过将源代码提供给各自的循环来运行的,并且在这两种情况下,代码只是数据(尽管在 Forth 情况下,它更像是数据也是代码)。

我怀疑有人说只有 LISP 有 REPL 是 READ 循环读取由 EVAL 解析的 DATA,并且创建了一个程序,因为 CODE 也是 DATA。Lisp 和其他语言之间的区别在很多方面都很有趣,但就 REPL 而言,这根本不重要。

让我们从外部考虑:

  1. READ -- 从标准输入返回输入
  2. EVAL - 将所述输入处理为语言中的表达式
  3. PRINT -- 打印 EVAL 的结果
  4. 循环——返回阅读

如果不深入了解实现细节,就无法将 Lisp REPL 与例如 Ruby REPL 区分开来。作为功​​能,它们是相同的。

于 2011-04-15T15:13:30.963 回答
4

我想你可以说 Scala 的“REPL”是一个“RCRPL”:读取、编译、运行、打印。但是由于编译器在内存中保持“热”状态,因此对于正在进行的交互来说非常快——只需几秒钟即可启动。

于 2011-04-15T00:33:53.067 回答
3

有很多人认为 REPL 的行为必须与 LISP 中的完全一样,否则它就不是真正的 REPL。相反,他们认为它有些不同,例如 CLI(命令行解释器)。老实说,我倾向于认为,如果它遵循以下基本流程:

  • 读取用户的输入
  • 评估该输入
  • 打印输出
  • 循环回读

然后它是一个REPL。如前所述,有很多语言具有上述功能。

有关此类讨论的示例,请参见此 reddit 线程。

于 2011-04-15T04:37:22.393 回答
2

There's a nice project called multi-repl which exposes various REPLs via Node.JS:

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

If you look at the list of supported languages, it's quite clear that not only Lisp has the concept of a REPL.

  • clj (clojure)
  • ghci (ghc)
  • ipython
  • irb (ruby)
  • js (spidermonkey)
  • node
  • python
  • sbcl
  • v8

In fact implementing a trivial one in Ruby is fairly easy:

repl = -> prompt { print prompt; puts(" => %s" % eval(gets.chomp!)) }
loop { repl[">> "] }
于 2011-04-15T07:51:10.747 回答
2

Lisp REPL 与非 Lisp REPL 有何不同?

让我们比较一下Common Lisp的 REPL 和 Python 的 IPython。

主要的两点是:

  • Lisp 是一种基于图像的语言。更改后无需重新启动进程/REPL/整个应用程序。我们逐个函数编译我们的代码(带有编译器警告等)。
  • 我们不松散状态。更重要的是,当我们更新类定义时,REPL 中的对象也会更新,遵循我们可以控制的规则。这样我们就可以在正在运行的系统中热重载代码。

在 Python 中,通常是启动 IPython 或进入 ipdb。在尝试新功能之前,您需要定义一些数据。你编辑了你的源代码,你想再试一次,所以你退出了 IPython 并重新开始了整个过程。在 Lisp(主要是 Common Lisp)中,一点也不,它更具交互性。

于 2018-11-03T00:24:32.040 回答