Clojure Cond宏如何工作

时间:2018-09-28 19:13:54

标签: clojure macros

我对Clojure相当满意,但始终避开宏。为了解决这个问题,我正在阅读“精通Clojure宏”,并通常查看一些Clojure核心宏。

在阅读cond宏的同时,我对何时进行实际评估感到有些困惑。假设clauses不为零,并且初始when测试通过,然后评估列表调用。 List是一个函数,因此它必须首先评估其所有参数,然后再输入其主体。第一个参数只是符号'if,然后第二个参数是(first claues),它的值等于第一个测试的值,但是我发现有些困惑的是下一个(第3个)参数会发生什么。看起来像整个表格:

(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))))))

1 个答案:

答案 0 :(得分:1)

查看宏工作原理的最简单方法是使用clojure.core/macroexpand-1clojure.walk/macroexpand-all对其进行检查。

例如,我们可以看到如何扩展以下表格:

(cond
  (pos? 1) :positive
  (neg? -1) :negative)

macroexpand-1

(macroexpand-1
  '(cond
    (pos? 1) :positive
    (neg? -1) :negative))

;; => (if (pos? 1) :positive (clojure.core/cond (neg? -1) :negative))

我们可以看到,当扩展该格式时,clauses绑定到以下表达式的序列:(pos? 1):positive(neg? -1):negative

(first clauses)的计算结果为(pos? 1),其值将用作发出的if的测试表达式。然后,宏通过检查是否有多个子句来检查第一个谓词是否具有所需的结果表达式:(next clauses)的计算结果为(:positive (neg? -1) :negative),这是事实,是发出的{{1}的真实分支。 }将获得if的值(second clauses)

发出的:positive的else分支将得到if。由于发出的代码将再次包含对(clojure.core/cond (neg? -1) :negative)宏的调用,因此它将再次被调用并再次扩展。

要查看完全扩展的代码,我们可以使用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)) 中包含的表单,则可以扩展该主题,我们可以在代码中注入一些副作用:

clauses

我们看到没有执行任何副作用,因为在宏扩展期间没有任何子句被求值。

我们还可以通过提供(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)) 来检查对throw的调用是否在宏扩展过程中进行评估,该调用将导致clauses的else分支被调用:

(if (next clauses) ...

在这里我们看到抛出异常,并且通过返回宏扩展代码,(macroexpand-1 '(cond (pos? 1))) java.lang.IllegalArgumentException: cond requires an even number of forms 宏的宏扩展未正常完成。 cond格式在宏扩展过程中被求值的原因是它没有被引号(例如``(throw ...)`)。