Archive for 2013年3月

LEGO 与光剑

2013/03/28

永远不要低估的黑暗面。

Simulation 与 Infinite-Stream

2013/03/02

SICP 确实是本博大精深的书。读过前三章,即便不能领会作者的全部意图,单就其中几个例子也是值得借鉴的。第 3.3.5 节的「约束系统」就是一种我此前并不了解的用 imperative 方式实现 declarative kownledge 的初级方案。第 3.3.4 节的「电路模拟」系统则介绍了如何用「显式 schedule」方式模拟随时间变化的系统。

读到第 3.5.2 节的 infinite-stream 时,跟上作者的思路已略显吃力。以往对递归的理解包含三个要素:终止条件、调用自身、每次调用时减小问题的规模。Inifite-stream 则不同:没有终止条件、每次调用自身时问题规模保持不变、on-demand 解决问题中必要的部分而延迟其余部分。从 3.5.3 节开始,延迟解决的部分甚至不再是直接构建 lambda 函数而是调用 stream 本身。

SICP 的这个部分似乎体现了技术书籍的一个常见问题,即便经典也不例外 —— 作者认为显而易见而略过的东西耗费了读者大量脑力。如下图:

上图 (Figure 3.25) 是第 3.3.4 节对电路系统的示意图,下图 (Figure 3.32) 是第 3.5.3 节对 infinite-stream 的信号处理示意图 (该图的例子是对 input 的积分)。近似的表现形式让我对 Figure 3.32 的意义完全迷惑,因为 infinite-stream 并没有电路模拟那样的 schedule 机制,但具有自反馈和延迟求值。通过不时对 Figure 3.32 发呆,终于在一周后恍然大悟,这两个看似差不多的系统有完全相反的性质。Figure 3.25 的系统是「输入驱动」,系统的动作由左向右进行。而 Figure 3.32 是「输出牵引」,整个系统的动作从右向左引发。输出端像一个 signal puller,每当「需要」一个信号的时候,才会激发必要的输入端提供数值。

阅读 SICP 的另一个难点也是它强大之处。Peter Norvig 在 Amazon 上对此书的评价写道 (斜体为我划出):

But … you’re not looking for one more trick, rather you’re looking for a way of synthesizing what you already know, and building a rich framework onto which you can add new learning over a career.

SICP 是一个 framework。软件开发经验不多的人很难读懂,经验足够丰富的人则面临另一项挑战:如何把已知的东西归结到这个 framework 上。为了让 framework 尽可能通用和严密,SICP 用一些更形式化的词来替代软件业常用的「行话」。读者必须自己体会这些「奇怪」名词和日常所见的联系。例如高级动态语言中经常提及的「lexical scope」在 SICP 中只出现过两次 —— 前言中一笔带过,之后出现在很晚的章节。对 lexical scope 的详细阐述以「environment model」的名称在第 3.2 节中出现。

另一个最初难以理解,但和日常所见联系起来之后就显而易见的概念是 explicit-delay。在第 3.5.4 节中提出了一个解微分方程的 stream/signal-processing 系统,如下图所示。

其中 map 和 integral 的定义相互依赖 (上图的 code 片段中,integral 函数的 delayed-integrand 的传入实参就是 map 函数),所以 integral 中依赖 map 的参数必须设计为延迟取值。听起来很玄,不过你只要把「延迟求值」设想为 pass-by-reference (即传入的不是 map 函数的指针而是函数指针的地址,因为这个函数指针在 integral 被调用之后才被赋予合法值),把 force 想像成 dereference (也就是 C 里著名的 * 操作符) 就一目了然了。这与 C 语言里定义相互依赖的类型时用指针来代替实际类型没有本质区别。

读 SICP 的有两种可能的后果。一种是扩展了你的 mindset,而另一种可能是同事们从此听你在说一种别人完全不懂的语言 (也许从你在 SICP 上看到第一行 Lisp 代码的时候就就注定了这种结果)。