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

When I use if-let like

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

I get an IllegalArgumentException:

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

Similarly for if-let ...

This is not what I would expect. If-let could try all the bindings and break if someone fails and evaluates the else statement.

The same complaints can be found in the comments on clojuredocs . I found an answer here that didn't really satisfy, since the poster seems to have the equivalent of a nested if-let-structure.

What are the reasons for limiting * -let macro bindings?

UPDATE: It seems incomprehensible to me what are my expectations from if-let:

  • He must evaluate all the bindings sequentially.
  • When everything succeeds, it must evaluate the β€œthat” case.
  • If one binding fails, it should immediately break and evaluate the "else".
  • In case of failure, bindings, even successful ones, should not be available in 'else'-expression
+6
source share
3 answers

if-let and let are for different purposes, and if-let is not just a more limited version of let. the if-let instance differs from let in that the value is only associated with the then clause , not the else clause .

 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 is designed to make life easier when you bind a value just for testing and use.

+3
source

Try the following:

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

Here it is in action:

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

Try it.

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

This macro passed these tests below.

  (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 yx] y)))
     (is (= 0 (if-lets [x 0 yx] y 1)))
     (is (= 1 (if-lets [x nil yx] y 1)))
     (is (= 0 (if-lets [x 0 yxzy] z)))
     (is (= 0 (if-lets [x 0 yxzy] z 1)))
     (is (= 1 (if-lets [x nil yxzy] 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"))
   )
 )
+1
source

Source: https://habr.com/ru/post/943659/


All Articles