Home > Blockchain >  Constructing an infinite, lazy Monad value recursively
Constructing an infinite, lazy Monad value recursively

Time:12-28

As an exercise, I tried to construct an infinite, lazy list, recursively, inside a monad.

Basically, the equivalent of nat, but monadic, monat:

nat :: Int -> [Int]
nat n = n : nat (n 1)

monat :: Monad m => Int -> m [Int]
monat n = return n >>= \n -> (n:) <$> monat (n 1)

But monat isn't really lazily evaluated: it's impossible to get the first elements without computing it all:

ghci> take 5 $ nat 0
[0,1,2,3,4]

ghci> take 5 <$> (monat 0) :: Maybe [Int]
... never ending computation ...

I suspect the issue must lie in fmap or >>=, since they're the only extra functions I'm using there but not in nat. However I looked at their implementation and I don't see why or where laziness is broken:

instance  Functor Maybe  where
    fmap _ Nothing       = Nothing
    fmap f (Just a)      = Just (f a)

instance  Monad Maybe  where
    (Just x) >>= k      = k x
    Nothing  >>= _      = Nothing
  • Can you help me understand why the list in monat isn't computed lazily?

  • Is there any way to understand why the computation doesn't end, besides analyzing the code? For instance, can I do something like pausing evaluation in GHCi and look at the few latest stack trace frames in order to understand where it's looping? How?

CodePudding user response:

The reason for the infinite loop is simply that fmap/<$> for the Maybe functor needs to know whether the input is Nothing or Just. Because it only applies the function if there is a Just otherwise it will propagate the Nothing.

Your function monat doesn't specify whether the result should be Nothing or Just. Both are theoretically fixed points of the equation with which you have defined monat. More practically, you can think of it as monat postponing the choice between Nothing and Just every recursive call. In that way, it never actually makes the choice.

One way to resolve this problem while still keeping the monadic interface is to use the ListT monad transformer. This monad transformer is like a normal list but interspersed with a monad. You can define monat like this:

import Control.Monad.Trans.Class (lift)
import qualified ListT -- from the list-t package
import ListT (ListT)

monat :: Monad m => Int -> ListT m Int
monat n = lift (return n) >>= \m -> cons m (monat (m   1))

And you can use that like this:

ghci> ListT.toList (ListT.take 5 (monat 0) :: ListT Maybe Int)
Just [0,1,2,3,4]
  • Related