Macros 有没有一个简单的例子来解释Lisp宏到;“通用”;程序员

Macros 有没有一个简单的例子来解释Lisp宏到;“通用”;程序员,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,最近我和一位同事进行了一次谈话,并试图告诉他(普通)口齿不清的美妙之处。我试图解释宏,因为我认为宏是Lisp的杀手锏之一,但我失败得相当惨——我找不到一个好例子,它是一个“凡人”程序员(十年的java经验,一个聪明的家伙,但经验很少)。“高阶”语言) 如果有必要,您将如何通过示例解释Lisp宏?新的WHILE语句 你的语言设计师忘记了一个WHILE语句。你已经给他发了好几次邮件。没有成功。你从语言版本2.5、2.6到3.0一直在等待。什么都没有发生 在Lisp中: (defmacro while

最近我和一位同事进行了一次谈话,并试图告诉他(普通)口齿不清的美妙之处。我试图解释宏,因为我认为宏是Lisp的杀手锏之一,但我失败得相当惨——我找不到一个好例子,它是一个“凡人”程序员(十年的java经验,一个聪明的家伙,但经验很少)。“高阶”语言)


如果有必要,您将如何通过示例解释Lisp宏?

新的WHILE语句

你的语言设计师忘记了一个WHILE语句。你已经给他发了好几次邮件。没有成功。你从语言版本2.5、2.6到3.0一直在等待。什么都没有发生

在Lisp中:

(defmacro while…在此处插入while实现…)

完成了

使用循环的简单实现需要一分钟

根据规范生成代码

然后,您可能需要解析呼叫详细记录(CDR)。您有带字段描述的记录名称。现在,我可以为每个记录编写类和方法。我还可以发明一些配置格式,解析配置文件并创建类。在Lisp中,我将编写一个宏,从紧凑的描述生成代码

请参阅,一个显示从工作草图到简单的基于宏的概括的典型开发周期的屏幕广播

代码重写

假设您必须使用getter函数访问对象的插槽。现在假设您需要在某个代码区域多次访问某些对象。出于某种原因,使用临时变量不是解决方案

...
... (database-last-user database) ...
...
现在您可以使用-GETTER编写一个宏,它为GETTER表达式引入一个符号

(with-getters (database (last-user database-last-user))
   ...
   ... last-user
   ...)

宏将重写封闭块内的源代码,并用getter表达式替换所有指定的符号。

我对CL不太了解,但Scheme宏可以吗?Scheme中有一个while循环:

(with-getters (database (last-user database-last-user))
   ...
   ... last-user
   ...)
(define-syntax while
  (syntax-rules ()
    ((while pred body ...)
     (let loop ()
       (if pred (begin body ... (loop)))))))
在本例中,该示例演示了您可以使用宏轻松编写自己的控制结构。这是一个更有用的循环结构集合(对于CL的循环结构,可能没有什么新的内容,但对于演示仍然很有用)


另一个用例:从关联列表中选择值。假设用户将列表作为选项传递给函数。使用此宏可以轻松地选择值:

(define-syntax let-assq
  (syntax-rules ()
    ((let-assq alist (key) body ...)
     (let ((key (assq-ref alist 'key)))
       body ...))
    ((let-assq alist (key rest ...) body ...)
     (let ((key (assq-ref alist 'key)))
       (let-assq alist (rest ...) body ...)))))

;; Guile built-in
(define (assq-ref alist key)
  (cond ((assq key alist) => cdr)
        (else #f)))
用法示例:

(define (binary-search tree needle (lt? <))
  (let loop ((node tree))
    (and node
         (let-assq node (value left right)
           (cond ((lt? needle value) (loop left))
                 ((lt? value needle) (loop right))
                 (else value))))))

(define(binary search tree needle)(lt?)根据我的经验,当人们看到宏如何有助于生成过程或其他构造无法生成的代码时,宏会给他们留下最好的印象。这些事情通常可以描述为:

<common code>
<specific code> 
<other common code>
您不能将所有公共代码放入过程中,因为它会包装实际代码。(好的,如果语言支持,您可以创建一个过程并在lambda函数中传递实际代码,但这并不总是方便的)。
而且,您很可能知道,在Lisp中,您只需创建
time
宏并将实际代码传递给它:

(time 
  <actual code>) 
(时间)
-基于事务?同样,您应该为此任务编写单独的类并手动使用它,即在要同步的每个语句周围放置公共代码

这只是几个例子,你可以提到“不要忘记”“像使用open
系列的
这样的宏可以清理环境并保护您不受资源泄漏的影响,像
cond
这样的新构造宏代替了多个
if
if
以及
这样的惰性构造,当然,不要忘记它们不计算参数(与程序应用相反)

一些程序员可能会主张,他们的语言有一种处理这种或那种情况的技术(ORM、AOP等),但问他们,如果存在宏,是否需要所有这些技术


C++(C++语言,C++等),将其转换为LISP,然后将其改写为宏。< /P> < P>我将宏视为抽象的相似(或双)。对于函数,除了您可以选择何时以及如何计算参数之外。这强调了为什么宏和函数一样有用,可以防止代码重复并简化维护

我最喜欢的例子是回指宏。如aif、片刻或A和:

  (defmacro aif (test-form then-form &optional else-form)
  `(let ((it ,test-form))
     (if it ,then-form ,else-form)))

  (defmacro awhile (expr &body body)
      `(do ((it ,expr ,expr)) ((not it))
         ,@body))

    (defmacro aand (&rest args)
      (cond 
        ((null args) t)
        ((null (cdr args)) (car args))
        (t `(aif ,(car args) (aand ,@(cdr args))))))

这些都很简单,可以节省大量的打字时间。

这不是你能在短时间内解释的东西,是的,宏的概念可以用一句话解释,一个例子,比如一段时间,是相当容易理解的,问题是这个人不会真正理解为什么宏是一件很好的东西只有这些琐碎的例子。

< P>因为具体的例子会陷入你所写的语言的细节中,所以考虑一个不具体但又可接受的陈述:

“您知道有时必须编写的所有样板代码吗?您永远不必用lisp编写样板代码,因为您可以随时编写代码生成器来为自己编写样板代码。”


通过“样板”,我在java中考虑一次性接口实现,重写C++中的隐式构造函数,写GET()- SET()结对等等。我认为这种修辞策略可能比试图直接解释宏更有效,因为他可能对各种形式的样板太熟悉了,而他从未见过宏。

相关:我认为最好的宏介绍之一是在。这是第一章之一,所以它没有假设太多了,也没那么长。整本书很好,强烈推荐给任何“泛型”程序员或非“泛型”程序员(虽然也有关于泛型编程的章节!)。+1代表遗忘:-(答案的其余部分也很好,但我特别喜欢这一点。