An oversimplified version: the monad rules/type signature only allow for possibly-recursive sequences of statements. That admits straight-line code as well as if, while, and for. But it doesn't allow for any exceptional loop exits (more formally, doesn't allow for any backwards edge from node A to B unless A postdominates B).
There are ways to transform code to allow this to work (there has to be, since Haskell is Turing-complete), but it's not straightforward.
I guess I'm just not sure why this is a problem, exactly. Even if you couldn't use those statements inside of a `do` block, that shouldn't be an issue? Just like using `break` outside of a loop is invalid, it would be the same here.
The Option monad already is a sort of early return. Inside of monadic combinators, you use the monad for control flow instead of those statements. Seems fine to me, though admittedly my Type Theory Wizardry isn't the strongest.
do is only really good if you use it for the whole function, or at least have some way to get the error out of the do block to the code that's supposed to handle it. But if break isn't allowed inside a do block, then if you need to break you have to split up your do blocks into blocks before the break and after the break. Now if there's an error thrown by one of the statements in one of those do blocks, then you have no straightforward way to propagate it out of the function. You would need to pattern match on the result of each do block and return the error if there was one--in other words, you would need to write try!
This is why I called try! monads for imperative languages: the early return that is expands to is the key to playing nice with imperative constructs like break and continue.
Right. Maybe this is because I tend to write small functions, but the amount of things that end in `Ok(())` especially with IO still makes me think it would be useful.
Can't break be simulated by something like MaybeT? And forWithBreakM :: [a] -> (a -> m (Maybe b)) -> m [b] that stops when f yields Nothing? (isn't this actually sequence . forM?)
Yeah, there are transformations that we could apply to the code to make it work. But those transformations aren't trivial in the general case: how about breaking out of multiple loops or returns out of the function from inside loops? At some point, the transformations that we'd have to do would get so complex they'd hinder the programmer's mental model of what code will get generated. We'd also have to do a fair bit of work to make sure the optimizer can figure out what we're encoding so as to not lose performance.
There are ways to transform code to allow this to work (there has to be, since Haskell is Turing-complete), but it's not straightforward.