concrete functor and monad transformers
#33*> must be defined in instances to prevent space leak with forever
forever, used with StateT s IO or ReaderT r IO has a space leak, as documented here: https://ghc.haskell.org/trac/ghc/ticket/12804
The space leak goes away by giving this implementation of *> for ReaderT:
f *> v = ReaderT $ \ r -> runReaderT f r *> runReaderT v r
Since forever could hardly do anything better than it already does I believe the bug is indeed with transformers and this change should be made (and the corresponding changes for the other transformers).
- description updated
- status set to closed
Done, and similarly for (<$), (<*) and (>>).
- status set to open
I got a space leak for the same reason, only with the strict
StateT
. As I see there are still no specialized operators forStateT
.- status set to closed
defined (*>) = (>>) for both StateT's
- status set to open
Unfortunately this is not fully fixed. Consider the following test program:
{-# LANGUAGE LambdaCase #-} {-# OPTIONS_GHC -Wall #-} module Main where import Control.Monad import Control.Monad.Trans.Cont import Control.Monad.Trans.Except import Control.Monad.Trans.Identity import Control.Monad.Trans.Maybe import Control.Monad.Trans.Reader import Control.Monad.Trans.Select import System.Environment import qualified Control.Monad.Trans.State.Lazy as LS import qualified Control.Monad.Trans.State.Strict as SS main :: IO () main = getArgs >>= \case ["cont"] -> void f_cont ["except"] -> void f_except ["identity"] -> void f_identity ["maybe"] -> void f_maybe ["reader"] -> void f_reader ["select"] -> void f_select ["lazy_state"] -> void f_lazy_state ["strict_state"] -> void f_strict_state args -> putStrLn $ "Invalid arguments: " ++ show args ---------- {-# NOINLINE worker #-} worker :: Monad m => m a worker = forever $ return () {-# NOINLINE f_cont #-} f_cont :: Monad m => m a f_cont = runContT worker id {-# NOINLINE f_except #-} f_except :: Monad m => m (Either () a) f_except = runExceptT worker {-# NOINLINE f_identity #-} f_identity :: Monad m => m (Either () a) f_identity = runIdentityT worker {-# NOINLINE f_maybe #-} f_maybe :: Monad m => m (Maybe a) f_maybe = runMaybeT worker {-# NOINLINE f_reader #-} f_reader :: Monad m => m a f_reader = runReaderT worker () {-# NOINLINE f_select #-} f_select :: Monad m => m a f_select = runSelectT worker (\_ -> return ()) {-# NOINLINE f_lazy_state #-} f_lazy_state :: Monad m => m (a, ()) f_lazy_state = LS.runStateT worker () {-# NOINLINE f_strict_state #-} f_strict_state :: Monad m => m (a, ()) f_strict_state = SS.runStateT worker ()
and http://rybczak.net/files/transformers-space-leak-fix.patch.
Here are the results of the test run with current transformers (GHC 8.0.2) and patched one with and without optimization:
current, -O0 cont - leaks except - leaks identity - leaks maybe - leaks reader - ok select - leaks lazy_state - ok strict_state - ok current, -O cont - leaks except - leaks identity - leaks maybe - leaks reader - ok select - leaks lazy_state - ok strict_state - ok current, -O, no NOINLINE pragmas cont - exits with <<loop>> except - leaks (!) identity - ok maybe - ok reader - ok select - ok lazy_state - ok strict_state - ok patched, -O0 and -O cont - ok except - ok identity - ok maybe - ok reader - ok select - ok lazy_state - ok strict_state - ok
The biggest problem is that currently ExceptT always leaks space, but for other monad transformers we rely on GHC specializing the code to a concrete monad and applying further optimizations (which NOINLINE prevents as it simulates a typical behaviour when these functions are defined in multiple modules/libraries), which is quite fragile and also confusing to the user.
- status set to closed
applied, thanks