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 是一种可编程编程语言。