Last active
June 7, 2021 11:49
-
-
Save mitranim/148f0a0b51f36ef6a2cd3a0996dc7104 to your computer and use it in GitHub Desktop.
Clojure macros for writing flat blocks with early returns, avoiding the let/if-pyramid
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defmacro return-or [sym expr] {:pre [(symbol? sym)]} | |
`(if (reduced? ~sym) (unreduced ~sym) ~expr)) | |
(defn imp-block [[expr & tail-exprs]] | |
(match expr | |
(('let pattern init) :seq) | |
(if-not tail-exprs | |
(list init) | |
(match (imp-block tail-exprs) | |
(((((:or 'let `let) bindings & tail) :seq)) :seq) | |
`((let [~pattern ~init ~@bindings] ~@tail)) | |
block-tail | |
`((let [~pattern ~init] ~@block-tail)))) | |
(('let & _) :seq) | |
(throw (new IllegalArgumentException | |
"in the root of an `imp` block, `let` must have the form: (let pattern init)")) | |
:else | |
(if-not tail-exprs | |
(list expr) | |
`(~expr ~@(imp-block tail-exprs))))) | |
(defn list-to-expr [[expr & tail-exprs]] | |
(if-not tail-exprs expr `(do ~expr ~@tail-exprs))) | |
(defmacro imp | |
"Variant of a `do` block where `let` emulates imperative-style variable | |
assignment. Convenient for inserting assertions and other side-effectul | |
operations between bindings. | |
Usage: | |
(imp | |
(let pattern <expr>) | |
(when-not <assertion> (throw <fail>)) | |
(let pattern <expr>) | |
(let pattern <expr>) | |
<exprs> | |
...) | |
Each `let` creates a subscope. Adjacent lets are merged together. | |
Examines only the let expressions at the root level that start with the | |
raw 'let symbol. Ignores subforms and 'clojure.core/let." | |
[& exprs] | |
(list-to-expr (imp-block exprs))) | |
(defmacro do? | |
"Variant of a `do` block with early interruption via clojure.core/reduced. | |
When a subform satisfies clojure.core/reduced?, the block short-curcuits, | |
immediately returning that value. | |
Expansion: | |
(do? | |
expr | (let [value expr] (if (reduced? value) (unreduced value) | |
expr | (let [value expr] (if (reduced? value) (unreduced value) | |
expr | (let [value expr] (if (reduced? value) (unreduced value) value))))))) | |
" | |
[& [expr & tail-exprs]] | |
(if tail-exprs | |
`(let [value# ~expr] | |
(return-or value# (do? ~@tail-exprs))) | |
`(unreduced ~expr))) | |
(defn imp?-expr [exprs] | |
(when-let [[expr & tail-exprs] (seq exprs)] | |
(match expr | |
(('let pattern init) :seq) | |
`(let [value# ~init] | |
(return-or | |
value# | |
(let [~pattern value#] ~(imp?-expr tail-exprs)))) | |
(('let & _) :seq) | |
(throw (new IllegalArgumentException | |
"in the root of an `imp?` block, `let` must have the form: (let pattern init)")) | |
:else | |
(if | |
tail-exprs | |
`(let [value# ~expr] | |
(return-or value# ~(imp?-expr tail-exprs))) | |
`(unreduced ~expr))))) | |
(defmacro imp? | |
"Variant of a `do` block that emulates imperative assignment and supports | |
early interruption via clojure.core/reduced. Conceptual combination of `imp` | |
and `do?`. See `(doc imp)` for assignment, and `(doc do?)` for interruption. | |
Usage: | |
(imp? | |
(let [one two] (range 10)) | |
(when (> one 10) | |
(reduced :early-result)) | |
(let [three four] (range 20)) | |
:late-result) | |
General form: | |
(imp? | |
(let pattern reduced-or-result) | |
(let pattern reduced-or-result) | |
reduced-or-result | |
reduced-or-result | |
(let pattern reduced-or-result) | |
reduced-or-result | |
...) | |
Expansion: | |
(imp? | |
(let pattern init) | (let [value init] (if (reduced? value) (unreduced value) (let [pattern value] | |
(let pattern init) | (let [value init] (if (reduced? value) (unreduced value) (let [pattern value] | |
expr | (let [value expr] (if (reduced? value) (unreduced value) | |
(let pattern init) | (let [value init] (if (reduced? value) (unreduced value) (let [pattern value] | |
expr | (let [value expr] (if (reduced? value) (unreduced value) | |
expr | (let [value expr] (if (reduced? value) (unreduced value) | |
(let pattern init) | (let [value init] (if (reduced? value) (unreduced value) (let [pattern value] | |
(let pattern init) | (let [value init] (if (reduced? value) (unreduced value) (let [pattern value] | |
expr | (let [value expr] (if (reduced? value) (unreduced value) | |
expr | (let [value expr] (if (reduced? value) (unreduced value) value))))))))))))))))))))))))) | |
" | |
[& exprs] | |
(imp?-expr exprs)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment