如何在 Common Lisp 中实现短路的 "and" 宏?
How to implement a short-circuited "and" macro in Common Lisp?
假设宏将采用布尔类型 a
和 b
。如果 a
是 nil
,那么宏应该 return nil
(不计算 b
),否则它 returns b
.你是怎么做到的?
这实际上取决于您可以使用什么。
例如,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))))
简洁明了,但有两个限制:
- 它只适用于两个参数,而内置的 and 和 or 可以接受任意数量的参数。更新解决方案以采用任意数量的参数并不难,但会稍微复杂一些。
- 更重要的是,它非常直接地基于语言中已经存在的延迟操作。也就是说,它利用了 if 不会评估 then 或 else 部分直到它首先评估了条件。
这可能是一个很好的练习,然后,注意当宏需要延迟对某些形式的评估时,它通常是 最简单的 策略(在实现方面,但不一定是最有效的)以使用扩展为采用函数的函数调用的宏。例如,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)))
使用这样的技术,您可以轻松获得二进制 and(以及 or):
(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 元情况(因为 and 和 or 实际上可以接受任意数量的参数),您可以编写一个函数它获取一个 lambda 函数列表并调用它们中的每一个,直到你到达一个会短路(或者到达终点)的函数。 Common Lisp 实际上已经有了这样的函数:every 和 some。使用这种方法,您可以通过将所有参数包装在 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 完成之前它们不会被调用。
唯一的区别是 和 被专门定义为 return 如果前面的所有参数都为真,则最后一个参数的值(例如,(and t t 3) returns 3,不是t,而具体的return未指定 every 的值(除非它将是 true 值)。
使用这种方法,实施或(使用some)并不比实施和更复杂:
(defmacro my-or (&rest args)
`(some #'funcall ,@(mapcar #'(lambda (form)
`(lambda () ,form))
args)))
假设宏将采用布尔类型 a
和 b
。如果 a
是 nil
,那么宏应该 return nil
(不计算 b
),否则它 returns b
.你是怎么做到的?
这实际上取决于您可以使用什么。
例如,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))))
- 它只适用于两个参数,而内置的 and 和 or 可以接受任意数量的参数。更新解决方案以采用任意数量的参数并不难,但会稍微复杂一些。
- 更重要的是,它非常直接地基于语言中已经存在的延迟操作。也就是说,它利用了 if 不会评估 then 或 else 部分直到它首先评估了条件。
这可能是一个很好的练习,然后,注意当宏需要延迟对某些形式的评估时,它通常是 最简单的 策略(在实现方面,但不一定是最有效的)以使用扩展为采用函数的函数调用的宏。例如,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)))
使用这样的技术,您可以轻松获得二进制 and(以及 or):
(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 元情况(因为 and 和 or 实际上可以接受任意数量的参数),您可以编写一个函数它获取一个 lambda 函数列表并调用它们中的每一个,直到你到达一个会短路(或者到达终点)的函数。 Common Lisp 实际上已经有了这样的函数:every 和 some。使用这种方法,您可以通过将所有参数包装在 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 完成之前它们不会被调用。
唯一的区别是 和 被专门定义为 return 如果前面的所有参数都为真,则最后一个参数的值(例如,(and t t 3) returns 3,不是t,而具体的return未指定 every 的值(除非它将是 true 值)。
使用这种方法,实施或(使用some)并不比实施和更复杂:
(defmacro my-or (&rest args)
`(some #'funcall ,@(mapcar #'(lambda (form)
`(lambda () ,form))
args)))