【问题标题】:Clojure: Why does if-let only allow 2 forms in the binding vector?Clojure:为什么 if-let 在绑定向量中只允许 2 种形式?
【发布时间】:2013-04-19 22:00:38
【问题描述】:

当我使用 if-let 时

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

我得到一个 IllegalArgumentException:

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

类似于当-让...

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

可以在clojuredocs 的 cmets 中找到相同的投诉。我找到了一个答案 here 并没有真正令人满意,因为海报似乎已经考虑到了嵌套的 if-let 结构。

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

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

  • 它应该按顺序评估所有绑定。
  • 当全部成功时,它应该评估'then'-case。
  • 如果一个绑定失败,它应该立即中断并评估“else”情况。
  • 如果绑定失败,即使是成功的绑定,也不应该在“else”表达式中可用

【问题讨论】:

  • 你的 if-let 应该做什么并不是很明显。它是否应该在 let 子句中的第一个错误条目时失败?还是应该只测试第一个条目,如果是这样,评估其他 let 子句以在以下代码中使用?即把它当作一个嵌套函数: (if-let [a 2] (let [b nil] (+ a b))) - 我过去当然也想要这个结构......
  • @Korny:关于我的期望,请参阅我更新的问题。

标签: clojure


【解决方案1】:

试试这个。

(defmacro if-let ([bindings true-expr] `(if-lets ~bindings ~true-expr nil)) ([绑定 true-expr false-expr] (条件 (or (not (seq bindings)) (not (zero? (rem (count bindings) 2)))) `(抛出(IllegalArgumentException。“if-lets 需要 2 个或多个 2 个表单在用户:1 的绑定向量中”)) (seq (drop 2 bindings)) `(if-let ~(vec (take 2 bindings)) (if-lets ~(vec (drop 2 bindings)) ~true-expr ~false-expr) ~false-expr) :别的 `(if-let ~(vec 绑定) ~true-expr ~false-expr))))

这个宏通过了下面的这些测试。

(deftest ut-if-let (测试“if-lets 宏(正常情况)” (is (= 0 (if-lets [x 0] x))) (是 (= 0 (if-lets [x 0] x 1))) (is (= 1 (if-lets [x nil] x 1))) (is (= 0 (if-lets [x 0 y x] y))) (是 (= 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 (测试“if-lets 宏(异常情况)” (is (= (try (if-lets [] true false) (catch Exception e (.getMessage e))) “if-lets 需要用户:1 中的绑定向量中的 2 种或 2 种形式的倍数”)) (is (= (try (if-lets [x] true false) (catch Exception e (.getMessage e))) “if-lets 需要用户:1 中的绑定向量中的 2 种或 2 种形式的倍数”)) (is (= (try (if-lets [x true y] true false) (catch Exception e (.getMessage e))) “if-lets 需要用户:1 中的绑定向量中的 2 种或 2 种形式的倍数”)) ) )

【讨论】:

    【解决方案2】:

    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 旨在让您在绑定值只是为了测试和使用它的情况下更轻松。

    【讨论】:

    • 我不同意:如果您在then 子句中要做的只是返回测试绑定的值,那么您不应该使用if-let。即,而不是(if-let [ans (+ 1 2 3)] ans :foo),写(or (+ 1 2 3) :foo) 显然更好。 if-let 对于测试某些东西然后进一步操作它很有用。
    • @amalloy 也许我可以更清楚地说明这一点,您提到的代码描述了 if-let 和 let 不同的一种方式。最后一行描述了 if-let 的一种潜在用途。我会改用“return”这个词,也许这会澄清。
    • @ArthurUlfeldt:我不希望 else 子句中的绑定。更新了我的问题以澄清这一点。
    【解决方案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"
    

    【讨论】:

    • 这绝对有帮助。谢谢!但为什么 if-let 一开始就没有那样做呢?
    猜你喜欢
    • 2012-04-11
    • 2019-11-30
    • 2020-03-29
    • 2015-03-24
    • 1970-01-01
    • 2014-03-21
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多