宏,中缀表达式,自定义语法与可编程的编程语言
光剑后日谈 #2. 在 Scheme/Racket 的世界里,我们常说“一切代码皆为 S-expression”。一个函数调用是 (函数名 参数1 参数2),一个列表是 (元素1 元素2 元素3),它们都遵循着括号包裹、前缀表示的统一形式。 但细心的你可能会问,'(a b c) 或者 #'(+ 1 stx) 呢?开头的那个 ' 或 #' 字符,怎么看也不像是列表的一部分。它们是如何融入这套 S-expression 体系的? 答案很简单:它们是一种语法糖。我们都知道 'exp 等价于 (quote exp),#'exp 等价于 (syntax exp)。这个单引号 ' 仿佛是一个别名,在 Racket 读取我们的代码时,就悄无声息地将它转换成了标准的形式。这种在“读取阶段”就生效的转换规则,被称为读取器宏(Reader Macro)。 读取器宏背后,是整个 Lisp 家族语言引以为傲的宏系统。宏是一种强大的元编程工具,它本质上是一个在编译期(或称为“展开时”)执行的函数。这个特殊的函数,它的输入是代码(以语法对象的形式),输出也是代码。它允许我们对代码进行任意复杂的、图灵完备的变换,从而彻底扩展语言本身的语法。 那么,亲手编写一个宏,究竟是怎样的体验呢? 宏之初体验:编写你的第一个宏 when 让我们从一个简单的需求开始。在编程中,我们经常遇到一个场景:“当某个条件成立时,执行一系列操作”。用 Racket 的 if 可以这样写: 1 2 3 4 5 (if (< user-level 5) (begin (display "权限不足!") (log-warning "Attempted access by low-level user.")) #f) 每次都写 (begin ...) 有点繁琐(更何况 Racket 强制 if 有两个分支,那个不需要的分支混淆了语义)。如果我们能创造一个新语法 when,让代码变成下面这样,岂不是更清晰? ...