【问题标题】:How to implement a short-circuited "and" macro in Common Lisp?如何在 Common Lisp 中实现短路的“和”宏?
【发布时间】:2015-04-06 12:04:40
【问题描述】:

假设宏将采用布尔类型 ab 。如果anil,那么宏应该返回nil(不计算b),否则返回b。你是怎么做到的?

【问题讨论】:

    标签: macros common-lisp short-circuiting


    【解决方案1】:

    这真的取决于你可以使用什么。 例如,or 是否可用? if? cond?

    这是一个例子:

    (defmacro and (a b)
      `(if ,a ,b nil)
    

    编辑。在回复评论时,or 更复杂,因为我们必须避免双重评估:

    (defmacro or (a b)
      (let ((v (gensym "OR")))
        `(let ((,v ,a))
           (if ,v ,v ,b))))
    

    【讨论】:

    • 这是一个古老的考试问题,并没有指定我不允许使用任何东西,所以我猜你的解决方案是允许的。我只是很惊讶这这么短。 or 是否同样短,还是需要我们使用 gensym
    • @Sid 注意两点:这些实现利用了这样一个事实,即有其他可用的运算符 (if) 具有非常相关的语义(例如,延迟评估)。更通用的方法使用 lambda 表达式和函数调用来延迟评估。它也适用于任意数量的参数(就像语言中的 andor 一样),而这个答案中的实现却没有。 (它可以很容易地添加,但它会使实现更加复杂。)
    • @JoshuaTaylor,有条件执行和延迟执行。特殊运算符if 做前者,使用闭包稍后执行代码是实现后者的一种方式。但它们是正交的概念。此外,重复出现的or 宏(甚至是没有断言的普通 lisp 等价物)也没有那么复杂:(defmacro or (&rest args) (if (null args) nil (if (null (rest args)) (first args) (let ((v (gensym "OR"))) ``(let ((,v ,(first args))) (if ,v ,v (or ,@(rest args))))))))。我发现闭包的使用更加复杂在这种情况下
    【解决方案2】:

    sds's answer 简洁明了,但有两个限制:

    1. 它只能使用两个参数,而内置的 andor 可以使用任意数量的参数。更新解决方案以采用任意数量的参数并不难,但会有点复杂。
    2. 更重要的是,它非常直接地基于语言中已经存在的延迟操作。即,它利用了 if 在首先评估条件之前不评估 thenelse 部分这一事实。李>

    那么,这可能是一个很好的练习,注意当宏需要延迟对某些表单的评估时,它通常是最简单的策略(就实现而言,但不一定是最有效的) 使用扩展为接受函数的函数调用的宏。例如,with-open-file 的简单实现可能是:

    (defun %call-with-open-file (pathname function)
      (funcall function (open pathname)))
    
    (defmacro my-with-open-file ((var pathname) &body body)
      `(%call-with-open-file
        ,pathname
        (lambda (,var)
          ,@body)))
    

    使用这样的技术,您可以轻松获得二进制(和):

    (defun %and (a b)
      (if (funcall a)
          (funcall b)
          nil))
    
    (defmacro my-and (a b)
      `(%and (lambda () ,a)
             (lambda () ,b)))
    

    CL-USER> (my-and t (print "hello"))
    "hello" ; printed output
    "hello" ; return value
    CL-USER> (my-and nil (print "hello"))
    NIL
    

    类似:

    (defun %or (a b)
      (let ((aa (funcall a)))
        (if aa
            aa
            (funcall b))))
    
    (defmacro my-or (a b)
      `(%or (lambda () ,a)
            (lambda () ,b)))
    

    要处理 n 元情况(因为 andor 实际上接受任意数量的参数),您可以编写一个接受 lambda 函数列表和调用它们中的每一个,直到你到达一个会短路(或者到达终点)的地方。 Common Lisp 实际上已经有这样的功能:everysome。使用这种方法,您可以通过将所有参数包装在 lambda 函数中,以 every 的形式实现

    (defmacro my-and (&rest args)
      `(every #'funcall
              (list ,@(mapcar #'(lambda (form)
                                  `(lambda () ,form))
                              args))))
    

    例如,通过这个实现,

    (my-and (listp '()) (evenp 3) (null 'x))
    

    扩展到:

    (EVERY #'FUNCALL
           (LIST (LAMBDA () (LISTP 'NIL))
                 (LAMBDA () (EVENP 3))
                 (LAMBDA () (NULL 'X))))
    

    由于所有表单现在都包装在 lambda 函数中,因此在 every 达到那一步之前,它们不会被调用。

    唯一的区别是 and 是专门定义的,如果前面的所有参数都为真,则返回最后一个参数的值(例如,(and t t 3) 返回3,而不是 t,而 every 的具体返回值没有指定(除非它是一个 true 值)。

    通过这种方法,实现(使用some)并不比实现复杂:

    (defmacro my-or (&rest args)
      `(some #'funcall ,@(mapcar #'(lambda (form)
                                     `(lambda () ,form))
                                 args)))
    

    【讨论】:

    • 在我看来,为了避免使用if,这有点过于学术了,ifconditional的特殊运算符(即语言的固有部分) > 执行。您描述了 延迟 执行,所以这几乎是一个答案。请注意everysome 最终必须使用某种if。本质上,您是以闭包的形式将执行推迟到everysome,这将有条件地执行您的闭包。没有转义的if,你将如何实现everysome而不直接或间接依赖它?
    • @PauloMadeira 我并不是建议将其作为逻辑运算符的替代实现,而是添加一些关于如何“无需评估 [某物]”即可完成任务的评论。你说得对,在冯诺依曼型机器上,我们总是需要分支执行。也就是说,您仍然可以使用函数进行条件判断,例如,pastebin.com/fPSTLHsRChurch booleans 的快速而肮脏的实现。当然,在 Common Lisp 中布尔值实际上并不是这样,所以它不是很
    • @PauloMadeira 在这方面很有用,但这并不意味着了解其中一些技术永远不会有帮助。这个答案的要点是,如果您需要延迟评估,使用宏执行此操作的最直接方法之一是将表单包装在 lambda 函数中。
    • @PauloMadeira 但是为了表明我不反对在宏扩展中使用 if,我添加了一个确实使用它的二进制版本,但仍然使用 lambda 函数推迟评估。 :)
    猜你喜欢
    • 2014-09-03
    • 2017-06-08
    • 2013-08-10
    • 2011-03-08
    • 2020-08-16
    • 2020-12-18
    • 2023-03-23
    • 1970-01-01
    • 2016-12-22
    相关资源
    最近更新 更多