【问题标题】:Is there a simple example for explaining Lisp macros to a "generic" programmer?有没有一个简单的例子可以向“通用”程序员解释 Lisp 宏?
【发布时间】:2011-06-01 07:59:15
【问题描述】:

我最近和一位同事交谈,并试图告诉他 (Common) Lisp 的美妙之处。我试图以某种方式解释宏,因为我认为宏是 Lisp 的杀手级功能之一,但我失败得相当惨——我找不到一个很好的例子,它简短、简洁并且可以被“凡人”理解" 程序员(十年的 Java 经验,完全是个聪明人,但对“高阶”语言的经验很少)。

如果必须的话,你会如何举例解释 Lisp 宏?

【问题讨论】:

  • 我认为对宏的最佳介绍之一是Practical Common Lisp。这是第一章之一,所以没有过多的假设,也没有那么长。整本书都很棒,强烈推荐给任何人,无论是否是“通用”程序员(不过也有关于通用编程的章节!)。

标签: macros lisp common-lisp


【解决方案1】:

根据我的经验,当人们看到宏如何帮助生成代码时,宏会给他们留下最好的印象,而这些代码是过程或其他构造无法实现的。很多时候这样的事情可能被描述为:

<common code>
<specific code> 
<other common code>

其中&lt;common code&gt; 始终相同。以下是此类架构的一些示例:

1. time 宏。 没有宏的语言代码看起来像这样:

int startTime = getCurrentTime();
<actual code>
int endTime = getCurrentTime();
int runningTime = endTime - startTime; 

您不能将所有通用代码都放入过程中,因为它包含实际代码。 (好吧,如果语言支持,你可以在 lambda 函数中创建一个过程并传递实际代码,但并不总是很方便)。
而且,您很可能知道,在 Lisp 中,您只需创建 time 宏并将实际代码传递给它:

(time 
  <actual code>) 

2。事务。 要求 Java 程序员用 JDBC 编写简单的 SELECT 方法 - 这需要 14-17 行,包括打开连接和事务的代码,关闭它们,几个嵌套的 try-catch-finally 语句,只有 1或 2 行唯一代码。
在 Lisp 中,您只需编写 with-connection 宏并将代码减少到 2-3 行。

3.同步。好的,Java、C# 和大多数现代语言已经有了它的声明,但是如果你的语言没有这样的结构怎么办?或者,如果您想引入新的同步类型,例如基于STM 的事务?同样,您应该为此任务编写单独的类并手动使用它,即在要同步的每个语句周围放置通用代码。

这只是几个例子。您可以提及像with-open 系列这样的“不可忘记”宏,它可以清理环境并保护您免受资源泄漏,像cond 这样的新构造宏而不是多个ifs,当然,不要不要忘记像 iforand 这样的惰性结构,它们不会评估它们的参数(与过程应用程序相反)。

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

因此,将其全部纳入并回答有关如何解释宏的原始问题。将 Java 中任何广泛使用的代码(C#、C++ 等)转换为 Lisp,然后将其重写为宏。

【讨论】:

    【解决方案2】:

    新的 WHILE 语句

    您的语言设计者忘记了 WHILE 语句。你给他发了几次邮件。没有成功。从语言版本 2.5、2.6 到 3.0,您已经等待了。什么都没发生……

    在 Lisp 中:

    (defmacro while ... 在此处插入您的 while 实现 ...)

    完成。

    使用 LOOP 的简单实现需要一分钟。

    根据规范生成代码

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

    请参阅Domain Specific Languages in Lisp,这是一个截屏视频,展示了从工作草图到基于简单宏的概括的典型开发周期。

    代码重写

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

    ...
    ... (database-last-user database) ...
    ...
    

    现在您可以编写一个宏 WITH-GETTER,它为 getter 表达式引入一个符号。

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

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

    【讨论】:

    • +1 表示忘记了。 :-)(其余的答案也很好,但是,我特别喜欢那一点。)
    【解决方案3】:

    由于具体示例可能会陷入您编写它们所用语言的细节中,因此请考虑一个不具体但相关的陈述:

    “您知道有时必须编写的所有样板代码吗?您永远不必在 lisp 中编写样板代码,因为您总是可以编写代码生成器来为您完成。”

    通过“样板”,我正在考虑 Java 中的一次性接口实现、覆盖 C++ 中的隐式构造函数、编写 get()-set() 对等。我认为这种修辞策略可能比尝试直接详细解释宏,因为他可能对各种形式的样板都太熟悉了,而他从未见过宏。

    【讨论】:

      【解决方案4】:

      我不太了解 CL,但 Scheme 宏会吗?这是 Scheme 中的一个 while 循环:

      (define-syntax while
        (syntax-rules ()
          ((while pred body ...)
           (let loop ()
             (if pred (begin body ... (loop)))))))
      

      在这种情况下,该示例演示了您可以使用宏轻松编写自己的控制结构。 foof-loop 是一个更有用的循环结构的集合(对于 CL 来说可能没有什么新东西,但仍然适合演示)。


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

      (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))))))
      

      注意let-assq 宏如何允许从“节点”中挑选出valueleftright 键,而无需编写更长的let 表单。

      【讨论】:

        【解决方案5】:

        我将宏视为类似于(或双重)函数的抽象,但您可以选择何时以及如何评估参数。这强调了为什么宏很有用 - 就像函数一样,可以防止代码重复并简化维护。

        我最喜欢的例子是照应宏。比如 aif、awhile 或 aand:

          (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))))))
        

        这些都很简单,可以省去很多打字。

        【讨论】:

          【解决方案6】:

          这不是你可以在短时间内解释的东西,好吧,宏的概念可以用一句话解释,一个例子比如一段时间就很容易理解,问题是那个人会不太明白为什么只有这些微不足道的例子就可以使用宏。

          【讨论】:

            猜你喜欢
            • 2013-07-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-08-01
            相关资源
            最近更新 更多