简体   繁体   English

Clojure Cond 宏的工作原理

[英]How Clojure Cond macro works

I am fairly comfortable with Clojure but have always shied away from macros.我对 Clojure 相当满意,但一直回避宏。 In an attempt to remedy that, I'm reading through "Mastering Clojure Macros" and also looking at some Clojure core macros generally.为了解决这个问题,我正在通读“掌握 Clojure 宏”,并总体上查看一些 Clojure 核心宏。

While reading over the cond macro, I got a little tripped up as to what is actually being evaluated when.在阅读cond宏时,我对实际评估的内容有点困惑。 Assuming clauses is not nil and that the initial when test passes, we then evaluate the list call.假设clauses不是 nil 并且测试通过when的初始值,然后我们评估列表调用。 List is a function, so it must first evaluate all of it's arguments before entering it's body. List 是一个函数,所以它必须在进入它的主体之前首先评估它的所有参数。 The first argument is just the symbol 'if , the next argument is then (first claues) which evaluates to the first test, but then the part I found a bit confusing is what happens with the next (3rd) argument.第一个参数只是符号'if ,下一个参数是 then (first claues) ,它评估为第一个测试,但是我发现有点令人困惑的部分是下一个(第三个)参数会发生什么。 It looks like the entire form:它看起来像整个表格:

(if (next clauses)
    (second clauses)
    (throw (IllegalArgumentException.
            "cond requires an even number of forms")))


is actually evaluated before the final macroexpansion is returned for evaluation.实际上在返回最终宏展开进行评估之前进行评估。 If this is correct, does that mean that the test for an even number of forms occurs before the macro is actually expanded, and therefore can bail with an exception before the macro has actually generated a list for evaluation at runtime?如果这是正确的,这是否意味着偶数形式的测试发生在宏实际展开之前,因此可以在宏实际生成运行时评估列表之前抛出异常?

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

The easiest way to see how a macro works is to check it with clojure.core/macroexpand-1 or clojure.walk/macroexpand-all .查看宏如何工作的最简单方法是使用clojure.core/macroexpand-1clojure.walk/macroexpand-all检查它。

We can for example see how the following form will be expanded:例如,我们可以看到以下表单将如何展开:

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

with macroexpand-1 :使用macroexpand-1

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

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

We can see that when that form is expanded, clauses is bound to the sequence of these expressions: (pos? 1) , :positive , (neg? -1) and :negative .我们可以看到,当扩展该形式时, clauses绑定到以下表达式的序列: (pos? 1) , :positive , (neg? -1):negative

(first clauses) will evaluate to (pos? 1) and its value will be used as the test expression for the emitted if . (first clauses)将评估为(pos? 1)并且其值将用作发出的if的测试表达式。 Then the macro checks if the first predicate has its required result expression by checking if it has more than one clause: (next clauses) will evaluate to (:positive (neg? -1) :negative) which is truthy and the true branch of the emitted if will get the value of (second clauses) which is :positive .然后宏通过检查它是否有多个子句来检查第一个谓词是否具有其所需的结果表达式:( (next clauses)将评估为(:positive (neg? -1) :negative)是真值和真分支发出的if将获得(second clauses)的值,即:positive

The else branch of the emitted if will get (clojure.core/cond (neg? -1) :negative) .发出的if的 else 分支将得到(clojure.core/cond (neg? -1) :negative) As the emitted code will again include a call to cond macro, it will be called again and expanded again.由于发出的代码将再次包含对cond宏的调用,因此它将再次被调用并再次展开。

To see the fully expanded code we can use clojure.walk/macroexpand-all :要查看完全展开的代码,我们可以使用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))

To expand on the topic if the forms included in clauses are evaluated during macro expansion, we can inject some side effects into the code:如果在宏扩展期间评估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))

As we can see no side effects were executed because none of the clauses were evaluated during macro expansion.正如我们所看到的,没有执行任何副作用,因为在宏扩展期间没有评估任何子句。

We can also check if the call to throw is evaluated during macro expansion by providing clauses that will cause the else branch of the (if (next clauses) ... to be called:我们也可以检查,如果该呼叫throw宏扩展过程中,评估通过提供clauses ,这将导致的else分支(if (next clauses) ...被称为:

(macroexpand-1 '(cond (pos? 1)))
java.lang.IllegalArgumentException: cond requires an even number of forms

Here we can see that the exception was thrown and macro expansion of the cond macro didn't complete normally by returning the macro expanded code.在这里我们可以通过返回宏扩展代码看到异常被抛出并且cond宏的宏扩展没有正常完成。 The reason the throw form is evaluated during macro expansion is it is not quoted (eg ``(throw ...)`).在宏扩展期间对throw形式求值的原因是它没有被引用(例如 ``(throw ...)`)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM