> The issue here seems to have little to do with exception handlers; see that
> head [0, throw E]
> also produces such an "indeterminate" answer depending on order of evaluation, as long as you define things as you have here.
I disagree; note that I said:
> we treat an "unhandled exception" as not getting an answer (equivalent to an infinite loop)
More formally, throwing an exception/error results in _|_ (bottom) and all _|_ results are equivalent i.e. we can't tell "which error" we got. In particular, we can't tell the difference between _|_ due to an error being thrown, and _|_ due to an infinite loop.
This is important in a general recursive language, since we can't know (in general) whether evaluating some value (with whatever strategy) will eventually produce a result or not. Consider the following, where omega = (\x -> x x)(\x -> x x):
head [0, omega]
Under a call-by-name strategy this will reduce to 0, under call-by-value it will loop forever, i.e. giving _|_. Should this count as breaking confluence?
Total languages like Agda and Coq would say this breaks confluence, due to general recursion being a side-effect, and hence it should be encoded as such rather than allowed willy-nilly.
Turing-complete languages like Haskell would say that such encoding is cumbersome, and hence that their notion of confluence should be weaker; specifically, evaluating an expression using any evaluation strategy to get a value other than _|_ will produce the same value.
It just so happens that lazy evaluation has the property that if some (perhaps unknown) strategy can reduce an expression to normal form, then non-strict evaluation can also reduce it to normal form. In other words, lazy evaluation avoids all 'avoidable' infinite loops, but it still gets stuck in 'unavoidable' ones. That's nice, but isn't important for confluence.
You're perfectly right that introducing sequencing like `seq` throws a spanner in the works :)