a breaking monad

Hal Daume t-hald@microsoft.com
Thu, 31 Jul 2003 13:18:40 -0700


i've noticed in a lot of my imperative-looking monadic code, i have lots
of stuff that looks like:

> ... =3D do
>   q <- some test
>   if q
>     then return some constant
>     else do
>       major code body here

lots of these things embedded makes the code hard to read and introduces
way too much indentation (i'm one of those who doesn't like curly
braces).

one solution to this is to write it as:

> ... =3D do
>   q <- some test
>   if q then return some constant else do
>   major code body here

but this is ugly, IMO :).

my solution was to fashion a monad transformer that supports a break
statement.  basically, it looks like:

> data Break b m a =3D Break { runBreak :: m (Maybe b, a) }
>=20
> instance MonadTrans (Break b) where
>   lift x =3D Break $ do a <- x; return (Nothing, a)
>=20
> instance Monad m =3D> Monad (Break b m) where
>   return a =3D Break $ return (Nothing, a)
>   b >>=3D k  =3D Break $ runBreak b >>=3D \ (broken, a) ->
>                       case broken of
>                         Nothing -> runBreak (k a)
>                         Just br -> return (Just br, undefined)
>=20
> breakable :: Monad m =3D> Break a m a -> m a
> breakable b =3D runBreak b >>=3D \ (broken, a) ->
>                 case broken of
>                   Nothing -> return a
>                   Just br -> return br
>=20
> break :: Monad m =3D> b -> Break b m a
> break b =3D Break (return (Just b, undefined))

essentially you introduce code blocks with the "breakable" function and
then can break with the break function.  a useful combinator i've found
is:

> breaksTo :: Monad m =3D> m Bool -> b -> Break b m ()
> breaksTo k b =3D lift k >>=3D \x -> if x then break b else return ()

using this, you can write stuff which looks like:

> test1 :: IO [String]
> test1 =3D breakable $ do
>   lift $ putStrLn "Enter something, or nothing to quit:"
>   l <- lift $ getLine
>   when (null l) $ break []
>   rest <- lift test1
>   return (l:rest)
>=20
> test2 :: IO ()
> test2 =3D breakable $ repeatM $ do=20
>   x <- lift $ getLine >>=3D passThrough putStrLn
>   when (null x) $ break ()
>=20
> test3 :: Handle -> IO [String]
> test3 h =3D breakable $ do
>   hIsEOF h `breaksTo` []
>   lift $ do=20
>     l <- hGetLine h
>     rest <- test3 h
>     return (l:rest)

where passThrough and repeatM are:

> repeatM :: Monad m =3D> m () -> m ()
> repeatM x =3D x >> repeatM x
>=20
> passThrough :: Monad m =3D> (a -> m b) -> a -> m a
> passThrough f a =3D f a >> return a

so, my questions are: does this exist in some other form I'm not aware
of?  is there something fundamentally broken about this (sorry for the
pun)?  any other comments, suggestions?


--
 Hal Daume III                                   | hdaume@isi.edu
 "Arrest this man, he talks in maths."           | www.isi.edu/~hdaume