Usually DSLs with control structures rely on custom monads in Scala/Haskell , smthng like (pseudo code):
//I'm going to skip monad transformers and Free assuming it's "all in one monad" (state, AST, interpreters)
for {
counter1 <- variable(0)
counter2 <- variable(0)
repetition <- loop //should be trampolined to avoid stack-overflow
_ <- counter1.update(_ + 1)
counter1value <- counter1.get
_ = print(counter1value)
_ <- repetition.stopIf(counter1Value == 5)
_ <- counter2.update(_ + 2)
counter2value <- counter2.get
_ = print(counter2value)
_ <- repetition.stopIf(counter2Value == 8)
_ <- repetition.repeatForever
}
I won't go to the details given that you're unlikely to choose this approach, but in general bind
(aka flatMap) F[X], X => F[Y]
allows you to simulate ";" (semicolon) traditional in programming languages and also memorize every operation on the go like it was a piece of AST (as you can get it lazily inside the bind's F[Y]
) and even repeat it.
Although, unlike Haskell, Scala supports more "natural" way to stop execution of the code in the middle - namely "throw":
import scala.util._
object StopException extends Throwable
def loop (block: => Unit) = while ({
Try(block) match {
case Failure(StopException) => false
case Failure(t) => throw t
case Success(_) => true
}
}) {}
def stopIf(condition: Boolean) = if (condition) throw StopException
Scala makes it even easier with breakable:
import util.control.Breaks._
def loop (block: => Unit) = breakable(while(true){block})
def stopIf(condition: Boolean) = if (condition) break
Basically your code works as expected with both definitions:
var counter1 = 0
var counter2 = 0
loop {
counter1 += 1
println(counter1)
stopIf(counter1 == 5)
counter2 += 2
println(counter2)
stopIf(counter2 == 8)
}
1
2
2
4
3
6
4
8
P. S. You can also build async loops using stop-exceptions approach with recover on a Future. Alternatively continuation-passing style (suspend, coroutines) could be done with monads (see CpsMonad).