Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Constructing an infinite, lazy Monad value recursively

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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?

>Solution :

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]
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading