macros - 是什么让 Lisp 宏如此特别?

标签 macros lisp homoiconicity

阅读 Paul Graham's essays关于编程语言,人们会认为 Lisp macros是唯一的出路。作为一名忙碌的开发人员,在其他平台上工作,我还没有使用 Lisp 宏的特权。作为想要了解动态的人,请解释是什么让这个功能如此强大。

还请将此与我从 Python、Java、C# 或 C 开发领域理解的内容联系起来。

最佳答案

为了给出简短的答案,宏用于定义 Common Lisp 或领域特定语言 (DSL) 的语言语法扩展。这些语言直接嵌入到现有的 Lisp 代码中。现在,DSL 的语法可以类似于 Lisp(例如 Peter Norvig 的 Prolog Interpreter 用于 Common Lisp)或完全不同(例如 Infix Notation Math 用于 Clojure)。

这是一个更具体的例子:
Python 在语言中内置了列表推导式。这给出了常见情况的简单语法。线路

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

生成一个列表,其中包含 0 到 9 之间的所有偶数。回到 Python 1.5天没有这样的语法;你会使用更像这样的东西:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

它们在功能上是等价的。让我们暂停怀疑并假装 Lisp 有一个非常有限的循环宏,它只做迭代,没有简单的方法来做等同于列表理解。

在 Lisp 中,您可以编写以下内容。我应该注意到,这个人为的例子被选为与 Python 代码相同,而不是 Lisp 代码的好例子。

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

在继续之前,我应该更好地解释一下什么是宏。它是 by 代码对代码执行的转换。也就是说,由解释器(或编译器)读取的一段代码,它将代码作为参数,进行操作并返回结果,然后就地运行。

当然,这需要大量的输入,而程序员又很懒惰。所以我们可以定义 DSL 来进行列表理解。事实上,我们已经在使用一个宏(循环宏)。

Lisp 定义了一些特殊的语法形式。引号 (') 表示下一个标记是文字。准引号或反引号 (`) 指示下一个标记是带有转义符的文字。转义由逗号运算符指示。文字 '(1 2 3) 相当于 Python 的 [1, 2, 3]。您可以将它分配给另一个变量或就地使用它。您可以将 `(1 2 ,x) 视为 Python 的 [1, 2, x] 的等价物,其中 x 之前是一个变量定义。这个列表符号是进入宏的魔法的一部分。第二部分是 Lisp 阅读器,它智能地用宏替换代码,但最好的说明如下:

因此我们可以定义一个名为lcomp(列表理解的缩写)的宏。它的语法将与我们在示例中使用的 python 完全一样 [x for x in range(10) if x % 2 == 0] - (lcomp x for x in (range 10) 如果 (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

现在我们可以在命令行执行:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

非常整洁,是吧?现在它不止于此。如果你愿意,你有一个机制,或者一个画笔。您可以使用任何您可能想要的语法。类似于 Python 或 C# 的 with 语法。或者 .NET 的 LINQ 语法。最后,这就是 Lisp 吸引人们的地方 - 极致的灵 active 。

关于macros - 是什么让 Lisp 宏如此特别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/267862/

相关文章:

lisp - 如果在 lisp 中则嵌套

lisp - "unfold"用于常见的 lisp?

reflection - 哪些情况难以区分代码和数据?

lisp - 如何在 lisp 中读取和编辑 file.txt 的内容

programming-languages - 同形性究竟是什么意思?

reflection - 遍历 Scheme 函数作为列表

c - 语法错误: missing ')' before 'constant'

c - 在编译时确定#defined 字符串长度

c++ - __glibcxx_function_requires 和 __glibcxx_requires_valid_range 宏是如何工作的?

Scala 3 (Dotty) 模式匹配带有宏引用的函数