简单的二叉树
"简单二叉树"其实不是一个游戏,而是一个游戏策略。真正的游戏,叫"搜索和排序"。这是不同街道的 Lisp 晚会组织者经常在一起竞争的一个游戏,或者不如说是一个比赛。在比赛当晚,两个街道的 Lisp 周末聚会的参加者聚在一起,组成两个队伍,每一个队伍都按照前面说的那样各自安排好一个酒吧招待,然后两个队伍各自按照自己的游戏策略,开始进行运算,看哪一支队伍的运算速度快,能够抢先得到符合要求的结果。关于这样的比赛的具体安排,有兴趣的读者可以参考著名的计算机科学家 Donald Knuth 的"编程的艺术",那套三卷本的巨著。这里不详细叙说了。下面来讲"简单二叉树"具体的游戏策略。
这个游戏策略需要数量相当多的、一对又一对的先生和太太们。而且还要在他们之间进行紧密合作。首先,要把这一对一对的先生和太太们分成小组。每个小组由三对先生和太太组成。这样的小组被称为节点。节点中的第一对先生和太太负责节点的维护和管理。这一对先生和太太当中的太太,要负责召集第二对先生和太太。而第一对当中的先生要负责召集第三对先生和太太。
前面在最基本的游戏规则中规定过,参加游戏的先生和太太每人只能记住不超过一件事情。在上面的安排中,节点中的第一对先生和太太,他们各自记住的唯一一件事情,就是如何分别找到第二对和第三对先生和太太。
节点当中的第二对先生和太太,他俩每人各自要负责记住这个节点要记忆的两件事情当中的一件。事情由酒吧招待交待给这个节点。这两件事情中,太太所记住的这件,称为"键"。先生记住的这件,被称为"值"。当酒吧招待走到他们这个节点前,如果酒吧招待大喊一声 "给我你们的键!"那么,这个节点中领头的那对先生和太太当中的太太,就要赶紧把节点中的第二对先生和太太召集来,然后,第二对中的太太,要把自己记住的事情汇报给酒吧招待。如果酒吧招待大喊"给我你们的值!"接下来应该发生的,读者应该能依葫芦画瓢,自己想出来了。
这里酒吧招待大喊的"给我你们的键!"或者"给我你们的值!"其实是两个 Macro。如果我们把这两个 Macro 扒开来仔细看,就会看到下面的情况。以"给我你们的值!"为例。
酒吧招待走到节点的第一对先生和太太面前,大喊"看啊!"于是这对中的太太,就把自己知道的事情告诉酒吧招待。这件事情,就是在客厅的哪个角落可以找到节点中的第二对先生和太太。酒吧招待于是自己找到第二对先生和太太,然后,对他们大喊一声"苦的!"于是第二对中的先生就把自己知道的事情,告诉酒吧招待。这件事情,就是这个节点的"值"。
所以,像"给我你们的键!"或者"给我你们的值!"这样的喊话,其实就是 CAR、CDR 和 CONS 的 Macro 组合。这里的这两句喊话没有用到 CONS,在以后的例子里面,能看到用到CONS 情况。
酒吧招待还有可能自己事先安排好一件事情,要分配给节点来记住。如果是这样,当她或者他走到节点的领头的先生和太太面前时,她或他就大喊"设置你们的键!"或者"设置你们的值!"然后,相应的,这个节点的第二对先生和太太被召集来以后,先生或者太太就要记住酒吧招待安排的事情。原来记住的事情就被忘掉了。
节点当中的第三对先生和太太,他俩也是每人各自要负责记住一件事情。太太负责记住的事情被称作为"左"。先生负责记住的事情,被称为"右"。太太负责记住的"左",就是另一对先生和太太当中太太的位置。先生负责记住的"右",也是一样类型,是另一对先生和太太当中太太的位置。
如果"简单二叉树"游戏没有出毛病,那么在正常的情况,上面第三对先生和太太各自记住的另外两个太太,她们各自都应该是另外某两个节点当中领头的那对先生和太太当中的太太。
讲了这么半天,如果再讲下去,可能大家真的会忘记我们是在讲 Lisp 和 Scheme!还是不要继续讲先生和太太了。还是回到平日常见的所谓"很 Dry"的计算机科学"专业语言"上来吧。^_^
上面的安排,其实就是用 Lisp 的三个 Pair 实现二叉树的一个 Node。换句话说,上面的例子,显示了如何使用这样看上去很简单、很原始的 Pair 数据结构,来表示复杂的、常见的数据结构。这一点是 Lisp 的精髓之一。在 Lisp 里面,所有的数据结构,最后都归结为 List,归结为 Pair,以及在 Pair 上进行的 CAR、CDR 和 CONS 操作。
许多程序语言的设计者都看不到这一点对于程序语言的重要影响。一个简单、一致、而且足够强大的数据模型,对于程序员来说,是非常重要的事情。可是这一点,偏偏就被绝大多数程序语言所忽视。以后的例子里,会反复看到对这个观点的一再强调。离开了具体的例子,的确很难让这个观点具有足够强的说服力。否则,也不会有那么多程序语言会忽略这个问题了。
Macro
下面讲 R5RS Scheme 中所谓卫生的 Macro。这个 Macro 被支持传统 Lisp Macro 的程序员强烈批评。这些批评中陈述的卫生的 Macro 的缺陷,在下面的例子程序里也会看到。不过本文并不打算详细分析这两个 Macro 系统的利弊。无论如何,这两个 Macro 系统都能恰如其分的体现 Lisp 这一族程序语言在全体程序语言世界里鹤立鸡群的地位。
可能有部分熟悉 C 语言的程序员,一听说 Macro,就想起 C 语言预处理器的噩梦。请这些读者大可放心。Lisp 的 Macro 无论是卫生的还是不够那么卫生的,都远非 C 语言预处理器所可以相提并论。
R5RS 中的 Macro 采用模式匹配的方式来定义和使用。首先用 Macro 编写者提供的模式,去匹配程序中相应的 sexp 表达式。如果发生匹配,则按照匹配的效果,给模式中的变量赋值。模式中这些变量得到相应的赋值后,就可以用这些变量,再按照由 Macro 编写者所提供的,和匹配的模式相对应的模板,来生成另一个 sexp 表达式。Macro 的效果就是由一个 sexp,翻译成另一个 sexp。用到一个模式匹配,再用到一个相应的模板改写。这就是 Macro 简单明了的语义。其实是很容易掌握的。可是在 Lisp 以外的语言里,却从来都没有把它做漂亮。想想 C 语言预处理器里那个恐怖的取消换行效果的"\"字符。
定义 Macro 要用到的语法结构如下。大写的字母表示 Scheme 关键字。在实际写程序时,可以用小写字母。一般都用小写字母。这里用大写字母不过是为了醒目。
(DEFINE-SYNTAX macro-name
(SYNTAX-RULES (literal ...)
匹配以及改写规则之一
匹配以及改写规则之二
匹配以及改写规则之三 等等))
上面的"匹配以及改写规则"按下面这样来写。
(匹配用的模式 改写用的模板)
每个模式都对应一个模板。一个模式就是一个 sexp。第一个单词为"macro-name"或者简单写成"_"。表达式中出现的其它单词,不管位于层次多深的嵌套括号之中,只要不是在前面定义中出现的 literal 中的一员,就被当作当前模式的一个变量。如果这个模式确实发生了匹配,这些变量就被赋予相应的匹配所捕获的单词。
比如下面这个模式。
(_ (var1 var2 ...) var3 ...)
三个点的省略号含义是很直观的。简单的说,它表示后面可能还有若干个在同样的括号嵌套层次上的单词,或者是成对括号括起来的组合,可能也需要匹配。上面这个模式匹配起来就是像下面这样。
匹配 (macro-name (some wonderful roads to) gandor (the land of) the kings)
不匹配 (macro-name ())
不匹配 (macro-name dragon of the (iron hill))
上面第三行,不匹配的原因是,前面的模式要求 macro-name 后面必须紧跟着一对括号括起来的一个组合。而上面第三行,紧跟在 macro-name 后面的只是一个单词,并不是括号括起来的组合。
在上面第一行发生的匹配中,模式变量 var1 变成了单词 some,模式变量 var2 和后面的省略号,涵盖了 wonderful roads to 三个单词。模式变量 var3 和后面的省略号覆盖了四个元素。第一个元素是单词 gandor,第二个元素是一对括号中的全部内容"(the land o f)",再后面的两个元素,是单词 the 和单词 kings。
前面讲了模式匹配,以及模式变量的赋值。下面看模板。模板和匹配的模式变量被一起用来重写原先的 sexp,生成另一个 sexp。来看和上面的模式一起的下面这个模板。
(if (+ var1 (beautify var2) ...) (quote var3) ...)
上面这个模板中用到了全部模式变量。按照这个模板,前面匹配的 sexp 就被改写成下面这样。
(if (+ some (beautify wonderful)
(beautifu roads)
(beautify to))
(quote gandor)
(quote (the land of))
(quote the)
(quote kings))
模板中,模式变量以外的单词,比如上面的 if、+、beautify 和 quote,这些单词被原封不动的拷贝到结果的 sexp。模板中的那些三个点的省略号所起的作用,直观上的含义是很清楚的。
看完这个例子,再来把上面 Macro 的定义在下面完整写出来。这个 Macro 的定义只用到一条模式匹配规则,以及相应的模板重写规则。一般的 Macro 定义,经常出现多条模式匹配规则。在后面模拟 CEC-I 型中华学习机 BASIC 语言的简单程序里能看到很多这样的例子。
(define-syntax macro-name
(syntax-rules ()
((_ (var1 var2 ...) var3 ...)
(if (+ var1 (beautify var2) ...) (quote var3) ...))))
由于篇幅限制,只举了区区一个 Macro 的例子。更多的例子在程序里。
http://www-128.ibm.com/developerwor...l-scheme/part2/