Clojure:为什么if-let在绑定向量中只允许2种形式?

Clojure: Why does if-let only allow 2 forms in the binding vector?

当我使用if-let like

1
(if-let [a 2 b nil] (+ a b))

我收到一个IllegalArgumentException:

1
clojure.core/if-let requires exactly 2 forms in binding vector...

类似的时候让......

这不是我期望的。 if-let可以尝试所有绑定并在一个绑定失败时中断并评估else表达式。

在clojuredocs的评论中可以找到相同的投诉。我在这里找到了一个并不能真正满足的答案,因为发帖人似乎已经想到了嵌套的if-let-structure。

有什么理由限制* -let宏的绑定?

更新:
似乎还不清楚,我对if-let的期望是:

  • 它应该按顺序评估所有绑定。
  • 当一切都成功后,它应该评估'then \\'情况。
  • 如果一个绑定失败,它应该立即中断并评估" else"情况。
  • 如果失败,即使在\\'else \\'表达式中,即使是成功的绑定也不应使用


尝试一下:

1
2
3
4
5
6
7
(defmacro if-let-multi
  ([bindings then-exp]
     (let [values (take-nth 2 (rest bindings))]
       `(if (and ~@values) (let ~bindings ~then-exp) false)))
  ([bindings then-exp else-exp]
     (let [values (take-nth 2 (rest bindings))]
       `(if (and ~@values) (let ~bindings ~then-exp) ~else-exp))))

这里正在起作用:

1
2
3
4
5
6
user> (if-let-multi [a 2 b nil] (+ a b))
false
user> (if-let-multi [a 2 b 3] (+ a b))
5
user> (if-let-multi [a 2 b nil] (+ a b)"NO WAY")
"NO WAY"


if-letlet具有不同的用途,并且if-let不仅仅是let的受限制版本。 if-let的实例与let的不同之处在于,该值仅绑定了then子句,而不绑定else。

1
2
3
4
5
6
user> (if-let [ans (+ 1 2 3)] ans :foo)  
6
user> (if-let [ans (+ 1 2 3)] ans ans)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: ans in this context, compiling:(NO_SOURCE_PATH:1)
user> (let [ans (+ 1 2 3)] ans ans)
6

if-let旨在使绑定值只是为了测试和使用它时使生活更轻松。


尝试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(defmacro if-lets
  ([bindings true-expr] `(if-lets ~bindings ~true-expr nil))
  ([bindings true-expr false-expr]
    (cond
      (or (not (seq bindings)) (not (zero? (rem (count bindings) 2))))
        `(throw (IllegalArgumentException."if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
      (seq (drop 2 bindings))
        `(if-let ~(vec (take 2 bindings))
                 (if-lets ~(vec (drop 2 bindings))
                          ~true-expr
                          ~false-expr)
                 ~false-expr)
      :else
        `(if-let ~(vec bindings)
                 ~true-expr
                 ~false-expr))))

此宏通过了以下这些测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
(deftest ut-if-lets
  (testing"if-lets macro (normal cases)"
    (is (= 0 (if-lets [x 0] x)))
    (is (= 0 (if-lets [x 0] x 1)))
    (is (= 1 (if-lets [x nil] x 1)))
    (is (= 0 (if-lets [x 0 y x] y)))
    (is (= 0 (if-lets [x 0 y x] y 1)))
    (is (= 1 (if-lets [x nil y x] y 1)))
    (is (= 0 (if-lets [x 0 y x z y] z)))
    (is (= 0 (if-lets [x 0 y x z y] z 1)))
    (is (= 1 (if-lets [x nil y x z y] y 1)))
    (is (= true (if-lets [x true] true false)))
    (is (= false (if-lets [x false] true false)))
    (is (= true (if-lets [x true y true] true false)))
    (is (= false (if-lets [x false y true] true false)))
    (is (= false (if-lets [x true y false] true false)))
    (is (= true (if-lets [x true y true z true] true false)))
    (is (= false (if-lets [x false y true z true] true false)))
    (is (= false (if-lets [x true y false z true] true false)))
    (is (= false (if-lets [x true y true z false] true false)))
  )
)

(deftest ut-if-lets-ab
  (testing"if-lets macro (abnormal cases)"
    (is (= (try (if-lets [] true false) (catch Exception e (.getMessage e)))
       "if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
    (is (= (try (if-lets [x] true false) (catch Exception e (.getMessage e)))
       "if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
    (is (= (try (if-lets [x true y] true false) (catch Exception e (.getMessage e)))
       "if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
  )
)