Clojure Cond宏的工作原理

Clojure Cond宏的工作原理,clojure,macros,Clojure,Macros,我对Clojure相当满意,但总是回避宏。为了弥补这一点,我正在阅读“掌握Clojure宏”,并大致了解一些Clojure核心宏 在阅读cond宏时,我有点搞不清楚什么是实际计算的时间。假设子句不是nil,并且当测试通过时,初始的,然后我们评估列表调用。List是一个函数,所以它必须首先计算它的所有参数,然后才能进入它的主体。第一个参数只是符号,如果,那么下一个参数就是(first claues),它的计算结果是第一个测试,但是我发现有点混淆的是下一个(第三个)参数会发生什么。它看起来像整个表单

我对Clojure相当满意,但总是回避宏。为了弥补这一点,我正在阅读“掌握Clojure宏”,并大致了解一些Clojure核心宏

在阅读
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
传递代码并接收奇数个子句来证明这一点。让我把它添加到我的答案中。