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

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


当前回答

我不确定我能给每个人的(优秀的)帖子添加一些见解,但是……

Lisp宏工作得很好,因为Lisp语法的本质。

Lisp是一种非常规则的语言(把所有东西都想象成一个列表);宏使您能够将数据和代码视为相同的(不需要字符串解析或其他技巧来修改lisp表达式)。将这两个特性结合起来,就有了一种非常简洁的方式来修改代码。

编辑:我想说的是Lisp是同构的,这意味着Lisp程序的数据结构是用Lisp本身编写的。

因此,您最终可以在语言之上创建自己的代码生成器,使用语言本身的所有功能(例如。在Java中,你必须破解字节码编织的方法,尽管一些框架(如AspectJ)允许你使用不同的方法来做到这一点,但它基本上是一种破解)。

在实践中,使用宏可以在lisp的基础上构建自己的迷你语言,而不需要学习其他语言或工具,并使用语言本身的全部功能。

其他回答

我不确定我能给每个人的(优秀的)帖子添加一些见解,但是……

Lisp宏工作得很好,因为Lisp语法的本质。

Lisp是一种非常规则的语言(把所有东西都想象成一个列表);宏使您能够将数据和代码视为相同的(不需要字符串解析或其他技巧来修改lisp表达式)。将这两个特性结合起来,就有了一种非常简洁的方式来修改代码。

编辑:我想说的是Lisp是同构的,这意味着Lisp程序的数据结构是用Lisp本身编写的。

因此,您最终可以在语言之上创建自己的代码生成器,使用语言本身的所有功能(例如。在Java中,你必须破解字节码编织的方法,尽管一些框架(如AspectJ)允许你使用不同的方法来做到这一点,但它基本上是一种破解)。

在实践中,使用宏可以在lisp的基础上构建自己的迷你语言,而不需要学习其他语言或工具,并使用语言本身的全部功能。

Lisp宏允许您决定何时(如果有的话)对任何部分或表达式求值。举个简单的例子,想想C语言:

expr1 && expr2 && expr3 ...

它说的是:计算expr1,并且,如果它是正确的,计算expr2,等等。

现在试着把这个&&变成一个函数…没错,你不能。像这样调用:

and(expr1, expr2, expr3)

将在产生答案之前评估所有三个表达式,而不管expr1是否为假!

使用lisp宏,你可以编写如下代码:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

现在你有一个&&,你可以像函数一样调用它,它不会计算你传递给它的任何表单,除非它们都为真。

要了解这是如何有用的,请进行对比:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

and:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

你可以用宏做的其他事情是创建新的关键字和/或迷你语言(例如,查看(loop…)宏),将其他语言集成到lisp中,例如,你可以编写一个宏,让你这样说:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

这还没有涉及到Reader宏。

希望这能有所帮助。

我从通用的lisp烹饪书中得到了这个,我认为它解释了为什么lisp宏是有用的。

宏是一段普通的Lisp代码,它对另一段假定的Lisp代码进行操作,将其翻译成(更接近于)可执行的Lisp。这听起来可能有点复杂,所以让我们举一个简单的例子。假设您想要一个版本的setq,将两个变量设置为相同的值。所以如果你写

(setq2 x y (+ z 3))

当z=8时,x和y都被设为11。(我想不出这有什么用,但这只是一个例子。)

It should be obvious that we can't define setq2 as a function. If x=50 and y=-5, this function would receive the values 50, -5, and 11; it would have no knowledge of what variables were supposed to be set. What we really want to say is, When you (the Lisp system) see (setq2 v1 v2 e), treat it as equivalent to (progn (setq v1 e) (setq v2 e)). Actually, this isn't quite right, but it will do for now. A macro allows us to do precisely this, by specifying a program for transforming the input pattern (setq2 v1 v2 e)" into the output pattern (progn ...)."

如果你觉得这很好,你可以继续读下去: http://cl-cookbook.sourceforge.net/macros.html

我认为我从来没有见过比这个家伙解释得更好的Lisp宏:http://www.defmacro.org/ramblings/lisp.html

简而言之,宏用于定义通用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吸引人们的地方——极致的灵活性。