Clojure Cond宏的工作原理
我对Clojure相当满意,但总是回避宏。为了弥补这一点,我正在阅读“掌握Clojure宏”,并大致了解一些Clojure核心宏 在阅读Clojure Cond宏的工作原理,clojure,macros,Clojure,Macros,我对Clojure相当满意,但总是回避宏。为了弥补这一点,我正在阅读“掌握Clojure宏”,并大致了解一些Clojure核心宏 在阅读cond宏时,我有点搞不清楚什么是实际计算的时间。假设子句不是nil,并且当测试通过时,初始的,然后我们评估列表调用。List是一个函数,所以它必须首先计算它的所有参数,然后才能进入它的主体。第一个参数只是符号,如果,那么下一个参数就是(first claues),它的计算结果是第一个测试,但是我发现有点混淆的是下一个(第三个)参数会发生什么。它看起来像整个表单
cond
宏时,我有点搞不清楚什么是实际计算的时间。假设子句
不是nil,并且当测试通过时,初始的,然后我们评估列表调用。List是一个函数,所以它必须首先计算它的所有参数,然后才能进入它的主体。第一个参数只是符号,如果,那么下一个参数就是(first claues)
,它的计算结果是第一个测试,但是我发现有点混淆的是下一个(第三个)参数会发生什么。它看起来像整个表单:
(if (next clauses)
(second clauses)
(throw (IllegalArgumentException.
"cond requires an even number of forms")))
实际上是在返回最终宏展开进行评估之前进行评估的。如果这是正确的,这是否意味着对偶数个表单的测试在宏实际展开之前进行,因此可以在宏实际生成运行时求值列表之前进行异常检查
(defmacro cond
"Takes a set of test/expr pairs. It evaluates each test one at a
time. If a test returns logical true, cond evaluates and returns
the value of the corresponding expr and doesn't evaluate any of the
other tests or exprs. (cond) returns nil."
{:added "1.0"}
[& clauses]
(when clauses
(list 'if (first clauses)
(if (next clauses)
(second clauses)
(throw (IllegalArgumentException.
"cond requires an even number of forms")))
(cons 'clojure.core/cond (next (next clauses))))))
查看宏如何工作的最简单方法是使用或进行检查
例如,我们可以看到以下表单将如何展开:
(cond
(pos? 1) :positive
(neg? -1) :negative)
使用宏扩展-1
:
(macroexpand-1
'(cond
(pos? 1) :positive
(neg? -1) :negative))
;; => (if (pos? 1) :positive (clojure.core/cond (neg? -1) :negative))
我们可以看到,当这种形式被展开时,子句
被绑定到这些表达式的序列:(pos?1)
,:positive
,(neg?-1)
和:negative
(第一条)
将计算为(位置1)
,其值将用作发出的的测试表达式(如果)。然后,宏通过检查第一个谓词是否有多个子句来检查其所需的结果表达式:(下一个子句)
将计算为(:正(负?:负)
,这是真实的,发出的if
的真分支将获得(第二个子句)的值
哪个是:正的
发出的if
的else分支将获得(clojure.core/cond(neg?-1):负值)
。由于发出的代码将再次包含对cond
宏的调用,因此将再次调用并展开该代码
要查看完全展开的代码,我们可以使用clojure.walk/macroexpand all
:
(require 'clojure.walk)
(clojure.walk/macroexpand-all
'(cond
(pos? 1) :positive
(neg? -1) :negative))
;; => (if (pos? 1) :positive (if (neg? -1) :negative nil))
如果在宏扩展过程中评估了子句
中包含的表单,则要扩展主题,我们可以在代码中注入一些副作用:
(clojure.walk/macroexpand-all
'(cond
(do
(println "(pos? 1) evaluated!")
(pos? 1))
(do
(println ":positive evaluated1")
:positive)
(do
(println "(neg? -1) evaluated!")
(neg? -1))
(do
(println ":negative evaluated!")
:negative)))
=>
(if
(do (println "(pos? 1) evaluated!") (pos? 1))
(do (println ":positive evaluated1") :positive)
(if (do (println "(neg? -1) evaluated!") (neg? -1)) (do (println ":negative evaluated!") :negative) nil))
正如我们所看到的,没有执行任何副作用,因为在宏扩展期间没有对任何子句进行评估
我们还可以通过提供将导致调用(if(next子句).
的子句来检查在宏扩展期间是否对throw的调用进行了评估
(macroexpand-1 '(cond (pos? 1)))
java.lang.IllegalArgumentException: cond requires an even number of forms
在这里,我们可以看到异常被抛出,并且通过返回宏扩展代码,cond
宏的宏扩展没有正常完成。在宏扩展期间计算throw
表单的原因是它没有被引用(例如,`(throw…).谢谢你的回复Piotrek!我知道macroexpand-1
和macroexpand
,但它们向我显示了扩展的结果,而不是什么时候发生的。我的问题更多的是问列表
的第三个参数(if表单)在宏体中,在返回用于运行时计算的最终宏展开列表之前对其进行计算。似乎在展开之前,会对(下一个子句)
执行检查。例如,检查(下一个子句)内部if
中的在测试(第一个子句)
的外部if
之前进行实际计算。在宏扩展期间,不会计算子句的任何形式。例如(下一个子句)
检查包含所有子句的序列是否有多个元素,但没有对这些子句进行实际计算(它们是简单的数据元素,如符号列表-”(位置1)
或关键字:正数
)。但形式为(位置1)
在宏扩展期间不会被计算。我已经更新了我的答案,以涵盖在宏扩展期间计算if/when子句。我感谢您的回答,但我的问题非常具体,我可能问得不太好。我想知道的是…非法参数if
测试是否在宏扩展之前发生。它必须是因为如果要从返回:阳性,则测试(下一条)
必须进行评估。我越是打出来思考,我就越觉得答案显然是肯定的,我想知道为什么我一开始就有这个问题!有时正式提问的过程会让你得到我猜的答案。哦,现在我明白了你的问题:)是的,在宏扩展期间,将评估对throw
的调用。您可以通过使用cond
传递代码并接收奇数个子句来证明这一点。让我把它添加到我的答案中。