如何实施短路"和" Common Lisp中的宏?

时间:2015-04-06 12:04:41

标签: macros common-lisp short-circuiting

假设宏将采用布尔类型ab。如果anil,则宏应返回nil(无需评估b),否则返回b。你是怎么做到的?

2 个答案:

答案 0 :(得分:2)

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

  1. 它仅适用于两个参数,而内置的以及可以使用任意数量的参数。更新解决方案以获取任意数量的参数并不太难,但它会有点复杂。
  2. 更重要的是,它直接基于语言中已经存在的延迟操作。即,它利用了如果不评估然后其他部分直到它首次评估条件的事实。
  3. 这可能是一个很好的练习,然而,要注意当宏需要延迟对某些表单的评估时,它通常是最简单的策略(在实现方面,但不一定是效率最高的) )使用扩展为函数调用的宏来获取函数。例如, 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-ary情况(因为并且实际上接受任意数量的参数),您可以编写一个函数来获取lambda函数列表和调用它们中的每一个,直到你找到一个会短路的(或者到达终点)。 Common Lisp实际上已经具有这样的功能:每个一些。使用这种方法,您可以通过将所有参数包装在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函数中,所以在每个到达那么远之前不会调用它们。

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

    使用这种方法,实施(使用some)并不比实施更复杂:

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

答案 1 :(得分:1)

这实际上取决于您可以使用的内容。 例如,or可用吗? ifcond

以下是一个例子:

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

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

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