i've noticed in a lot of my imperative-looking monadic code, i have lots of stuff that looks like:
... = 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:
... = 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 = Break { runBreak :: m (Maybe b, a) }
instance MonadTrans (Break b) where lift x = Break $ do a <- x; return (Nothing, a)
instance Monad m => Monad (Break b m) where return a = Break $ return (Nothing, a) b >>= k = Break $ runBreak b >>= \ (broken, a) -> case broken of Nothing -> runBreak (k a) Just br -> return (Just br, undefined)
breakable :: Monad m => Break a m a -> m a breakable b = runBreak b >>= \ (broken, a) -> case broken of Nothing -> return a Just br -> return br
break :: Monad m => b -> Break b m a break b = 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 => m Bool -> b -> Break b m () breaksTo k b = lift k >>= \x -> if x then break b else return ()
using this, you can write stuff which looks like:
test1 :: IO [String] test1 = breakable $ do lift $ putStrLn "Enter something, or nothing to quit:" l <- lift $ getLine when (null l) $ break [] rest <- lift test1 return (l:rest)
test2 :: IO () test2 = breakable $ repeatM $ do x <- lift $ getLine >>= passThrough putStrLn when (null x) $ break ()
test3 :: Handle -> IO [String] test3 h = breakable $ do hIsEOF h `breaksTo` [] lift $ do l <- hGetLine h rest <- test3 h return (l:rest)
where passThrough and repeatM are:
repeatM :: Monad m => m () -> m () repeatM x = x >> repeatM x
passThrough :: Monad m => (a -> m b) -> a -> m a passThrough f a = 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