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 for StateT.

    • 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