It's easy to create an infinite loop in Haskell.
main = mapM_ print [1..]
-- 1
-- 2
-- 3
-- ...
Of course, you can create infinite loops in other languages as well with while(1)
, but can you break
and escape from the infinite loop as you do in an imperative programming language?
Here's how we can do it in Haskell.
import Control.Monad.Cont
main :: IO ()
main = do
putStrLn "Start"
withBreak $ \break ->
forM_ [1..] $ \i -> do
liftIO . putStrLn $ "Loop: " ++ show i
when (i == 5) $ break ()
putStrLn "End"
where
withBreak = flip runContT pure . callCC
and run it.
$ runhaskell Main.hs
Start
Loop: 1
Loop: 2
Loop: 3
Loop: 4
Loop: 5
End
Most important part is here.
withBreak $ \break ->
forM_ [1..] $ \i -> do
liftIO . putStrLn $ "Loop: " ++ show i
when (i == 5) $ break ()
Here is the same code in C
.
int i = 0;
while (1) {
i++;
printf("Loop: %d\n", i);
if (i == 5) break;
}
I'll explain withBreak
later in this article. forM_
is implemented simply as a function with flipped mapM_
arguments.
forM_ :: (Monad m) => [a] -> (a -> m b) -> m ()
forM_ = flip mapM_
liftIO
is a function for using IO actions in withBreak
, and when
executes the second argument only when the condition of the first argument is satisfied.
The list passed to forM_
is [1..]
, which is indeed an infinite list, but as you can see in the execution result, the process ends with break
.
First, let's look at the implementation of withBreak
.
withBreak = flip runContT pure . callCC
The two most important ones are runContT
and callCC
.
runContT :: ContT r m a -> (a -> m r) -> m r
callCC :: ((a -> ContT r m b) -> ContT r m a) -> ContT r m a
ContT
is called a continuation monad, which is a monad that can handle subsequent calculations.
In fact, the break
in withBreak $ \break -> ...
just throws away the subsequent calculation.
Let's break down withBreak
further to understand what's going on here. First of all, ContT
is defined like this, which is just isomorphic to (a -> m r) -> m r
.
newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r }
The implementation of callCC
is
callCC :: ((a -> ContT r m b) -> ContT r m a) -> ContT r m a
callCC f = ContT $ \ c -> runContT (f (\ x -> ContT $ \ _ -> c x)) c
, but if you try hard to rewrite it using isomorphic, it becomes
callCC :: ((a -> (b -> m r) -> m r) -> (a -> m r) -> m r) -> (a -> m r) -> m r
callCC f c = f (\x _ -> c x) c
. The type has become more complex, but the implementation has become very easy. With this, withBreak
will be
withBreak :: ((a -> (b -> m r) -> m r) -> (a -> m r) -> m r) -> m r
withBreak f = f (\x _ -> pure x) pure
. The correspondence to f
in the first program is \break -> ...
. Finally, it turns out that break
is
break :: a -> (b -> m r) -> m r
break = \x _ -> pure x
.
Let's go back to the continuation monad again. The implementation of the ContT
monad is as follows.
instance Monad (ContT r m) where
return x = ContT ($ x)
m >>= k = ContT $ \c -> runContT m (\x -> runContT (k x) c)
Let's rewrite >>=
according to isomorphic of ContT
.
(>>=) :: ((a -> m r) -> m r) -> (a -> ((b -> m r) -> m r)) -> (b -> m r) -> m r
m >>= k = \c -> m (\x -> (k x) c)
Again, the type is more complex, but the implementation is easier to read. If you look at the implementation, you'll see that it evaluates k
and then uses that value to evaluate m
.
Now that we are ready, let's consider the following expression to see the behavior of break
.
actionA >> break () >> actionB
First, expand the >>
on the left side.
(\c -> actionA (\_ -> break () c)) >> actionB
Next, expand break
.
(\c -> actionA (\_ -> pure ()) >> actionB
At this point, we can see that the subsequent calculation c
has disappeared.
Expand the last remaining >>
.
\c -> actionA (\_ -> pure ())
actionB
has disappeared nicely.
Finally, let's go back to the first program.
withBreak $ \break ->
forM_ [1..] $ \i -> do
liftIO . putStrLn $ "Loop: " ++ show i
when (i == 5) $ break ()
As we've already seen, once i
reached 5 and break
was evaluated, the infinite calculations that followed were discarded.
Top comments (1)
Interesting, I once needed to break out of an possible infinite effectfull loop so I've write an apomorphism based on the free monad