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/... 交互界面都一样。
问题:
那么,阅读表达式、评估表达式并打印其值的最初想法有多重要?这与其他语言所做的事情相比是否重要:阅读文本、解析文本、执行文本、可选地打印某些内容以及可选地打印返回值?通常没有真正使用返回值。
所以有两种可能的答案:
Lisp REPL 与大多数其他文本交互界面不同,因为它基于 s 表达式的数据 I/O 并评估这些概念。
REPL 是一个通用术语,用于描述与编程语言实现或其子集的文本交互接口。
Lisp 中的 REPL
在实际实现中,Lisp REPL 具有复杂的实现并提供大量服务,直至输入和输出对象的可点击表示(符号、CLIM、SLIME)。高级 REPL 实现例如在 SLIME(一个流行的基于 Emacs 的 Common Lisp IDE)、McCLIM、LispWorks 和 Allegro 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))
现在我们在它周围添加一些代码来将变量a 和b 绑定到一些数字。我们正在使用 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 ...)