Lisp 宏如何扩展 Lisp 编程语言的语法和语义?
How does a Lisp macro extend the syntax and semantics of the Lisp programming language?
我正在读的一本书 [1] 是这样说的:
One of the most interesting developments in programming languages has
been the creation of extensible languages—languages whose syntax and
semantics can be changed within a program. One of the earliest and
most commonly proposed schemes for language extension is the macro
definition.
你能举一个扩展 Lisp 编程语言语法和语义的 Lisp 宏的例子(连同解释)吗?
[1] 解析、翻译和编译理论,第 1 卷解析,Aho 和 Ullman,第 58 页。
嗯,也许解释会很简洁,但是你可以看看 lisp 语言本身使用的宏,例如 defun
。
http://clhs.lisp.se/Body/m_defun.htm
在 lisp 中,宏是语言本身的重要组成部分,基本上允许您在编译之前重写代码。
想象一下当时的情景:1958 年,FORTRAN 刚刚被发明出来。仅仅被原子测试的余辉所点燃,原始的 Lisp 程序员正在用原始的 Lisp 编写循环,就像原始的 FORTRAN 程序员一直使用的那样:
(prog ((i 0)) ;i is 0
start ;label beginning of loop
(if (>= i 10)
(go end)) ;skip to end when finished
(do-hard-sums-on i) ;hard sums!
(setf i (+ i 1)) ;increment i
(go start) ;jump to start
end) ;end
(除了,当然这都是大写的,因为那时还没有发明 lower-case,而我写成 setf
的东西会更难看,因为 setf
(宏!)那时还没有发明。
从喷气背包冒出的 only-slightly 有毒烟雾中,另一位 Lisp 程序员从未来逃到了 1958 年。 'Look',他们说,'we could write this strange future thing':
(defmacro sloop ((var init limit &optional (step 1)) &body forms)
(let ((<start> (make-symbol "START")) ;avoid hygiene problems ...
(<end> (make-symbol "END"))
(<limit> (make-symbol "LIMIT")) ;... and multiple evaluation problems
(<step> (make-symbol "STEP")))
`(prog ((,var ,init)
(,<limit> ,limit)
(,<step> ,step))
,<start>
(if (>= ,var ,<limit>)
(go ,<end>))
,@forms
(setf ,var (+ ,var ,<step>))
(go ,<start>)
,<end>)))
'And now',他们说,'you can write this':
(sloop (i 0 10)
(do-hard-sums i))
因此发明了简单的循环。
以后回到这里我们可以看到这个循环扩展成什么:
(sloop (i 0 10)
(format t "~&i = ~D~%" i))
->
(prog ((i 0) (#:limit 10) (#:step 1))
#:start (if (>= i #:limit) (go #:end))
(format t "~&i = ~D~%" i)
(setf i (+ i #:step))
(go #:start)
#:end)
这是原始的 Lisp 程序员用来手写的代码。我们可以 运行 这个:
> (sloop (i 0 10)
(format t "~&i = ~D~%" i))
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
nil
事实上,这就是当今 Lisp 中循环的工作方式。如果我尝试一个简单的 do
循环,Common Lisp 的预定义宏之一,我们可以看到它扩展成什么:
(do ((i 0 (+ i 1)))
((>= i 10))
(format t "~&i = ~D~%" i))
->
(block nil
(let ((i 0))
(declare (ignorable i))
(declare)
(tagbody
#:g1481 (if (>= i 10) (go #:g1480))
(tagbody (format t "~&i = ~D~%" i)
(setq i (+ i 1)))
(go #:g1481)
#:g1480)))
好吧,这个展开不一样,它使用了我没有谈到的结构,但你可以看到重要的事情:这个循环已经被重写为使用 GO
。而且,虽然 Common Lisp 没有定义它的循环宏的扩展,但几乎可以肯定所有标准宏都扩展成这样的东西(但通常更复杂)。
换句话说:Lisp 没有任何原始的循环结构,但是所有这些结构都是通过宏添加到语言中的。这些宏以及以其他方式扩展语言的其他宏,可以由用户编写:它们不必由语言本身提供。
Lisp 是一种可编程编程语言。
我正在读的一本书 [1] 是这样说的:
One of the most interesting developments in programming languages has been the creation of extensible languages—languages whose syntax and semantics can be changed within a program. One of the earliest and most commonly proposed schemes for language extension is the macro definition.
你能举一个扩展 Lisp 编程语言语法和语义的 Lisp 宏的例子(连同解释)吗?
[1] 解析、翻译和编译理论,第 1 卷解析,Aho 和 Ullman,第 58 页。
嗯,也许解释会很简洁,但是你可以看看 lisp 语言本身使用的宏,例如 defun
。
http://clhs.lisp.se/Body/m_defun.htm
在 lisp 中,宏是语言本身的重要组成部分,基本上允许您在编译之前重写代码。
想象一下当时的情景:1958 年,FORTRAN 刚刚被发明出来。仅仅被原子测试的余辉所点燃,原始的 Lisp 程序员正在用原始的 Lisp 编写循环,就像原始的 FORTRAN 程序员一直使用的那样:
(prog ((i 0)) ;i is 0
start ;label beginning of loop
(if (>= i 10)
(go end)) ;skip to end when finished
(do-hard-sums-on i) ;hard sums!
(setf i (+ i 1)) ;increment i
(go start) ;jump to start
end) ;end
(除了,当然这都是大写的,因为那时还没有发明 lower-case,而我写成 setf
的东西会更难看,因为 setf
(宏!)那时还没有发明。
从喷气背包冒出的 only-slightly 有毒烟雾中,另一位 Lisp 程序员从未来逃到了 1958 年。 'Look',他们说,'we could write this strange future thing':
(defmacro sloop ((var init limit &optional (step 1)) &body forms)
(let ((<start> (make-symbol "START")) ;avoid hygiene problems ...
(<end> (make-symbol "END"))
(<limit> (make-symbol "LIMIT")) ;... and multiple evaluation problems
(<step> (make-symbol "STEP")))
`(prog ((,var ,init)
(,<limit> ,limit)
(,<step> ,step))
,<start>
(if (>= ,var ,<limit>)
(go ,<end>))
,@forms
(setf ,var (+ ,var ,<step>))
(go ,<start>)
,<end>)))
'And now',他们说,'you can write this':
(sloop (i 0 10)
(do-hard-sums i))
因此发明了简单的循环。
以后回到这里我们可以看到这个循环扩展成什么:
(sloop (i 0 10)
(format t "~&i = ~D~%" i))
->
(prog ((i 0) (#:limit 10) (#:step 1))
#:start (if (>= i #:limit) (go #:end))
(format t "~&i = ~D~%" i)
(setf i (+ i #:step))
(go #:start)
#:end)
这是原始的 Lisp 程序员用来手写的代码。我们可以 运行 这个:
> (sloop (i 0 10)
(format t "~&i = ~D~%" i))
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
nil
事实上,这就是当今 Lisp 中循环的工作方式。如果我尝试一个简单的 do
循环,Common Lisp 的预定义宏之一,我们可以看到它扩展成什么:
(do ((i 0 (+ i 1)))
((>= i 10))
(format t "~&i = ~D~%" i))
->
(block nil
(let ((i 0))
(declare (ignorable i))
(declare)
(tagbody
#:g1481 (if (>= i 10) (go #:g1480))
(tagbody (format t "~&i = ~D~%" i)
(setq i (+ i 1)))
(go #:g1481)
#:g1480)))
好吧,这个展开不一样,它使用了我没有谈到的结构,但你可以看到重要的事情:这个循环已经被重写为使用 GO
。而且,虽然 Common Lisp 没有定义它的循环宏的扩展,但几乎可以肯定所有标准宏都扩展成这样的东西(但通常更复杂)。
换句话说:Lisp 没有任何原始的循环结构,但是所有这些结构都是通过宏添加到语言中的。这些宏以及以其他方式扩展语言的其他宏,可以由用户编写:它们不必由语言本身提供。
Lisp 是一种可编程编程语言。