memory leak when using "forever"

Hello all, can you please explain why does this simple program leak memory. But, if I replace loop2 with loop1 (that is: without using "forever"), then it does not leak. Is this a problem in "forever" implementation or am I misusing this function? In the sources of base: http://hackage.haskell.org/package/base-4.9.0.0/docs/src/Control.Monad.html#... ... there is some mention of memory leak prevention, but it looks like something is not right in this case. import Control.Concurrent import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.Reader import Control.Monad.Trans.State main :: IO () main = do --let loop1 = (liftIO $ threadDelay 1) >> loop1 let loop2 = forever (liftIO $ threadDelay 1) _ <- runStateT (runReaderT loop2 'a') 'b' return () regards, Zoran

On Tue, Oct 04, 2016 at 01:51:04PM +0200, Zoran Bosnjak wrote:
can you please explain why does this simple program leak memory.
But, if I replace loop2 with loop1 (that is: without using "forever"), then it does not leak.
import Control.Concurrent import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.Reader import Control.Monad.Trans.State
main :: IO () main = do --let loop1 = (liftIO $ threadDelay 1) >> loop1 let loop2 = forever (liftIO $ threadDelay 1)
_ <- runStateT (runReaderT loop2 'a') 'b' return ()
My results below. Looks like there's something wrong with *> for ReaderT and StateT. I get a stack overflow even with just _ <- runStateT loop () and just _ <- runReaderT loop () 8<--- import Control.Concurrent import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.Reader import Control.Monad.Trans.State import Control.Applicative -- Fine forever0 a = let a' = a >> a' in a' -- Stack overflow forever1 a = let a' = a *> a' in a' -- Fine forever2 a = a >> forever2 a -- Stack overflow forever3 a = a *> forever3 a main :: IO () main = do let loop = forever3 (liftIO $ return ()) _ <- runStateT (runReaderT loop ()) () return ()

On Tue, Oct 04, 2016 at 06:19:52PM +0100, Tom Ellis wrote:
On Tue, Oct 04, 2016 at 01:51:04PM +0200, Zoran Bosnjak wrote:
can you please explain why does this simple program leak memory.
But, if I replace loop2 with loop1 (that is: without using "forever"), then it does not leak.
import Control.Concurrent import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.Reader import Control.Monad.Trans.State
main :: IO () main = do --let loop1 = (liftIO $ threadDelay 1) >> loop1 let loop2 = forever (liftIO $ threadDelay 1)
_ <- runStateT (runReaderT loop2 'a') 'b' return ()
My results below. Looks like there's something wrong with *> for ReaderT and StateT.
This seems to be how it executes let loop = return () *> loop in loop in runReaderT loop () let loop = return () *> loop in loop in loop () let loop = (id <$ return ()) <*> loop in loop in loop () -- <*> for ReaderT in terms of <*> for m let loop = \r -> (id <$ return ()) r <*> loop r in loop in loop () let loop = \r -> (id <$ return ()) r <*> loop r in loop in (id <$ return ()) () <*> loop () let loop = \r -> (id <$ return ()) r <*> loop r in loop in ((fmap . const) id (return ())) () (loop ()) let loop = \r -> (id <$ return ()) r <*> loop r in loop in (fmap (const id) (return ())) () <*> loop () -- fmap for ReaderT m in terms of fmap for m let loop = \r -> (id <$ return ()) r <*> loop r in loop in (fmap (const id) . (return ())) () <*> loop () let loop = \r -> (id <$ return ()) r <*> loop r in loop in (\x -> fmap (const id) (return () x)) () <*> loop () let loop = \r -> (id <$ return ()) r <*> loop r in loop in fmap (const id) (return () ()) <*> loop () -- return for ReaderT in terms of return for m let loop = \r -> (id <$ return ()) r <*> loop r in loop in fmap (const id) (return ()) <*> loop () which then in IO I think becomes fmap (const id ()) (loop ()) so each time round the loop we add a redundant fmap (const id ()) on the front. Oh dear. Something needs fixing. I'm not sure what. We don't see the space leak in the Identity Applictave because fmap (const id) (return ()) <*> loop () is const id (return ()) (loop ()) which evaluates as (\x y -> x) id (return ()) (loop ()) id (loop ()) loop () Anyone who is at Haskell eXchange on Thursday and who is interested in working out how the above code executes can come to my talk! Tom

On Tue, Oct 04, 2016 at 06:56:48PM +0100, Tom Ellis wrote:
On Tue, Oct 04, 2016 at 06:19:52PM +0100, Tom Ellis wrote:
My results below. Looks like there's something wrong with *> for ReaderT and StateT.
This seems to be how it executes
let loop = return () *> loop in loop in runReaderT loop () [...]
I'm adding an evaluation trace of 'forever' in terms of '>>' to this discussion because the issue has arisen in https://www.reddit.com/r/haskell/comments/5axh2j/two_methods_of_using_statet... let loop = return () >> loop in loop in runReaderT loop () -- Un-newtype let loop = return () >> loop in loop in loop () -- Un-newtype let loop = return () >> loop in loop in loop () -- Definition of >> let loop = return () >>= (\_ -> loop) in loop in loop () -- >>= for Reader in terms of >>= for m let loop = \r -> (return () r) >>= (\_ -> loop r) in loop in loop () let loop = \r -> (return () r) >>= (\_ -> loop r) in loop in (\r -> (return () r) >>= (\_ -> loop r)) () let loop = \r -> (return () r) >>= (\_ -> loop r) in loop in (return () ()) >>= (\_ -> loop ()) -- return () for ReaderT in terms of return () for m let loop = \r -> (return () r) >>= (\_ -> loop r) in loop in return () >>= (\_ -> loop ()) So for m = IO evaluation proceeds without a space leak. We evaluate 'return ()' and then proceed to evaluate 'loop ()' without creating any new thunk. 'forever' needs urgent fixing! Tom
participants (2)
-
Tom Ellis
-
Zoran Bosnjak