Re: Lisp Style Restarts in Haskell
Mikael Brockman <mbrock <at> goula.sh>
2014-02-13 11:10:28 GMT
"Henk-Jan van Tuyl" <hjgtuyl <at> chello.nl> writes:
> 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
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
> 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
> 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
> 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
> 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