Pure syntax transformation affects performance.
Hi! Let's consider the following code (compiled with -O2, mtl and criterion needed):
{-# LANGUAGE FlexibleContexts #-}
module Main where
import Prelude
import Criterion.Main
import Control.Monad.State.Strict
import Control.Monad.Identity
repeatM :: Monad m => m a -> Int -> m ()
repeatM f = go where
go 0 = pure ()
go i = f >> go (i - 1)
{-# INLINE repeatM #-}
incState :: MonadState Int m => m ()
incState = modify' (1+) ; {-# INLINE incState #-}
test1, test2 :: Int -> Int
test1 = \n -> (runIdentity . flip evalStateT 0 . (\a -> repeatM incState a >> get)) n ; {-# INLINE test1 #-}
test2 = \n -> runIdentity . flip evalStateT 0 $ repeatM incState n >> get ; {-# INLINE test2 #-}
main :: IO ()
main = do
defaultMain
[ bgroup "monad transformers overhead"
[ bench "test1" $ nf test1 100000000
, bench "test2" $ nf test2 100000000
]
]
Functions test1 and test2 differ only syntactically and this difference should not affect GHC's inliner, because their implementations use fully saturated calls. The generated core for test1 and test2 is practically identical (there is an additional alias created for test1: test1 = lvl1_rhor 'cast' ...).
The problem is that test1 runs **3 times faster** than test2.
As a side note - if we add more state transformers to test1, it optimizes them all away, while test2 runs slower with each new transformer applied.