68

我遇到了Richard Stallman 的以下声明

'当你启动一个 Lisp 系统时,它会进入一个 read-eval-print 循环。大多数其他语言都无法与 read 相比,无法与 eval 相比,也无法与 print 相比。多么明显的缺陷!'

现在,我很少用 Lisp 进行编程,但我已经用 Python 编写了相当多的代码,最近还用 Erlang 编写了一些代码。我的印象是这些语言也提供 read-eval-print 循环,但 Stallman 不同意(至少关于 Python):

'在人们告诉我它与 Lisp 基本相似之后,我浏览了 Python 的文档。我的结论是,事实并非如此。当您启动 Lisp 时,它会执行“读取”、“评估”和“打印”,所有这些在 Python 中都没有。

Lisp 和 Python 的 read-eval-print 循环之间真的存在根本的技术差异吗?你能举例说明 Lisp REPL 使之变得容易而在 Python 中难以做到的事情吗?

4

4 回答 4

62

为了支持 Stallman 的立场,Python 在以下方面与典型的 Lisp 系统不同:

  • Lisp 中的read函数读取一个 S 表达式,它表示一个任意数据结构,可以将其视为数据,也可以将其计算为代码。Python 中最接近的东西读取单个字符串,如果您希望它表示任何含义,您必须自己解析它。

  • Lisp 中的eval函数可以执行任何 Lisp 代码。evalPython 中的函数计算表达式,并且需要exec语句来运行语句。但是这两种方法都适用于以文本形式表示的 Python 源代码,并且您必须跳过一堆障碍才能“评估”Python AST。

  • Lisp 中的函数以与接受print的完全相同的形式写出一个 S 表达式。在 Python 中打印出由您尝试打印的数据定义的内容,这当然并不总是可逆的。readprint

Stallman 的陈述有点虚伪,因为很明显 Python确实有精确命名的函数evaland print,但它们做的事情与他所期望的不同(和劣等)。

在我看来,Python确实在某些方面与 Lisp 相似,我可以理解为什么人们可能会建议 Stallman 研究 Python。然而,正如Paul Graham 在 What Made Lisp 的不同之处所指出的,任何包含 Lisp 所有功能的编程语言也必须Lisp。

于 2012-09-03T19:45:57.547 回答
33

Stallman 的观点是,与 Lisps 相比,不实现显式的“阅读器”会使 Python 的 REPL 显得残缺不全,因为它从 REPL 过程中删除了一个关键步骤。Reader 是将文本输入流转换到内存中的组件——想想像 XML 解析器这样的东西,内置在语言中,用于源代码数据。这不仅对编写宏很有用(理论上在 Python 中可以使用该ast模块),而且对调试和自省也很有用。

假设您对incf特殊表单的实现方式感兴趣。你可以像这样测试它:

[4]> (macroexpand '(incf a))
(SETQ A (+ A 1)) ;

incf可以做的不仅仅是增加符号值。当被要求增加哈希表条目时,它到底做了什么?让我们来看看:

[2]> (macroexpand '(incf (gethash htable key)))
(LET* ((#:G3069 HTABLE) (#:G3070 KEY) (#:G3071 (+ (GETHASH #:G3069 #:G3070) 1)))
 (SYSTEM::PUTHASH #:G3069 #:G3070 #:G3071)) ;

在这里我们了解到incf调用了一个系统特定的puthash函数,这是这个 Common Lisp 系统的一个实现细节。请注意“打印机”如何利用“读者”已知的功能,例如在#:语法中引入匿名符号,并在扩展表达式的范围内引用相同的符号。在 Python 中模拟这种检查会更加冗长且不易访问。

除了在 REPL 中的明显用途之外,经验丰富的 Lispers 在代码中使用printread在代码中作为一种简单且易于使用的序列化工具,可与 XML 或 json 相媲美。虽然 Python 有这个str功能,相当于 Lisp 的print,但它缺少 的等价物read,最接近的等价物是evaleval当然混合了两个不同的概念,解析和评估,这会导致这样的问题和这样解决方案,并且是 Python 论坛上反复出现的话题。这在 Lisp 中不会成为问题,因为读者和评估者是完全分开的。

最后,阅读器工具的高级特性使程序员能够以甚至宏无法提供的方式扩展语言。Mark Kantrowitz的infix就是一个完美的例子,它实现了一个全功能的中缀语法作为阅读器宏。

于 2012-09-03T20:01:04.187 回答
22

在基于 Lisp 的系统中,通常在程序从 REPL(读取 eval 打印循环)运行时开发程序。所以它集成了一堆工具:完成,编辑器,命令行解释器,调试器,......默认是有的。键入带有错误的表达式 - 您处于另一个 REPL 级别并启用了一些调试命令。你实际上必须做一些事情来摆脱这种行为。

REPL 概念可以有两种不同的含义:

  • 像 Lisp(或其他一些类似语言)中的 Read Eval Print Loop。它读取程序和数据,评估并打印结果数据。Python 不能以这种方式工作。Lisp 的 REPL 允许您直接以元编程方式工作,编写生成(代码)的代码、检查扩展、转换实际代码等。Lisp 具有 read/eval/print 作为顶部循环。Python 有类似 readstring/evaluate/printstring 作为顶层循环。

  • 命令行界面。交互式外壳。参见例如IPython。将其与 Common Lisp 的SLIME进行比较。

默认模式下 Python 的默认 shell 对于交互使用来说并不是那么强大:

Python 2.7.2 (default, Jun 20 2012, 16:23:33) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a+2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 

您收到一条错误消息,仅此而已。

将其与 CLISP REPL 进行比较:

rjmba:~ joswig$ clisp
  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8

Welcome to GNU CLISP 2.49 (2010-07-07) <http://clisp.cons.org/>

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2010

Type :h and hit Enter for context help.

[1]> (+ a 2)

*** - SYSTEM::READ-EVAL-PRINT: variable A has no value
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of A.
STORE-VALUE    :R2      Input a new value for A.
ABORT          :R3      Abort main loop
Break 1 [2]> 

CLISP 使用 Lisp 的条件系统闯入调试器 REPL。它提出了一些重新启动。在错误上下文中,新的 REPL 提供了扩展命令。

让我们使用:R1重启:

Break 1 [2]> :r1
Use instead of A> 2
4
[3]> 

因此,您可以获得程序的交互式修复和执行运行......

于 2012-09-03T20:03:07.377 回答
7

Python 的交互模式与 Python 的“从文件读取代码”模式在几个小而关键的方面不同,这可能是该语言的文本表示所固有的。Python 也不是同义词,这让我将其称为“交互模式”而不是“读取-评估-打印循环”。除此之外,我会说这更多的是等级差异而不是种类差异。

现在,在 Python 代码文件中,有些东西实际上接近于“种类差异”,您可以轻松地插入空行:

def foo(n):
  m = n + 1

  return m

如果您尝试将相同的代码粘贴到解释器中,它会认为该函数已“关闭”并抱怨您在错误的缩进处有一个赤裸裸的 return 语句。这在(通用)Lisp 中不会发生。

此外,Common Lisp (CL) 中有一些相当方便的便利变量在 Python 中不可用(至少据我所知)。CL 和 Python 都有“最后一个表达式的值”(*在 CL 中,_在 Python 中),但 CL 也有**(在最后一个表达式之前***的值) 和 (前一个表达式的值) and +, ++and +++(表达式本身)。CL 也不区分表达式和语句(本质上,一切都是表达式),所有这些都有助于构建更丰富的 REPL 体验。

就像我一开始说的,与其说是种类的不同,不如说是等级的不同。但是,如果他们之间的差距只是稍微大一点,那也可能是种类上的差异。

于 2012-09-07T13:53:20.120 回答