Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of instance Alternative IO?

Tags:

haskell

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?

like image 723
sevo Avatar asked Nov 29 '22 06:11

sevo


1 Answers

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.

like image 186
K. A. Buhr Avatar answered Dec 09 '22 14:12

K. A. Buhr