Lisp 宏如何扩展 Lisp 编程语言的语法和语义?

Rog*_*llo 0 lisp common-lisp

我正在读的一本书[1]是这样说的:

\n
\n

编程语言中最有趣的发展之一是可扩展语言的创建,其语法和语义可以在程序内更改。最早和\n最常提出的语言扩展方案之一是宏定义。

\n
\n

您能否给出一个扩展 Lisp 编程语言的语法和语义的 Lisp 宏示例(并附上解释)?

\n

[1]解析、翻译和编译理论,第 1 卷解析:Aho 和 Ullman,第 58 页。

\n

ign*_*ens 6

想象一下当时的场景:1958 年,FORTRAN 刚刚被发明。仅在原子测试的余辉的照耀下,原始 Lisp 程序员正在以原始 FORTRAN 程序员一贯的方式用原始 Lisp 编写循环:

(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
Run Code Online (Sandbox Code Playgroud)

(当然,这一切都是大写的,因为当时还没有发明小写字母,而且我写的东西setf会更难看,因为setf(宏!)当时也还没有发明)。

另一位从未来逃到 1958 年的 Lisp 程序员,从喷气背包中冒出微毒的烟雾。“看,”他们说,“我们可以写出这个奇怪的未来事物”:

(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>)))
Run Code Online (Sandbox Code Playgroud)

他们说,“现在你可以写这个”:

(sloop (i 0 10)
  (do-hard-sums i))
Run Code Online (Sandbox Code Playgroud)

简单的循环就这样被发明了。

稍后回到这里,我们可以看到这个循环扩展成什么:

(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)
Run Code Online (Sandbox Code Playgroud)

这是原始 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
Run Code Online (Sandbox Code Playgroud)

事实上,这就是当今 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)))
Run Code Online (Sandbox Code Playgroud)

嗯,这个扩展是不一样的,它使用了我没有讨论过的结构,但是你可以看到重要的事情:这个循环已经被重写为使用GO. 而且,尽管 Common Lisp 没有定义其循环宏的扩展,但几乎可以肯定的是,所有标准宏都扩展成这样的东西(但通常更复杂)。

换句话说:Lisp 没有任何原始循环结构,但所有此类结构都是通过宏添加到语言中的。这些宏以及以其他方式扩展语言的其他宏可以由用户编写:它们不必由语言本身提供。

Lisp 是一种可编程编程语言。