我遇到了以下statement by Richard Stallman:
'当你启动一个Lisp系统时,它会进入一个read-eval-print循环。大多数其他语言没有什么可比阅读,没有什么可比得上eval,没有什么比得上印刷。有什么差距不足! “
现在,我在Lisp中做了很少的编程,但我在Python中编写了大量代码,最近在Erlang中编写了一些代码。我的印象是这些语言也提供了read-eval-print循环,但Stallman不同意(至少关于Python):
'在人们告诉我它与Lisp基本类似之后,我浏览了Python的文档。我的结论是,情况并非如此。当你启动Lisp时,它会'read','eval'和'print',所有这些都在Python中缺失。'
Lisp和Python的read-eval-print循环之间真的存在根本的技术差异吗?你能举例说明Lisp REPL简单易用而难以用Python做的事情吗?
答案 0 :(得分:57)
为了支持Stallman的立场,Python在以下方面与典型的Lisp系统不同:
Lisp中的read
函数读取一个S表达式,它表示可以被视为数据或被评估为代码的任意数据结构。 Python中最接近的东西是读取一个字符串,如果你想要它意味着任何东西,你必须自己解析。
Lisp中的eval
函数可以执行任何Lisp代码。 Python中的eval
函数仅评估 表达式,并且需要exec
语句来运行语句。但这两种方法都与Python源代码一起表示为文本,你必须跳过一堆箍来“评估”Python AST。
Lisp中的print
函数以与read
接受的完全相同的形式写出一个S表达式。 Python中的print
打印出您尝试打印的数据定义的内容,这当然不总是可逆的。
Stallman的陈述有点不诚实,因为很明显Python 确实具有精确命名为eval
和print
的函数,但是他们做了与他期望的不同(并且低级)的事情
在我看来,Python 确实有一些类似于Lisp的方面,我可以理解为什么人们可能会建议Stallman看看Python。但是,作为Paul Graham argues in What Made Lisp Different,任何包含Lisp所有功能的编程语言都必须 Lisp。
答案 1 :(得分:29)
Stallman的观点是,没有实现一个明确的“读者”使得Python的REPL与Lisps相比显得瘫痪,因为它从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在代码中使用print
和read
作为一个简单易用的序列化工具,与XML或json相当。虽然Python具有str
函数,相当于Lisp的print
,但它缺少等价的read
,最接近的等价于eval
。 eval
当然会混淆两个不同的概念,解析和评估,这会导致problems like this和solutions like this,并且是Python论坛上反复出现的主题。这在Lisp中不是一个问题,因为读者和评估者是完全分开的。
最后,阅读器工具的高级功能使程序员能够以甚至宏无法提供的方式扩展语言。马克·坎特罗维茨(Mark Kantrowitz)将the infix
package作为一个阅读器宏实现了全功能的中缀语法。
答案 2 :(得分:19)
在基于Lisp的系统中,通常在从REPL(读取评估打印循环)运行时开发程序。所以它集成了一堆工具:完成,编辑器,命令行解释器,调试器......默认情况下就是这样。键入带有错误的表达式 - 您处于另一个REPL级别,并启用了一些调试命令。你实际上必须做些什么来摆脱这种行为。
您可以对REPL概念有两种不同的含义:
阅读评估打印循环,如Lisp(或其他一些类似语言)。它读取程序和数据,评估并打印结果数据。 Python无法以这种方式工作。 Lisp的REPL允许您直接以元编程方式工作,编写生成(代码)的代码,检查扩展,转换实际代码等.Lisp将read / eval / print作为顶部循环。 Python有一些像readstring / evaluate / printstring作为顶循环。
默认模式下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]>
因此,您可以获得程序和执行运行的交互式修复......
答案 3 :(得分:5)
Python的交互模式与Python的“从文件读取代码”模式不同,有几个小的,关键的方式,可能是语言的文本表示所固有的。 Python也不是homoiconic,这使我称之为“交互模式”而不是“read-eval-print loop”。除此之外,我会说这是一个等级差异而不是实物差异。
现在,有些东西接近于“实物差异”,在Python代码文件中,您可以轻松插入空白行:
def foo(n):
m = n + 1
return m
如果您尝试将相同的代码粘贴到解释器中,它会认为该函数是“已关闭”并且抱怨您在错误的缩进处有一个裸返回语句。这不会发生在(Common)Lisp中。
此外,Common Lisp(CL)中有一些相当方便的便利变量在Python中不可用(至少据我所知)。 CL和Python都有“最后一个表达式的值”(CL中为*
,Python中为_
),但CL也有**
(前一个表达式的值)和{{1} (前一个的值)和***
,+
和++
(表达式本身)。 CL也不区分表达式和语句(实质上,一切都是表达式),所有这些都有助于构建更丰富的REPL体验。
正如我在开始时所说的那样,它在等级上的差异比在实物上的差异更大。但是,如果它们之间的间隙只是一个更大的差距,它可能也会有所不同。