-
-
Save pingles/5150585 to your computer and use it in GitHub Desktop.
(ns if-let-multi-bind.core) | |
(defmacro if-let* | |
([bindings then] | |
`(if-let* ~bindings ~then nil)) | |
([bindings then else & oldform] | |
(let [test (cons #'and (map last (partition 2 bindings)))] | |
`(if ~test | |
(let ~bindings | |
~then) | |
~else)))) | |
(comment | |
(if-let* [a 1 | |
b 3] | |
(+ a b)) | |
;; 4 | |
(if-let* [a 1 | |
b nil] | |
(+ a b)) | |
;; nil | |
(def x 1) | |
(def y false) | |
(if-let* [a x | |
b y] | |
[a b]) | |
;; nil | |
) |
Cool!
One thing to consider might be to use and
instead of (every? identity tsts#)
, otherwise all tests will always be executed, but I guess that depends on what you're expecting if-let*
to do. Interesting that there are so many subtle things about a macro like this.
Cool- the gist now uses and
. Final step is to avoid duplicate evaluation of the test expressions. I wondered whether a sequence that had already been realised wouldn't require the re-evaluation of the tests but it doesn't :(
(defmacro if-let*
([bindings then]
`(if-let* ~bindings ~then nil))
([bindings then else & oldform]
(let [testexprs (map last (partition 2 bindings))
names (map first (partition 2 bindings))
test (cons #'and testexprs)]
`(if ~test
(let ~(vec (interleave names testexprs))
~then)
~else))))
(comment
(if-let* [a (do (println "a") 1)
b (do (println "b") 3)]
(+ a b))
;; a
;; b
;; a
;; b
;; => 4
Any thoughts?
Maybe my suggestion to use and
was actually a bad idea since we "lose" the values of resulting from the tests... I'm starting to think the nested if-let
approach might actually be necessary, at least if:
- Tests should be evaluated lazily, ie. first failure should short-circuit
- Tests should only be evaluated once
But there is most probably other possibilities/solutions too...
Just use recursion!
(defmacro if-let*
([bindings then]
`(if-let* ~bindings ~then nil))
([bindings then else]
(if (empty? bindings)
then
`(if-let ~(vec (take 2 bindings))
(if-let* ~(drop 2 bindings) ~then ~else)
~else))))
This way you don't evaluate any of the test expressions more than once, and you don't lose their values either.
Here is the new if-let* imp:
(defmacro if-let*
([bindings then]
`(if-let* ~bindings ~then nil))
([bindings then else]
(if (seq bindings)
`(if-let [~(first bindings) ~(second bindings)]
(if-let* ~(drop 2 bindings) ~then ~else)
~(if-not (second bindings) else))
then)))
(if-let* [a 1
b (+ a 1) ]
b)
;;=> 2
(if-let* [a 1
b (+ a 1)
c false] ;;false or nil - does not matter
b
a)
;;=> 1
Haha, nice... I've updated my code above- fixes the test evaluation at expansion rather than run time. Only thing to fix is the duplicate evaluation of the tests.