【发布时间】:2015-04-06 12:04:40
【问题描述】:
假设宏将采用布尔类型 a 和 b 。如果a 是nil,那么宏应该返回nil(不计算b),否则返回b。你是怎么做到的?
【问题讨论】:
标签: macros common-lisp short-circuiting
假设宏将采用布尔类型 a 和 b 。如果a 是nil,那么宏应该返回nil(不计算b),否则返回b。你是怎么做到的?
【问题讨论】:
标签: macros common-lisp short-circuiting
这真的取决于你可以使用什么。
例如,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?
if) 具有非常相关的语义(例如,延迟评估)。更通用的方法使用 lambda 表达式和函数调用来延迟评估。它也适用于任意数量的参数(就像语言中的 and 和 or 一样),而这个答案中的实现却没有。 (它可以很容易地添加,但它会使实现更加复杂。)
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))))))))。我发现闭包的使用更加复杂在这种情况下。
sds's answer 简洁明了,但有两个限制:
那么,这可能是一个很好的练习,注意当宏需要延迟对某些表单的评估时,它通常是最简单的策略(就实现而言,但不一定是最有效的) 使用扩展为接受函数的函数调用的宏。例如,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 元情况(因为 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 达到那一步之前,它们不会被调用。
唯一的区别是 and 是专门定义的,如果前面的所有参数都为真,则返回最后一个参数的值(例如,(and t t 3) 返回3,而不是 t,而 every 的具体返回值没有指定(除非它是一个 true 值)。
通过这种方法,实现或(使用some)并不比实现和复杂:
(defmacro my-or (&rest args)
`(some #'funcall ,@(mapcar #'(lambda (form)
`(lambda () ,form))
args)))
【讨论】:
if,这有点过于学术了,if 是 conditional的特殊运算符(即语言的固有部分) > 执行。您描述了 延迟 执行,所以这几乎是一个答案。请注意every 和some 最终必须使用某种if。本质上,您是以闭包的形式将执行推迟到every 和some,这将有条件地执行您的闭包。没有转义的if,你将如何实现every和some而不直接或间接依赖它?