Космическая утечка в трубах с RWST
Анализ памяти из следующей программы показывает, что функции noleak работают в постоянной памяти, а функция утечки теряет память линейным образом. dflemstr указал, что это может быть связано с RWST, вызывающим бесконечную цепочку распределений. Так ли это и какие существуют другие решения? Мне действительно не нужна монада писателей.
Окружающая среда:
GHC 7.8.3 на ARCH 64 бит
ghc Pipe.hs -o Pipe -prof
import Control.Concurrent (threadDelay)
import Control.Monad (forever)
import Pipes
import Control.Monad.Trans.RWS.Strict
main = leak
effectLeak :: Effect (RWST () () () IO) ()
effectLeak =
(forever $ do
liftIO . threadDelay $ 10000 * 1
yield "Space") >->
(forever $ do
text <- await
yield $ text ++ (" leak" :: String)) >->
(forever $ do
text <- await
liftIO . print $ text
)
effectNoleak :: Effect IO ()
effectNoleak =
(forever $ do
lift . threadDelay $ 10000 * 1
yield "Space") >->
(forever $ do
text <- await
yield $ text ++ (" leak" :: String)) >->
(forever $ do
text <- await
lift . print $ text
)
leak = (\e -> runRWST e () ()) . runEffect $ effectLeak
noleak = runEffect $ effectNoleak
Ответы
Ответ 1
Кажется, что Writer
часть RWST
на самом деле является виновником:
instance (Monoid w, Monad m) => Monad (RWST r w s m) where
return a = RWST $ \ _ s -> return (a, s, mempty)
m >>= k = RWST $ \ r s -> do
(a, s', w) <- runRWST m r s
(b, s'',w') <- runRWST (k a) r s'
return (b, s'', w `mappend` w') -- mappend
fail msg = RWST $ \ _ _ -> fail msg
Как вы можете видеть, писатель использует простой mappend
. Поскольку (,,)
не является строгим в своих аргументах, w `mappend` w'
строит серию thunks, даже жесткий <Monoid
экземпляр ()
довольно тривиален:
instance Monoid () where
-- Should it be strict?
mempty = ()
_ `mappend` _ = ()
mconcat _ = ()
Чтобы исправить это, вам нужно добавить строгость к w `mappend` w'
в кортеже:
let wt = w `mappend` w'
wt `seq` return (b, s'', wt)
Однако, если вам не нужен Writer
, вы можете просто использовать ReaderT r (StateT st m)
вместо:
import Control.Monad.Trans.Reader
import Control.Monad.Trans.State.Strict
type RST r st m = ReaderT r (StateT st m)
runRST :: Monad m => RST r st m a -> r -> st -> m (a,st)
runRST rst r st = flip runStateT st . flip runReaderT r $ rst
Однако, учитывая, что это заставит вас lift
вычислять правильную монаду, вы можете вместо этого использовать mtl
пакет. Код останется прежним, но импорт будет в этом случае
import Control.Monad.Reader
import Control.Monad.State.Strict
Ответ 2
Зета правы, и утечка пространства происходит из-за WriterT
. WriterT
и RWST
(как "строгие", так и ленивые версии) всегда просачиваются независимо от того, какой моноид вы используете.
Я написал более длинное объяснение этого здесь, но вот резюме: единственный способ не пропустить пространство - имитировать WriterT
с помощью StateT
monad где tell
моделируется с использованием строгой put
, например:
newtype WriterT w m a = WriterT { unWriterT :: w -> m (a, w) }
instance (Monad m, Monoid w) => Monad (WriterT w m) where
return a = WriterT $ \w -> return (a, w)
m >>= f = WriterT $ \w -> do
(a, w') <- unWriterT m w
unWriterT (f a) w'
runWriterT :: (Monoid w) => WriterT w m a -> m (a, w)
runWriterT m = unWriterT m mempty
tell :: (Monad m, Monoid w) => w -> WriterT w m ()
tell w = WriterT $ \w' ->
let wt = w `mappend` w'
in wt `seq` return ((), wt)
Это в основном эквивалентно:
type WriterT = StateT
runWriterT m = runStateT m mempty
tell w = do
w' <- get
put $! mappend w w'