Could you give a little more context here? If this is just a puzzle and you want it to be a pure function, I suspect what you’re describing could be achieved with a continuation, but it would likely be messy. If you don’t care about purity, you could use a keep-going? atom that holds a boolean outside the doseq, then check that before calling check-fn. You’d need to pass a function that sets it to false to your code (or maybe define that symbol while you’re evaluating code in the macro version). The downside of all this is that the notion of when to stop is split across two different places (check-exp and code). I’m finding it hard to say what a good way forward would be here without a little more context.
I generally agree with what @bmaddy is saying here. Imperative looping isn’t considered idiomatic in Clojure, functional approaches are generally preferred. As @bmaddy mentioned in his last comment, it could be useful to discuss the wider problem here – as the wider problem may have a neat functional solution.
That being said, Rosetta Code has a Clojure example that uses break in imperative languages, but loop/recur in Clojure. The following might compile to a Java loop using break:
Show a loop which prints random numbers (each number newly generated each loop) from 0 to 19 (inclusive).
If a number is 10, stop the loop after printing it, and do not generate any further numbers.
Otherwise, generate and print a second random number before restarting the loop.
If the number 10 is never generated as the first number in a loop, loop forever.
(loop [[a b & more] (repeatedly #(rand-int 20))]
(println a)
(when-not (= 10 a)
(println b)
(recur more)))
It’s not pretty The macro uses an anaphoric binding to access a function that sets the :while atom to false. If you want to know more about anaphoric macros I recommend reading Paul Graham’s On Lisp, and Doug Hoyte’s Let Over Lambda.
I think using exceptions is the simplest and maybe only way here. Otherwise there is no way to return from a non leaf position.
Break and continue would both throw. Your macro would try/catch it. Continue can be try/catched inside the body of the loop, and calls recur in the catch clause. Your break would try/catch outside the loop, and just return in its catch.
I second everyone else though. Apart from a fun experiment, please don’t let this make it inside of production code