when you have an alternating combination of bindings and side-effects in a function, you have to choose between nested lets (let [a 1] (println a) (let [b 2] (println b))) or underscore bindings (let [a 1, _ (println a), b 2] (println b)) (excuse the unimaginative examples). I came up with this little macro so you get to bind and perform side-effects at the same level without having to pollute your let with all those underscores. in retrospect, I think I might have gotten the idea from https://github.com/nubank/state-flow. curious to see what people here think of it
(defmacro do-let
"Vector forms become bindings as if in a normal `let`, all other
forms are executed for side-effects.
(do-let
a
b
[c d
e f]
x
y)
translates into:
(let
[_ a
_ b
c d
e f
_ x]
y)
Returns the last form."
[& forms]
`(let ~(vec (reduce (fn [acc form]
(cond (vector? form) (into acc form)
:else (into acc ['_ form])))
[]
(butlast forms)))
~(last forms)))it’s very common lisp flavored. when not a binding pair emacs lisp would just introduce a variable without a value. Here this evaluates a form without a binding
do you do this a lot? I’ve sometimes wanted this but i’m not sure i do it enough to reach for it a lot
Nice solution. But I never had this problem. I use destructuring a lot, and it would conflict with it.
I have used delays to get equivalent semantics (being able to do calculations on intermediate values before a side effecting calculation). an additional advantage with delay is it can simplify conditionals / short circuit logic
(let [a (delay 1)
b (delay 2)]
(println @a)
(println @b))(aside, annoying that slack wants to tag people when you deref in a code snippet)
> Nice solution. But I never had this problem. I use destructuring a lot, and it would conflict with it. it doesn't afaik. destructuring works fine inside the vector forms
> do you do this a lot? I’ve sometimes wanted this but i’m not sure i do it enough to reach for it a lot I introduced this at work and it got plenty of organic adoption to my surprise! lsp reports 69 usages, most of them in tests
that’s awesome that it found traction at work. is it often in a particular setting? like in api namespaces, tests, or in very procedural code parts, etc?
almost always tests
oh ok. i can see that being helpful there. that also lower’s people’s bar for what macros come in. pretty cool idea as a test helper for sure. and then it moves into src 🙂
with the now famous https://arnebrasseur.net/2026-07-16-vertical-programming.html macro:
(nest (let [a 1])
(do (println a))
(let [b (inc a)])
(do (println b)))@rolthiolliere haha can't believe I missed that! mind-blowing!