请解释Paul Graham在Lisp上的一些观点
我需要一些帮助来理解Paul Graham的“ What Made Lisp Different” 。
-
variables的新概念。 在Lisp中,所有variables都是有效的指针。 值是什么types,而不是variables,分配或绑定variables意味着复制指针,而不是指向。
-
符号types。 符号不同于string,因为您可以通过比较指针来testing相等性。
-
使用符号树的代码的符号。
-
整个语言总是可用的。 在读取时间,编译时间和运行时间之间没有真正的区别。 您可以在编译时读取,读取或运行代码的同时编译或运行代码,以及在运行时读取或编译代码。
这些意味着什么? 它们在C或Java等语言中有什么不同? 除了Lisp族语言以外的其他语言现在是否有这些构造?
马特的解释是完全正确的 – 而且他比较了C和Java,我不会这样做 – 但由于某种原因,我真的很喜欢讨论这个话题,所以 – 这里是我的镜头在一个答案。
关于(3)和(4):
点(3)和(4)在你的名单上似乎是最有趣的,现在仍然相关。
为了理解它们,清楚地了解Lisp代码发生的情况是非常有用的 – 以程序员input的字符stream的forms – 在执行的过程中。 我们来举一个具体的例子:
;; a library import for completeness, ;; we won't concern ourselves with it (require '[clojure.contrib.string :as str]) ;; this is the interesting bit: (println (str/replace-re #"\d+" "FOO" "a123b4c56"))
这段Clojure代码打印出一个aFOObFOOcFOO
。 请注意,Clojure可能无法完全满足您列表中的第四点,因为读取时间并不真正向用户代码开放; 不过,我会讨论这会是什么意思。
所以,假设我们已经把这个代码放在了某个文件中,我们要求Clojure来执行它。 另外,让我们假设(为了简单起见),我们已经通过了图书馆的导入。 有趣的位开始于(println
,结束于)
远处。 这是人们所期望的lexed / parsed,但已经有一个重要的一点出现: 结果不是一些特定的编译器特定的AST表示法 – 它只是一个常规的Clojure / Lisp数据结构 ,即包含一堆符号的嵌套列表,string和 – 在这种情况下 – 一个编译正则expression式模式对象对应于#"\d+"
文字(更多在这下面)。 一些Lisp在这个过程中join了自己的小小的变化,但是Paul Graham主要是指Common Lisp。 在与你的问题相关的问题上,Clojure与CL类似。
整个语言在编译时:
在这一点之后,所有的编译器都会处理(对于Lisp解释器来说也是如此; Clojure代码总是要编译的)是Lisp程序员用来操作的Lisp数据结构。 在这一点上,一个奇妙的可能性变得明显:为什么不让Lisp程序员编写Lisp函数来处理表示Lisp程序的Lisp数据,并输出表示转换程序的转换后的数据,以代替原始数据呢? 换句话说,为什么不允许Lisp程序员将它们的函数注册为各种编译器插件,在Lisp中称为macros? 实际上任何像样的Lisp系统都有这个能力。
因此,macros是在编译时在程序表示上运行的正常Lisp函数,在最终的编译阶段之前,当实际的目标代码被发射时。 由于macros允许运行的代码种类没有限制(特别是运行的代码通常是自由使用macros设施编写的),可以说“整个语言在编译时是可用的”。
整个语言在阅读时间:
让我们回到#"\d+"
正则expression式。 如上所述,在编译器听到第一次提到正在准备编译的新代码之前,它会在读取时转换为实际编译的模式对象。 这是怎么发生的?
那么,Clojure目前的实施方式,与Paul Graham的想法有所不同,尽pipe聪明的黑客可以做任何事情。 在Common Lisp中,这个故事在概念上会略微更清晰。 然而基本的东西却是相似的:Lisp Reader是一个状态机,它除了执行状态转换并最终声明它是否已经达到“接受状态”之外,还吐出了字符所代表的Lisp数据结构。 因此,字符123
成为数字123
等等。重要的一点是: 这个状态机可以被用户代码修改 。 (如前所述,在CL的情况下是完全正确的;对于Clojure来说,需要一个黑客(不鼓励在实践中使用),但是我离题了,这是我应该详细阐述的PG的文章,所以…)
所以,如果你是一个Common Lisp程序员,而且你恰好喜欢Clojure风格的vector文字的概念,那么你可以在阅读器中插入一个函数,对某些字符序列进行适当的反应 – [
或者#[
可能 – 和把它当作在匹配处结束的vector文本的开始]
。 这样的函数被称为读者macros ,就像常规macros一样,它可以执行任何种类的Lisp代码,包括本身已经被先前注册的读取器macros启用的时髦符号写入的代码。 所以在阅读时有整个语言给你。
把它包起来:
实际上,到目前为止已经certificate的是,可以在读取或编译时运行常规的Lisp函数。 从阅读,编译或运行时间来理解阅读和编译本身是如何实现的一步需要从这里理解阅读和编译本身是由Lisp函数执行的。 您可以随时调用read
或eval
来分别读取字符stream中的Lisp数据或编译和执行Lisp代码。 这就是所有的语言。
注意Lisp满足(3)点的事实对于满足(4)点的方式至关重要 – Lisp提供的macros的特定风格严重依赖于由正则Lisp数据表示的代码,这是由(3)启用的东西。 顺便说一下,在这里只有代码的“树状”方面非常重要 – 你可以想象使用XML编写的Lisp。
1) variables的新概念。 在Lisp中,所有variables都是有效的指针。 值是什么types,而不是variables,分配或绑定variables意味着复制指针,而不是指向。
(defun print-twice (it) (print it) (print it))
'它'是一个variables。 它可以绑定到任何值。 没有限制,也没有与variables相关联的types。 如果你调用函数,参数不需要被复制。 该variables类似于一个指针。 它有一种方法来访问绑定到variables的值。 没有必要保留内存。 当我们调用函数时,我们可以传递任何数据对象:任何大小和任何types。
数据对象有一个“types”,所有的数据对象可以查询其“types”。
(type-of "abc") -> STRING
2) 符号types。 符号不同于string,因为您可以通过比较指针来testing相等性。
符号是具有名称的数据对象。 通常这个名字可以用来查找对象:
|This is a Symbol| this-is-also-a-symbol (find-symbol "SIN") -> SIN
由于符号是真实的数据对象,我们可以testing它们是否是同一个对象:
(eq 'sin 'cos) -> NIL (eq 'sin 'sin) -> T
这允许我们例如写一个符号的句子:
(defvar *sentence* '(mary called tom to tell him the price of the book))
现在我们可以在句子中计算THE的数量:
(count 'the *sentence*) -> 2
在Common Lisp中,符号不仅具有名称,而且还可以具有值,函数,属性列表和包。 所以符号可以用来命名variables或函数。 属性列表通常用于将元数据添加到符号。
3) 使用符号树的代码的符号。
Lisp使用它的基本数据结构来表示代码。
列表(* 3 2)可以是数据和代码:
(eval '(* 3 (+ 2 5))) -> 21 (length '(* 3 (+ 2 5))) -> 3
那个树:
CL-USER 8 > (sdraw '(* 3 (+ 2 5))) [*|*]--->[*|*]--->[*|*]--->NIL | | | vvv * 3 [*|*]--->[*|*]--->[*|*]--->NIL | | | vvv + 2 5
4) 整个语言总是可用的。 在读取时间,编译时间和运行时间之间没有真正的区别。 您可以在编译时读取,读取或运行代码的同时编译或运行代码,并在运行时读取或编译代码。
Lisp提供函数READ来读取文本中的数据和代码,LOAD加载代码,EVAL评估代码,COMPILE编译代码,PRINT将数据和代码写入文本。
这些function始终可用。 他们不走开。 他们可以是任何程序的一部分。 这意味着任何程序都可以读取,加载,评估或打印代码。
它们在C或Java等语言中有什么不同?
这些语言不提供符号,代码作为数据或运行时评估数据作为代码。 C中的数据对象通常是无types的。
除了LISP族语言以外,现在还有其他语言是否有这些构造?
许多语言都有这些function。
区别:
在Lisp中,这些function被devise成语言,因此易于使用。
对于第(1)和(2)点,他正在谈论历史。 Java的variables几乎相同,这就是为什么你需要调用.equals()来比较值。
(3)正在谈论Sexpression式。 Lisp程序是用这种语法编写的,与Java和C这样的特定语法相比,它提供了许多优点,比如以比Cmacros或C ++模板更清晰的方式捕获macros中的重复模式,并使用相同的核心列表来操纵代码您用于数据的操作。
(4)以C语言为例:语言实际上是两种不同的子语言:像if()和while()这样的东西,以及预处理器。 您可以使用预处理器保存不必重复自己,或使用#if /#ifdef跳过代码。 但是这两种语言是完全独立的,在编译时你不能使用while(),就像#if。
C ++使模板变得更糟。 查看一下模板元编程的一些参考资料,它提供了在编译时生成代码的方法,对于非专家来说是非常困难的。 另外,使用模板和macros是一堆黑客和技巧,编译器无法提供一stream的支持 – 如果出现简单的语法错误,编译器无法给出明确的错误消息。
那么,对于Lisp,你用一种语言就可以完成这一切。 在第一天学习时,您可以在运行时使用相同的东西生成代码。 这并不是暗示元编程是微不足道的,但是对于一stream的语言和编译器支持来说,它肯定是更直接的。
点(1)和(2)也适合Python。 以一个简单的例子“a = str(82.4)”为例,解释器首先创build一个值为82.4的浮点对象。 然后调用一个string构造函数,然后返回一个值为“82.4”的string。 左边的'a'只是该string对象的标签。 原始的浮点对象是垃圾回收,因为没有更多的引用。
在计划中,所有东西都以类似的方式作为对象来对待。 我不确定Common Lisp。 我会尽量避免用C / C ++的概念来思考。 当我试图让我的脑袋绕过Lisps的美丽简单时,他们放慢了我的脚步。