Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't when-let and if-let support multiple bindings by default?

Tags:

Why don't when-let and if-let support multiple bindings by default?

So:

(when-let [a ...            b ...]   (+ a b)) 

...instead of:

(when-let [a ...   (when-let [b ...     (+ a b))) 

I am aware that I can write my own macro or use a monad (as described here: http://inclojurewetrust.blogspot.com/2010/12/when-let-maybe.html).

like image 532
ponzao Avatar asked Jul 26 '12 18:07

ponzao


Video Answer


2 Answers

Because (for if-let, at least) it's not obvious what to do with the "else" cases.

At least, motivated by Better way to nest if-let in clojure I started to write a macro that did this. Given

(if-let* [a ...           b ...]   action   other) 

it would generate

(if-let [a ...]   (if-let [b ...]     action     ?)) 

and it wasn't clear to me how to continue (there are two places for "else").

You can say that there should be a single alternative for any failure, or none for when-let, but if any of the tests mutate state then things are still going to get messy.

In short, it's a little more complicated than I expected, and so I guess the current approach avoids having to make a call on what the solution should be.

Another way of saying the same thing: you're assuming if-let should nest like let. A better model might be cond, which isn't a "nested if" but more an "alternative if", and so doesn't fit well with scopes... or, yet another way of saying it: if doesn't handle this case any better.

like image 197
andrew cooke Avatar answered Sep 17 '22 13:09

andrew cooke


Here is when-let*:

(defmacro when-let*   "Multiple binding version of when-let"   [bindings & body]   (if (seq bindings)     `(when-let [~(first bindings) ~(second bindings)]        (when-let* ~(vec (drop 2 bindings)) ~@body))     `(do ~@body))) 

Usage:

user=> (when-let* [a 1 b 2 c 3]                 (println "yeah!")                 a) ;;=>yeah! ;;=>1   user=> (when-let* [a 1 b nil c 3]                 (println "damn! b is nil")                 a) ;;=>nil 


Here is if-let*:

(defmacro if-let*   "Multiple binding version of if-let"   ([bindings then]    `(if-let* ~bindings ~then nil))   ([bindings then else]    (if (seq bindings)      `(if-let [~(first bindings) ~(second bindings)]         (if-let* ~(vec (drop 2 bindings)) ~then ~else)         ~else)      then))) 

Usage:

user=> (if-let* [a 1                   b 2                   c (+ a b)]               c               :some-val) ;;=> 3  user=> (if-let* [a 1 b "Damn!" c nil]               a               :some-val) ;;=> :some-val 

EDIT: It turned out bindings should not be leaked in the else form.

like image 25
Ertuğrul Çetin Avatar answered Sep 20 '22 13:09

Ertuğrul Çetin