And-let Macro in Clojure

In Clojure we have two nice macros if-let and when-let.

1
2
3
4
5
6
7
8
(if-let [a ...]
  (inc a)
  0)

; or we can use `when-let` when we don't have `else` branch

(when-let [a ...]
  (inc a))

but unfortunately we can’t pass more than two binding forms to these macros, and emulate the behavior of Scheme’s and-let or kind of maybe monad.

I mean smth like that:

1
2
3
4
5
(and-let [a 1 b 2]
         (+ a b)) ; => 3

(and-let [a 1 b nil]
         (+ a b)) ; => nil

It isn’t really hard to implement it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(defmacro and-let [bindings expr]
  (if (seq bindings)
    `(if-let [~(first bindings) ~(second bindings)]
       (and-let ~(drop 2 bindings) ~expr))
     expr))

(use 'clojure.walk)
(macroexpand-all '(and-let [a 1 b 2]
                           (+ a b)))

; (let* [temp__3971__auto__ 1]
;   (if temp__3971__auto__
;     (let* [a temp__3971__auto__]
;       (let* [temp__3971__auto__ 2]
;         (if temp__3971__auto__
;           (let* [b temp__3971__auto__]
;             (+ a b))
;           nil)))
;     nil))

But we could do some improvements here: check count of binding form (should be even), implement else clause, probably find a better name, implementation.

I’m not really sure about else clause, it can be very useful, but it sounds weird to have else part with and-let name. I thought about if-let* and when-let* names, it looks idiomatic for other Lisps but not for Clojure I think.

Wdyt?

PS. Another option is use the maybe monad from algo.monads but it’s extra dependency for relatively small amount of code (if you don’t plan use monads really often) and slightly different behavior.

Peace!

Comments