Why don\'t when-let
and if-let
support multiple bindings by default?
So:
(when-let [a ...
b ...]
(+ a b))
If you use cats, then there is a mlet
function that you might find useful :
(use 'cats.builtin) (require '[cats.core :as m]) (require '[cats.monad.maybe :as maybe]) (m/mlet [x (maybe/just 42) y nil] (m/return (+ x y))) ;; => nil
As you can see, the mlet short-circuits when encountering a nil value.
(from section 6.5.1 nil)
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.
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.