Skip to content

Instantly share code, notes, and snippets.

@yuest
Created August 16, 2012 04:50
Show Gist options
  • Select an option

  • Save yuest/3367033 to your computer and use it in GitHub Desktop.

Select an option

Save yuest/3367033 to your computer and use it in GitHub Desktop.

Revisions

  1. Yuest Wang revised this gist Sep 26, 2012. 1 changed file with 106 additions and 1 deletion.
    107 changes: 106 additions & 1 deletion readme.mkd
    Original file line number Diff line number Diff line change
    @@ -1 +1,106 @@
    建一个 gist 占位,之后到我用顺手的编辑器里面写。
    大名鼎鼎的 Paul Graham 写过一篇《[The Roots of Lisp](http://www.paulgraham.com/rootsoflisp.html)》,讲解他对 Lisp 根源的理解。这篇文章我在很多年前就试图阅读,但是都不大能读懂,直到去年年末时候我读了几十年前的 Lisp 1.5 手册的相关部分,终于算是理解了。再读一遍 pg 的文章,也就完全能懂了。现在结合自己的理解尝试把这些概念重述一遍。这篇文章就是想以自己的话语阐述一遍这些基本概念。这里会偏重于数学式的思维训练而不是实用性的程序编写,所以和实际情况会稍有出入。计划写两部分,第一部分是7个原始操作,第二部分讲实现 Lisp 解释器。

    在 lisp manual 里面,我看到 John 使用了一种数学人很喜欢的表述方式,就是先定义符号和规则,然后在这套规则下让我们来看看我们可以怎么玩。有一点数学基础的人可能就会觉得这样理解会很容易。

    ##基本定义

    首先定义`符号`,为了简化,我在这篇文章里就定义`可用的符号`**26 个小写字母、10个数字、空格、左右圆括号和单引号**

    然后我们定义`原子(atom)`,原子就是**由字母和数字组成序列并且只能由字母开头**,比如 abc123,比如 foo 或 bar 都是原子。

    之后我们定义`列表(list)`,列表的组成规则是,**一个括号里面有若干个以空格分开的原子**

    接着我们就可以定义`表达式(expression)`了,表达式的定义如下:

    1. **一个原子是表达式**
    2. **一个空括号是表达式**
    3. **一个括号内有若干表达式,它们之间以空格分隔开,这整个括号及其内的表达式组成一个表达式**

    可以看到表达式是递归定义的(定义包含了被定义的术语)。

    例如

    atom
    ()
    (atom)
    (a b)
    (() () ())

    等等

    #函数表示

    之后我们就可以看一下 pg 所说的七个原始操作符。不过要先插一点内容说明一下 lisp 中函数的调用。

    其实 John 在 Lisp Manual 里面定义的是有两种表达式的,S-表达式其实是用大写字母、括号和句点来表示,一个表达式只是一个括号包住它的左、右部分,用句点分隔,比如

    (A.B)
    (A.(B.C))
    (((A.B).(A.C)).(D.(E.F)))

    然后函数用小写字母和中括号、分号、等号来表示函数,如

    car[(A.B)]=A
    cdr[(A.B)]=B

    这样的定义就比较一目了然,知道 car 函数取 S-表达式的左部分,cdr 函数取 S-表达式的右部分。

    但是这个 S-表达式只存在于 MyCarthy 的手册里面,实际的 Lisp 实现都用的是手册里所称的 M-表达式,也就是不用大小写和中括号区分,一律用小写和圆括号,然后大家就说 lisp 是不区分程序和数据的语言。

    什么意思呢?lisp 中的函数调用也是表达式的形式,只是表达式中第一个元素是函数名或操作符(或者直接就是一个匿名函数,这个之后会讲),后面是参数,比如最简单的 `(+ 1 2 3)` 就是计算 `1 + 2 + 3` 的结果,这是操作符;而 `(a b c)` 就相当于其他语言中的 `a(b,c)`。可以看成 Lisp 是把操作符也当成是函数等同起来看待的。

    其实 Lisp 不是不区分数据和程序,而是形式上看起来他们会很像,都输出括号括起来的一个列表。那么问题来了,我怎么知道哪个是数据,哪个是程序呢。比如说我给出 (a b c) 的话,我可能是要表示别的编程语言中 a(b,c) 的意思,也可能是给出 (a b c) 这个列表。这就是 quote 操作符的意义所在:用来区分数据还是程序。

    这就是很多新手(包括以前的我)读到“(quote x),简记成 'x,返回 x”就晕掉而打算放弃的原因,因为这样的句子没有道出其背后的意义。

    ##七个原始操作

    quote:

    现在理解了吧? `'x` 返回的 `x` 表示就是 “x” 这个字母,而不是返回 `x` 变量所代表的值;`'(a b c)` 返回的 `(a b c)` 表示有三个 atom 元素的列表,而不是表示对 `a` 函数以 `b``c` 为参数去调用。

    atom:

    atom 操作符用来判断某个变量是不是原子或空表,`(atom x)` 这个表达式,如果 `x` 是原子或空表,就返回t,否则返回空表。在 Lisp 里面用原子 `'t` 和空表 `()` 表示其他语言中的布尔值真和假。

    eq:

    eq 用来判断两个变量是不是相同的原子或者都是空表。如果是相同原子或都是空表,返回 `'t`,否则返回 `()`

    car 和 cdr:

    前面说过一开始 John 定义的`S-表达式`是一个括号里面只有左右两边两个 S-表达式。car 取左边,cdr 取右边,这样很容易理解。紧接着 John 定义了 S-表达式的列表式写法和 S-表达式的等价关系。

    就是用 `(m1 m2 m3 m4 m5)` 表示 `(M1 . (M2 . (M3 . (M4. (M5 . ())))))`

    所以这样你就能明白为什么现在定义是 car 取列表的第一个元素,而 cdr 取列表去掉第一个元素后的其他元素组成的列表。因为对照 S-表达式过去,cdr 取到的是 `(M2 . (M3 . (M4. (M5 . ()))))`,用列表形式表示就是 `(m2 m3 m4 m5)`。而不断取 cdr,因为 `(m5)` 用 S-表达式是 `(M5 . ())`,取到的就是 `()`

    这样就容易理解了吧。

    cons:

    如果有变量 y 是一个列表,那么 `(cons x y)` 返回一个列表,这个列表的第一个元素是 x,后续是 y 列表中的各个元素。简单说,就是返回的新列表相当于将 x 插入 y 的开头。例如 `(cons 'a '(b c d))` 将返回 `'(a b c d)`

    这在 S-表达式下很容易理解。`cons[A,B]=(A.B)` 的意思就是把 `A``B` 两个 S-表达式合成 `(A.B)` 这样一个。如果 `B` 是一个列表,根据 S-表达式转 m-表达式的规则,不就相当于将 A 加到列表 B 前面嘛。

    cond:

    这个相当于其他语言的 if 或 switch 语句。

    (cond (p1 e1) (p2 e2) (p3 e3))

    相当于

    if (p1) e1; else if (p2) e2; else if (p3) e3;

    那么 else 怎么办呢?可以类似 `if (p) e1; else if (true) e2`,Lisp 里面就是 `(cond (p e1) ('t e2))`

    这也可以解释为 S-表达式的形式:

    cond[(P1.E1), (P2.E2)]

    cond 接受的是以 S-表达式为元素的列表,从第一个元素开始判断,如果这个 S-表达式的左边为真,整个 cond 函数的结果就取这个 S-表达式的右边,不为真就继续看下一个 S-表达式的左边是否为真。

    ##下集预告

    以上就是七个原始操作了,为什么定义这七个操作为原子操作符呢?因为有了以上这七个(quote、atom、eq、car、cdr、cons、cond)我们就可以用 Lisp 写一个解释器,去运行用 Lisp 写成的程序!虽然我不太清楚这意味着什么(当然,这意味着 Lisp 语言的解释器非常容易实现,意味着我们可以很容易地 hack Lisp 这门语言,但我不清楚这在学术意义上意味着什么),但是看起来是不是很厉害的样子,有木有?下一篇文章我们就来做这个事情。
  2. Yuest Wang created this gist Aug 16, 2012.
    1 change: 1 addition & 0 deletions readme.mkd
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    建一个 gist 占位,之后到我用顺手的编辑器里面写。