Clojure:为什么if-let只允许绑定向量中的2个表单?

时间:2013-04-25 20:46:56

标签: clojure

当我使用if-let时

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

我收到IllegalArgumentException:

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

类似于when-let ...

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

同样的投诉可以在clojuredocs的评论中找到。我发现答案here并没有真正满足,因为海报似乎有一个等效的嵌套if-let-structure。

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

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

  • 它应该按顺序评估所有绑定。
  • 当一切都成功时,它应该评估'then'案例。
  • 如果一个绑定失败,它应该立即打破并评估'else'-case。
  • 如果失败,绑定,即使是成功的绑定,也不应该在'else'表达式中提供

3 个答案:

答案 0 :(得分:3)

if-letlet有不同的用途,if-let不仅仅是let的限制版本。实例if-letlet的不同之处在于,该值仅限于then子句,而不是else。

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 :(得分:3)

试试这个:

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

这是在行动:

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"

答案 2 :(得分:1)

试试这个。

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

这个宏通过了以下测试。

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