阅读Paul Graham关于编程语言的文章,你可能会认为Lisp宏是唯一的选择。作为一个忙碌的开发人员,在其他平台上工作,我还没有使用Lisp宏的特权。作为一个想要了解热门话题的人,请解释一下是什么让这个功能如此强大。

请将这一点与我从Python、Java、c#或C开发世界中理解的东西联系起来。


当前回答

简而言之,宏用于定义通用Lisp或领域特定语言(dsl)的语言语法扩展。这些语言直接嵌入到现有的Lisp代码中。现在,dsl可以具有与Lisp相似的语法(如Peter Norvig的用于通用Lisp的Prolog解释器),也可以具有完全不同的语法(例如用于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))))

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

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

Lisp defines a couple of special syntax forms. The quote (') indicates the next token is a literal. The quasiquote or backtick (`) indicates the next token is a literal with escapes. Escapes are indicated by the comma operator. The literal '(1 2 3) is the equivalent of Python's [1, 2, 3]. You can assign it to another variable or use it in place. You can think of `(1 2 ,x) as the equivalent of Python's [1, 2, x] where x is a variable previously defined. This list notation is part of the magic that goes into macros. The second part is the Lisp reader which intelligently substitutes macros for code but that is best illustrated below:

因此,我们可以定义一个名为lcomp(列表理解的缩写)的宏。它的语法将完全像我们在示例中使用的python [x for x in range(10) if x % 2 == 0] - (lcomp x for x in (range 10) if (= (% 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#的语法一样。或者。net的LINQ语法。最终,这就是Lisp吸引人们的地方——极致的灵活性。

其他回答

简而言之,宏用于定义通用Lisp或领域特定语言(dsl)的语言语法扩展。这些语言直接嵌入到现有的Lisp代码中。现在,dsl可以具有与Lisp相似的语法(如Peter Norvig的用于通用Lisp的Prolog解释器),也可以具有完全不同的语法(例如用于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))))

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

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

Lisp defines a couple of special syntax forms. The quote (') indicates the next token is a literal. The quasiquote or backtick (`) indicates the next token is a literal with escapes. Escapes are indicated by the comma operator. The literal '(1 2 3) is the equivalent of Python's [1, 2, 3]. You can assign it to another variable or use it in place. You can think of `(1 2 ,x) as the equivalent of Python's [1, 2, x] where x is a variable previously defined. This list notation is part of the magic that goes into macros. The second part is the Lisp reader which intelligently substitutes macros for code but that is best illustrated below:

因此,我们可以定义一个名为lcomp(列表理解的缩写)的宏。它的语法将完全像我们在示例中使用的python [x for x in range(10) if x % 2 == 0] - (lcomp x for x in (range 10) if (= (% 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#的语法一样。或者。net的LINQ语法。最终,这就是Lisp吸引人们的地方——极致的灵活性。

简而言之,宏是代码的转换。它们允许引入许多新的语法结构。例如,考虑c#中的LINQ。在lisp中,有类似的由宏实现的语言扩展(例如,内置循环构造,迭代)。宏显著地减少了代码重复。宏允许嵌入«小语言»(例如,在c#/java中可以使用xml进行配置,在lisp中可以使用宏实现同样的事情)。宏可能隐藏使用库的困难。

例如,在lisp中你可以写

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

这隐藏了所有数据库的东西(事务,正确的连接关闭,获取数据等),而在c#中,这需要创建SqlConnections, SqlCommands,将SqlParameters添加到SqlCommands,在SqlDataReaders上循环,正确地关闭它们。

Lisp宏代表了几乎在任何大型编程项目中都会出现的一种模式。最终,在一个大的程序中,你会有一段代码,你会意识到,如果你写一个程序,把源代码输出为文本,然后你就可以粘贴进去,这会更简单,更不容易出错。

在Python中,对象有两个方法__repr__和__str__。__str__只是人类可读的表示。__repr__返回一个有效的Python代码表示,也就是说,可以作为有效的Python输入解释器。通过这种方式,您可以创建一些小的Python代码片段,生成可以粘贴到实际源代码中的有效代码。

在Lisp中,整个过程由宏系统形式化。当然,它允许您为语法创建扩展并执行各种奇特的事情,但它的实际用处可以从上面总结出来。当然,Lisp宏系统允许您使用整个语言的全部功能来操作这些“片段”是有帮助的。

想想在C或c++中可以用宏和模板做什么。它们是管理重复代码的非常有用的工具,但它们在相当严重的方面受到限制。

有限的宏/模板语法限制了它们的使用。例如,不能编写扩展为类或函数以外内容的模板。宏和模板不容易维护内部数据。 C和c++复杂且不规则的语法使得编写非常通用的宏非常困难。

Lisp和Lisp宏解决了这些问题。

Lisp宏是用Lisp编写的。您拥有Lisp的全部功能来编写宏。 Lisp有一个非常规则的语法。

与任何精通c++的人交谈,问他们花了多长时间来学习模板元编程所需的所有模板。或者是《现代c++设计》等(优秀)书籍中的所有疯狂技巧,尽管语言已经标准化了10年,但这些技巧仍然很难调试,而且(在实践中)无法在真实的编译器之间移植。如果用于元编程的语言与用于编程的语言相同,那么所有这些问题都消失了!

In python you have decorators, you basically have a function that takes another function as input. You can do what ever you want: call the function, do something else, wrap the function call in a resource acquire release, etc. but you don't get to peek inside that function. Say we wanted to make it more powerful, say your decorator received the code of the function as a list then you could not only execute the function as is but you can now execute parts of it, reorder lines of the function etc.