说人话 - 指代宏

说人话 - 指代宏

指代

你喜欢作文,而我只读诗。

指代,即对事物的引用。英语Get the wrench and put it on the table.it就是上文the wrench的指代。Javathis是当前对象的一个指代。

你可以轻易想到,指代为语言表达带来的便利。特别是当指代的内容比较复杂的时候!

在编程时,复杂通常表现为计算量会比较大或者等待时间长,通常我们会将这些复杂过程的结果暂存,然后在后续过程直接使用该结果,避免了对复杂过程的重复调用,如:

(let ((result (big-long-calculation)))
  (if result
    (foo result)))

JS版(函数命名与CL中的不同仅仅因为编程风格):

var result = big_long_calculation();
if (result) {
	foo (result);
}

用自然语言重述上述逻辑是这个样子的:
计算big—long-calculation,并用result表示结果,如果result为真,在result上做foo操作。

以这样的方式写下去,应该可以快速的满足语文考试最后一题作文的第一要求: 字数不少于800字。

换一种写法,是否读起来更加紧凑:
big-long-calculation计算结果为真时,在it的上做foo操作。
如果用程序语言翻译过来,应如下:

(if (big-long-calculation)
  (foo it))

JS版(函数命名与CL中的不同仅仅因为编程风格):

if (big_long_calculation()) {
	foo (it);
}

那么,如何让it指代big-long-calculation的结果呢?

从那一刻开始,我才知道,有些人已经可以编码这个世界本身。

这里没有 magic,想要指代,必然存在代词与实体的绑定。人类可以自行脑补,而 Common Lisp 有变量捕捉
显然it在“内置”的if中是找不到想要的实体的,让我们给it创造一个更加合适的舞台 —— Anaphoric if, 简称aif

(defmacro aif (test-form then-form &optional else-form)
  `(let ((it ,test-form))
    (if it ,then-form ,else-form)))

在这个舞台上,你可以用程序语言说出本只有人类才能理解的话:

(aif (big-long-calculation)
  (foo it))

这段代码的结果和文首代码的结果完全一样,但后者有前者无法比拟的紧凑性。后者更像诗。

看到这个宏调用,容易让人误会的是,认为这个it指代是一个全局变量。但你会发现,直接在toplevel使用,会得到一个The variable IT is unbound.的错误。上述代码之所以正常工作,得益于aif宏使其it在一个新的词法环境下被求值。

如果defun是在扩展词汇,那么defmacro就是在扩展语法。
macroexpand-1将上述宏调用展开,你会看到这样的代码:

CL-USER> (macroexpand-1 '(aif (big-long-calculation) (foo it)))
(let ((it (big-long-calculation)))
  (if it
    (foo it)
    nil))

请注意,这段代码和文首那段代码虽然长得一样,但却存在巨大的不同——来源。前者是手写的,而后者是aif生成的。这就是宏——生成代码的代码!

代码既数据

如果函数式编程给程序员以抽象过程的机会,那么宏就提供了抽象程序的机会。在lisp程序就是数据,使得修改程序就像修改数据一样方便。在这个舞台上,宏给了你一个轻易地将lisp变成你想要的样子。这样的编程更像是我们在改造语言本身,而不是在编写一个新的程序。然后,你会发现lisp早已成为你目标程序最自然的载体。