unj*_*nj2 144 lisp scheme clojure common-lisp paul-graham
我需要一些帮助来理解Paul Graham的What What Lisp Different中的一些观点.
一个新的变量概念.在Lisp中,所有变量都是有效的指针.值是具有类型而不是变量的值,分配或绑定变量意味着复制指针,而不是它们指向的内容.
符号类型.符号与字符串的不同之处在于您可以通过比较指针来测试相等性.
使用符号树的代码表示法.
整个语言始终可用.读取时间,编译时和运行时之间没有真正的区别.您可以在编译时编译或运行代码,同时在运行时读取或编译代码.
这些点意味着什么?它们在C或Java等语言中有何不同?除了Lisp系列语言之外的任何其他语言现在都有这些结构吗?
Mic*_*zyk 97
Matt的解释非常好 - 他对C和Java进行了比较,我不会这样做 - 但出于某种原因,我很乐意偶尔讨论这个话题,所以 - 这是我的镜头在答案.
列表中的点(3)和(4)似乎是最有趣的,现在仍然相关.
为了理解它们,有必要清楚地了解Lisp代码发生了什么 - 以程序员输入的字符流的形式 - 在执行的过程中.让我们用一个具体的例子:
;; 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"))
Run Code Online (Sandbox Code Playgroud)
这段Clojure代码打印出来aFOObFOOcFOO.请注意,Clojure可能无法完全满足列表中的第四点,因为读取时间并非真正对用户代码开放; 不过,我将讨论对于这个问题意味着什么.
所以,假设我们已经将代码放在某个文件中,我们要求Clojure执行它.另外,我们假设(为了简单起见)我们已经通过了库导入.有趣的一点从远处开始(println并结束).正如人们所期望的那样对它进行了解释/解析,但已经出现了一个重要的问题:结果不是一些特殊的编译器特定的AST表示 - 它只是一个常规的Clojure/Lisp数据结构,即包含一堆符号的嵌套列表,字符串和 - 在这种情况下 - 与#"\d+"文字对应的单个编译的正则表达式模式对象(更多内容见下文).有些Lisps在这个过程中添加了自己的小小的曲折,但Paul Graham主要指的是Common Lisp.在与您的问题相关的要点上,Clojure与CL类似.
在此之后,所有编译器处理(对于Lisp解释器也是如此; Clojure代码总是被编译)是Lisp程序员用来操作的Lisp数据结构.在这一点上,一个很好的可能性变得明显:为什么不允许Lisp程序员编写Lisp函数来操作代表Lisp程序的Lisp数据并输出代表转换程序的转换数据,用于代替原件?换句话说 - 为什么不允许Lisp程序员将他们的函数注册为各种编译器插件,在Lisp中称为宏?事实上,任何体面的Lisp系统都有这种能力.
因此,宏是在编译时在程序表示上运行的常规Lisp函数,在发出实际目标代码的最终编译阶段之前.由于允许运行的代码类型没有限制(特别是,它们运行的代码本身通常是自由使用宏设备编写的),可以说"整个语言在编译时可用" ".
让我们回到那个#"\d+"正则表达式文字.如上所述,在编译器听到第一次提到准备编译的新代码之前,它会在读取时转换为实际编译的模式对象.这是怎么发生的?
好吧,就像Clojure目前的实现方式一样,图片与保罗·格雷厄姆的想法有些不同,尽管聪明的黑客可能有任何可能.在Common Lisp中,故事在概念上会略微清晰.然而,基本知识是相似的:Lisp Reader是一个状态机,除了执行状态转换并最终声明它是否已达到"接受状态"之外,还会吐出字符所代表的Lisp数据结构.因此,字符123变为数字123等.现在重要的一点是:这个状态机可以通过用户代码修改.(如前所述,在CL的情况下这是完全正确的;对于Clojure来说,需要进行黑客攻击(不鼓励并且不在实践中使用).但我离题了,这是PG的文章,我应该详细阐述,所以...)
所以,如果你是一个Common Lisp程序员并且你碰巧喜欢Clojure风格的矢量文字的想法,你可以插入一个函数来对某些字符序列作出适当的反应 - [或者#[可能 - 并将其视为矢量文字的开头以匹配结束].这样的函数被称为读取器宏,就像常规宏一样,它可以执行任何类型的Lisp代码,包括本身用以前注册的读取器宏启用的时髦符号编写的代码.因此,您可以在阅读时阅读整个语言.
实际上,到目前为止已经证明的是,可以在读取时或编译时运行常规Lisp函数; 一步到位需要从这里开始理解读取和编译本身在读取,编译或运行时是否可行,就是要意识到读取和编译本身是由Lisp函数执行的.您可以随时调用read或eval随时从字符流中读取Lisp数据或分别编译和执行Lisp代码.这就是整个语言,一直都在那里.
注意Lisp从列表中满足point(3)的事实对于它设法满足point(4)的方式至关重要--Lisp提供的宏的特殊风格严重依赖于由常规Lisp数据表示的代码,这是(3)启用的东西.顺便说一句,在这里只有代码的"树状"方面才是真正重要的 - 你可以想象有一个使用XML编写的Lisp.
Rai*_*wig 65
1)变量的新概念.在Lisp中,所有变量都是有效的指针.值是具有类型而不是变量的值,分配或绑定变量意味着复制指针,而不是它们指向的内容.
(defun print-twice (it)
(print it)
(print it))
Run Code Online (Sandbox Code Playgroud)
'它'是一个变量.它可以绑定到任何值.没有限制,也没有与变量相关的类型.如果调用该函数,则不需要复制该参数.该变量类似于指针.它有一种方法可以访问绑定到变量的值.无需保留内存.我们在调用函数时可以传递任何数据对象:任何大小和任何类型.
数据对象具有"类型",并且可以查询所有数据对象的"类型".
(type-of "abc") -> STRING
Run Code Online (Sandbox Code Playgroud)
2)符号类型.符号与字符串的不同之处在于您可以通过比较指针来测试相等性.
符号是具有名称的数据对象.通常名称可用于查找对象:
|This is a Symbol|
this-is-also-a-symbol
(find-symbol "SIN") -> SIN
Run Code Online (Sandbox Code Playgroud)
由于符号是真实的数据对象,我们可以测试它们是否是同一个对象:
(eq 'sin 'cos) -> NIL
(eq 'sin 'sin) -> T
Run Code Online (Sandbox Code Playgroud)
这允许我们例如用符号写一个句子:
(defvar *sentence* '(mary called tom to tell him the price of the book))
Run Code Online (Sandbox Code Playgroud)
现在我们可以计算句子中THE的数量:
(count 'the *sentence*) -> 2
Run Code Online (Sandbox Code Playgroud)
在Common Lisp中,符号不仅具有名称,而且还可以具有值,函数,属性列表和包.因此,符号可用于命名变量或函数.属性列表通常用于将元数据添加到符号.
3)使用符号树的代码表示法.
Lisp使用其基本数据结构来表示代码.
列表(*3 2)可以是数据和代码:
(eval '(* 3 (+ 2 5))) -> 21
(length '(* 3 (+ 2 5))) -> 3
Run Code Online (Sandbox Code Playgroud)
那个树:
CL-USER 8 > (sdraw '(* 3 (+ 2 5)))
[*|*]--->[*|*]--->[*|*]--->NIL
| | |
v v v
* 3 [*|*]--->[*|*]--->[*|*]--->NIL
| | |
v v v
+ 2 5
Run Code Online (Sandbox Code Playgroud)
4)整个语言始终可用.读取时间,编译时和运行时之间没有真正的区别.您可以在编译时编译或运行代码,同时在运行时读取或编译代码.
Lisp提供READ函数,用于从文本读取数据和代码,LOAD用于加载代码,EVAL用于评估代码,COMPILE用于编译代码,PRINT用于将数据和代码写入文本.
这些功能始终可用.他们不会消失.他们可以成为任何计划的一部分.这意味着任何程序都可以读取,加载,评估或打印代码.
它们在C或Java等语言中有何不同?
这些语言不提供符号,代码作为数据或运行时评估数据作为代码.C中的数据对象通常是无类型的.
LISP家族语言以外的任何其他语言现在都有这些结构吗?
许多语言都具有这些功能.
区别:
在Lisp中,这些功能被设计为语言,因此易于使用.
Mat*_*tis 33
对于第(1)和(2)点,他正在谈论历史.Java的变量几乎相同,这就是你需要调用.equals()来比较值的原因.
(3)谈论S表达式.Lisp程序是用这种语法编写的,与Java和C等特殊语法相比,它提供了许多优点,例如以比C宏或C++模板更清晰的方式捕获宏中的重复模式,以及使用相同的核心列表操作代码您用于数据的操作.
(4)以C为例:该语言实际上是两种不同的子语言:if()和while()以及预处理器.您使用预处理器来保存不必一直重复自己,或者使用#if/#ifdef跳过代码.但是这两种语言是完全分开的,你不能像编译时那样在编译时使用while().
C++使模板更糟糕.查看一些关于模板元编程的参考资料,它提供了一种在编译时生成代码的方法,对于非专家来说非常困难.此外,它实际上是使用模板和宏的一堆黑客和技巧,编译器无法提供一流的支持 - 如果您发出简单的语法错误,编译器无法给您一个明确的错误消息.
好吧,使用Lisp,你可以用一种语言完成所有这些.在第一天学习时,您可以使用相同的东西在运行时生成代码.这并不是说元编程是微不足道的,但是对于第一类语言和编译器支持来说它肯定更直接.