Lisp 是唯一具有 REPL 的语言吗?

发布于 2024-11-01 23:37:55 字数 128 浏览 18 评论 0原文

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

There are languages other than Lisp (ruby, scala) that say they use REPL (Read, Eval, Print, Loop), but it is unclear whether what is meant by REPL is the same as in Lisp. How is Lisp REPL different from non-Lisp REPL?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

你是我的挚爱i 2024-11-08 23:37:55

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

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

上述四个函数都是原始 Lisp 函数。

在 Lisp 中,REPL 不是命令行解释器 (CLI)。 READ 不读取命令,并且 REPL 不执行命令。 READ 读取 s-表达式格式的输入数据并将其转换为内部数据。因此,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 并对其求值的思想。< /p>

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

Lisp 中的 REPL

在实际实现中,Lisp REPL 具有复杂的实现并提供大量服务,直至输入和输出对象的可点击表示(符号、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 ...)

The idea of a REPL comes from the Lisp community. There are other forms of textual interactive interfaces, for example the command line interface. Some textual interfaces also allow a subset of some kind of programming language to be executed.

REPL stands for READ EVAL PRINT LOOP: (loop (print (eval (read)))).

Each of the four above functions are primitive Lisp functions.

In Lisp the REPL is not a command line interpreter (CLI). READ does not read commands and the REPL does not execute commands. READ reads input data in s-expression format and converts it to internal data. Thus the READ function can read all kinds of s-expressions - not just Lisp code.

READ reads a s-expression. This is a data-format that also supports encoding source code. READ returns Lisp data.

EVAL takes Lisp source code in the form of Lisp data and evaluates it. Side effects can happen and EVAL returns one or more values. How EVAL is implemented, with an interpreter or a compiler, is not defined. Implementations use different strategies.

PRINT takes Lisp data and prints it to the output stream as s-expressions.

LOOP just loops around this. In real-life a REPL is more complicated and includes error handling and sub-loops, so-called break loops. In case of an error one gets just another REPL, with added debug commands, in the context of the error. The value produced in one iteration also can be reused as input for the next evaluation.

Since Lisp is both using code-as-data and functional elements, there are slight differences to other programming languages.

Languages that are similar, those will provide also similar interactive interfaces. Smalltalk for example also allows interactive execution, but it does not use a data-format for I/O like Lisp does. Same for any Ruby/Python/... interactive interface.

Question:

So how significant is the original idea of READing EXPRESSIONS, EVALuating them and PRINTing their values? Is that important in relation to what other languages do: reading text, parsing it, executing it, optionally print something and optionally printing a return value? Often the return value is not really used.

So there are two possible answers:

  1. a Lisp REPL is different to most other textual interactive interfaces, because it is based on the idea of data I/O of s-expressions and evaluating these.

  2. a REPL is a general term describing textual interactive interfaces to programming language implementations or subsets of those.

REPLs in Lisp

In real implementations Lisp REPLs have a complex implementation and provide a lot of services, up to clickable presentations (Symbolics, CLIM, SLIME) of input and output objects. Advanced REPL implementations are for example available in SLIME (a popular Emacs-based IDE for Common Lisp), McCLIM, LispWorks and Allegro CL.

Example for a Lisp REPL interaction:

a list of products and prices:

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

an order, a list of product and amount:

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

The price for the order, * is a variable containing the last REPL value. It does not contain this value as a string, but the real actual data.

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

But you can also compute Lisp code:

Let's take a function which adds the squares of its two args:

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

The fourth element is just the arithmetic expression. * refers to the last value:

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

Now we add some code around it to bind the variables a and b to some numbers. We are using the Lisp function LIST to create a new list.

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

Then we evaluate the above expression. Again, * refers to the last value.

CL-USER 7 > (eval *)
244

There are several variables which are updated with each REPL interaction. Examples are *, ** and *** for the previous values. There is also + for the previous input. These variables have as values not strings, but data objects. + will contain the last result of the read operation of the REPL. Example:

What is the value of the variable *print-length*?

CL-USER 8 > *print-length*
NIL

Let's see how a list gets read and printed:

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

Now let's set the above symbol *print-length* to 3. ++ refers to the second previous input read, as data. SET sets a symbols value.

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

Then above list prints differently. ** refers to the second previous result - data, not text.

CL-USER 11 > **
(1 2 3 ...)
坦然微笑 2024-11-08 23:37:55

REPL 的概念就是读取、评估、打印和读取。循环很多语言都有 REPL,这并不奇怪:

C/C++

C#/LINQ

Erlang

Haskell(在 Windows 上)

< a href="http://www.beanshell.org/" rel="nofollow noreferrer">Java

Javascript

Julia

Perl

Python

Ruby

Scala

Smalltalk -- 我在 REPL 上学到的!

Seeing as the concept of a REPL is to just Read, Eval, Print & Loop it's not too suprising that there are REPLs for many languages:

C/C++

C#/LINQ

Erlang

Haskell (on windows)

Java

Javascript

Julia

Perl

Python

Ruby

Scala

Smalltalk -- I learned it on a REPL!

倾城花音 2024-11-08 23:37:55

我认为比较两种方法很有趣。 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 循环读取 DATA,由 EVAL 解析,并创建一个程序,因为 CODE 也是 DATA。 Lisp 和其他语言之间的区别在很多方面都很有趣,但就 REPL 而言,这根本不重要。

让我们从外部考虑这一点:

  1. READ -- 从 stdin 返回输入
  2. EVAL -- 将所述输入处理为语言中的表达式
  3. PRINT -- 打印 EVAL 的结果
  4. LOOP -- 返回 READ

不深入实现细节,人们无法区分例如,来自 Ruby REPL 的 Lisp REPL。作为功​​能,它们是相同的。

I think it is interesting to compare two approaches. A bare bones REPL loop in a Lisp system would look like this:

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

Here are two actual Forth implementations of a REPL loop. I'm leaving nothing out here -- this is the full code to these loops.

: 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 and Forth do completely different things, particularly in the EVAL part, but also in the PRINT part. Yet, they share the fact that a program in both languages is run by feeding its source code to their respective loops, and in both cases code is just data (though in Forth case it is more like data is also code).

I suspect what anyone saying only LISP has a REPL is that the READ loop reads DATA, which is parsed by EVAL, and a program is created because CODE is also DATA. This distinction is interesting in many respects about the difference between Lisp and other languages, but as far as REPL goes, it doesn't matter at all.

Let's consider this from the outside:

  1. READ -- returns input from stdin
  2. EVAL -- process said input as an expression in the language
  3. PRINT -- print EVAL's result
  4. LOOP -- go back to READ

Without going into implementation details, one can't distinguish a Lisp REPL from, for example, a Ruby REPL. As functions, they are the same.

Smile简单爱 2024-11-08 23:37:55

我猜你可能会说 Scala 的“REPL”是“RCRPL”:读取、编译、运行、打印。但由于编译器在内存中保持“热”状态,因此对于正在进行的交互来说速度相当快——启动只需要几秒钟。

I guess you could say that Scala's "REPL" is an "RCRPL": Read, Compile, Run, Print. But since the compiler is kept "hot" in memory, it's pretty fast for ongoing interactions--it just takes a few seconds to start up.

痴情换悲伤 2024-11-08 23:37:55

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

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

,那么它就是 REPL。如前所述,有很多语言都具有上述功能。

有关此类讨论的示例,请参阅此 reddit 帖子

There are a number of people that consider a REPL to needs to behave exactly like it does in LISP, or it's not a true REPL. Rather, they consider it something different, like a CLI (command line interpreter). Honestly, I tend to think that if it follows the basic flow of:

  • read input from the user
  • evaluate that input
  • print the output
  • loop back to the read

then it's a REPL. As noted, there are a lot of languages that have the above capability.

See this reddit thread for an example of such a discussion.

梦回旧景 2024-11-08 23:37:55

Lisp REPL 与非 Lisp REPL 有何不同?

让我们将 Common Lisp 的 REPL 与 Python 的 IPython 进行比较。

主要两点是:

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

在 Python 中,通常,您启动 IPython 或者进入 ipdb。您定义一些数据,直到尝试新功能。您编辑源代码,并且想再试一次,因此退出 IPython 并重新开始整个过程​​。在 Lisp(主要是 Common Lisp)中,完全不是,它更具交互性。

How is Lisp REPL different from non-Lisp REPL?

Let's compare Common Lisp's REPL with Python's IPython.

The main two points are:

  • Lisp is an image-based language. There is no need to restart the process/the REPL/the whole app after a change. We compile our code function by function (with compiler warnings etc).
  • we don't loose state. Even more, when we update class definitions, our objects in the REPL are also updated, following rules we have control upon. That way we can hot-reload code in a running system.

In Python, typically, you start IPython or you are dropped into ipdb. You define some data until you try out your new function. You edit your source, and you want to try again, so you quit IPython and you start the whole process again. In Lisp (Common Lisp mainly), not at all, it's all more interactive.

别低头,皇冠会掉 2024-11-08 23:37:55

有一个名为 multi-repl 的不错的项目,它通过 Node.JS 公开各种 REPL:

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

如果你查看支持的语言列表,很明显不仅 Lisp 具有 REPL 的概念。

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

事实上,在 Ruby 中实现一个简单的操作相当容易:

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

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[">> "] }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文