
"Henk-Jan van Tuyl"
Or Control.Exception in the base package?
With Lisp-style restarts, exceptions (Common Lisp calls them "conditions") don't necessarily "unwind the stack." Instead, they provide a set of alternatives for how to proceed. Calls to the throwing function can be wrapped in a handler that chooses, say, whether to skip or abort. To take a Haskell example, Data.Text.Encoding has an API for doing Unicode decoding with "controllable error handling." It's pretty simple, and not very flexible.
type OnDecodeError = String -> Maybe Word8 -> Maybe Char
decodeUtf8With :: OnDecodeError -> ByteString -> Text
Considering some different possibilities for this API... Something like this (a kind of defunctionalized version) might be more familiar to a CL programmer:
data DecodeCondition = InvalidWord Word8 | UnexpectedEOF data DecodeRestart = Ignore | UseChar Char
decodeUtf8With :: (DecodeCondition -> DecodeRestart) -> ByteString -> Text
We can use ImplicitParams to approximate the dynamic scope behavior, and LambdaCase to write what CL calls the "restart-case":
decodeUtf8 :: (?restart :: DecodeCondition -> DecodeRestart) -> ByteString -> Text
Usage:
myDecode s = let ?restart = \case InvalidWord _ -> UseChar '*' UnexpectedEOF -> Ignore in decodeUtf8 s
* * * One of the cool things about CL's condition system that this implementation doesn't capture is the way the runtime environment can provide interactive prompts for restarting uncaught conditions. An example session:
CL-USER 6 > (restartable-gethash 'mango *fruits-and-vegetables*)
Error: RESTARTABLE-GETHASH error getting MANGO [...] 1 (continue) Return not having found the value. 2 Try getting the key from the hash again. 3 Use a new key. 4 Use a new hash. 5 (abort) Return to level 0. 6 Return to top loop level 0.
Type :b for backtrace, :c <option number> to proceed, or :? for other options
To increase the flexibility of our purely functional restarts, we can use monads:
decodeUtf8With :: Monad m => (DecodeCondition -> m DecodeRestart) -> ByteString -> m Text
myDecode :: ByteString -> IO Text myDecode = decodeUtf8With (\c -> askUserAbout c >>= decide)
We can also use other monads:
cautiousDecode :: ByteString -> Maybe Text cautiousDecode = decodeUtf8With (const Nothing)
This of course opens up a whole world of bizarre control flow possibilities. -- Mikael Brockman @mbrock