Lisp的read-eval-print循环如何与Python不同?

我遇到了Richard Stallman的以下陈述 :

'当你启动一个Lisp系统时,它会进入一个read-eval-print循环。 大多数其他语言没有什么比较可读的,没有任何可比的eval,没有什么可比的印刷品。 什么缺陷的缺陷! “

现在,我在Lisp中做了很less的编程,但是我在Python中编写了大量的代码,最近在Erlang中编写了一些代码。 我的印象是,这些语言也提供了read-eval-print循环,但Stallman不同意(至less关于Python):

人们告诉我这跟Python有着根本的相似之处,我剔除了Python的文档。 我的结论是,事实并非如此。 当你启动Lisp时,它会“读取”,“评估”和“打印”,所有这些在Python中都是缺失的。

Lisp和Python的read-eval-print循环之间有真正的技术差异吗? 你能举出一些Lisp REPL很容易实现的例子吗?

为了支持Stallman的观点,Python在以下几个方面与典型的Lisp系统不同:

  • Lisp中的read函数读取一个Sexpression式,该expression式表示一个任意的数据结构,可以将其视为数据,也可以视为代码。 Python中最接近的东西是读取一个单独的string,如果您希望它意味着什么,那么您将不得不parsing自己。

  • Lisp中的eval函数可以执行任何Lisp代码。 Python中的eval函数评估expression式,并需要exec语句来运行语句。 但是,这两个工作都是以文本forms表示的Python源代码,你必须通过一堆箍来“评估”一个Python AST。

  • Lisp中的print函数以与read接受的forms完全相同的forms写出Sexpression式。 用Python打印会打印出您要打印的数据定义的东西,这当然不总是可逆的。

Stallman的说法有些不诚实,因为Python显然确实有一些名为evalprint eval ,但是他们做了一些与他期望的不同的东西(和次要的东西)。

在我看来,Python 的确有一些与Lisp类似的方面,我可以理解为什么人们可能会build议Stallman看看Python。 然而,正如Paul Graham在“What Made Lisp Different”中所说的 ,任何包含Lisp所有function的编程语言也必须 Lisp。

Stallman的观点是,没有实现一个明确的“读者”,与Lisp相比,Python的REPL显得残缺,因为它从REPL过程中消除了一个关键的步骤。 Reader是将文本inputstream转换为内存的组件 – 想象一下内置于该语言中的XMLparsing器,用于源代码数据。 这不仅适用于编写macros(理论上可以在Python中使用ast模块),也适用于debugging和自省。

假设你对如何实施incf特殊表格感兴趣。 你可以像这样testing它:

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

但是, incf可以做比增加符号值更多的function。 当被要求增加一个散列表项时,它究竟做了什么? 让我们来看看:

 [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系统的实现细节。 请注意,“打印机”是如何利用“读者”已知的function的,如使用#:语法引入匿名符号,并在扩展expression式的范围内引用相同的符号。 在Python中模拟这种检查将更加冗长和不易访问。

除了REPL的明显用途之外,有经验的Lispers使用printread代码作为一个简单和容易获得的序列化工具,可以与XML或json相媲美。 虽然Python具有str函数,相当于Lisp的print ,但它缺less等价的read ,最接近的是eval 。 当然, eval当然会混淆两个不同的概念,parsing和评估,这会导致像这样的问题和这样的 解决scheme,并且是Python论坛上反复出现的主题。 这在Lisp中并不是一个问题,因为读者和评估者是完全分离的。

最后,阅读器设备的高级特性使程序员能够以macros甚至macros观上不能提供的方式来扩展语言。 马克·坎特罗维茨(Mark Kantrowitz) 的中infix包 ,是一个完美的例子,它将一个全function的中缀语法作为读者macros来实现。

在一个基于Lisp的系统中,通常从REPL运行程序(读取eval打印循环)开发程序。 所以它集成了一大堆工具:完成,编辑器,命令行解释器,debugging器,…默认是这样的。 input一个错误的expression式 – 您在另一个REPL级别中启用了一些debugging命令。 你实际上必须做些什么来摆脱这种行为。

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

  • 阅读Eval打印循环像Lisp(或其他一些类似的语言)。 它读取程序和数据,评估和打印结果数据。 Python不能这样工作。 Lisp的REPL允许你直接以元编程的方式工作,编写生成(代码)的代码,检查扩展,转换实际的代码等。Lisp已经将读/ eval / print作为顶层循环。 Python有像readtring / evaluate / printstring这样的顶层循环。

  • 命令行界面。 一个交互式shell。 例如查看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 iiiiiii ooooo o ooooooo ooooo ooooo IIIIIII 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的条件系统闯入一个debugging器REPL。 它提出了一些重新启动。 在错误上下文中,新的REPL提供扩展的命令。

让我们使用:R1重启:

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

因此,你得到程序和执行运行的交互式修复…

Python的交互模式与Python的“从文件中读取代码”模式有几个小而关键的方面,可能是语言的文本表示中固有的。 Python也不是homoiconic,这使得我称之为“交互模式”,而不是“read-eval-print loop”。 除此之外,我想说这是一个等级差异,而​​不是实物差异。

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

 def foo(n): m = n + 1 return m 

如果您尝试将相同的代码粘贴到解释器中,则会认为函数被“closures”,并且抱怨您在错误的缩进处有一个裸返回语句。 (Common)Lisp中不会发生这种情况。

此外,在Common Lisp(CL)中有一些非常方便的便利variables,在Python中是不可用的(至less据我所知)。 CL和Python都有“最后expression式的值”(在CL中是* ,在Python中),但是CL也有** (前一个expression式的值)和*** (前一个的值)和+ +++++ (expression式本身)。 CL也不区分expression式和语句(实质上,一切都是expression式),所有这一切都有助于构build更丰富的REPL体验。

正如我刚开始所说的那样,在品位上差别更大。 但是,如果差距只是他们之间的差距,那么这可能也是一种差异。