This instance doesn't seem to behave properly:
> guard True <|> guard False
> guard False <|> guard False
*** Exception: user error (mzero)
One might argue that this cannot result in anything else. But why define such instance in the first place? Is there any good reason to result in _|_
whenever evaluation does not make sense?
The purpose of the Alternative
instance for IO
is to combine IO
actions that might fail (by causing an IO error or otherwise throwing an exception) into a single IO
action that "tries" multiple actions in turn, accepting the first successful one, or -- if all actions fail -- fails itself.
So, something like this would work to read one or more lines (using some
) from standard input or else (using <|>
) complain if no lines are available:
main = (print =<< some getLine) <|> putStrLn "No input!"
or you could write something like:
readConfig :: IO Config
readConfig = readConfigFile "~/.local/myapp/config"
<|> readConfigFile "/etc/myapp/config"
<|> return defaultConfig
Given this, it makes perfect sense that:
guard False <|> guard False
represents an action that, when executed, must fail by generating an exception. If it didn't, as @danidaz has pointed out, then executing the action:
guard False <|> guard False <|> putStrLn "success!"
wouldn't work to execute the third action. Since <|>
is left associative and tries its left action before its right, executing the value of this expression would just execute whatever successful action guard False <|> guard False
represented (e.g., return ()
or whatever) and never try putStrLn "success!"
.
There's a subtlety here that may be throwing you off. Contrary to first appearances, the value of:
guard False <|> guard False
isn't _|_
in the usual sense. Rather it's a perfectly well defined IO action that, if executed will fail to terminate in the sense of throwing an exception. That type of non-termination is still useful, though, because we can catch it (by adding another <|>
alternative, for example!).
Also note, because you haven't supplied a better exception, a default exception of userError "mzero"
is thrown. If you had instead caused failure via:
ioError (userError "one") <|> ioError (userError "two")
you'd see that if all actions fail, the last exception thrown is the one that gets thrown by the composite action.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With