packages feed

monad-effect-0.1.0.0: test/Main.hs

{-# OPTIONS_GHC -fconstraint-solver-iterations=100 -Wno-partial-type-signatures -fmax-worker-args=16 #-}
{-# LANGUAGE DataKinds, GADTs, PartialTypeSignatures #-}
module Main where

import Control.Monad.Effect as ME
import Control.Monad
import Criterion.Main
import Data.TypeList
import Data.TypeList.FData as ME
import Module.RS as ME
import Module.RS.QQ
import qualified Control.Monad.State as S

-- The test result shows that for FList there is around (k+1) ns overhead for fetching the k-th state inside the effect system
-- for typical applications with 8 modules,
-- the expected monad access & bind is around 5 ns, which is around 200000000 access & binds per second.
--
-- This means the overhead is completely neglegible in actual applications with effects.
--
-- But of course, in a very very tight loop, you should avoid using Eff, use a single layer of StateT or
-- embed your computation as pure functions.
--
--
-- For FData, the overhead can be eliminated and GHC optimize it very very well
-- even without any optimizations, the speed is 10 times faster than FList
--
-- with -O2 -flate-dmd-anal it can optimize to a minimal hand-written fast loop!

programMonadEffect :: ME.In (ME.SModule Int) mods => ME.EffT mods ME.NoError ME.Identity Int
programMonadEffect = do
    x <- ME.getS @Int
    if x == 0
        then pure x
        else do
            ME.putS (x - 1)
            programMonadEffect

programMonadEffectTH' :: ME.In Countdown mods => ME.EffT mods ME.NoError ME.Identity Int
programMonadEffectTH' = do
    x <- ME.getsModule countdown
    if x == 0
        then pure x
        else do
            ME.putModule (CountdownState (x - 1))
            programMonadEffectTH'

countdownMonadEffect :: Int -> (Int, _)
countdownMonadEffect n =
    ME.runIdentity $ ME.runEffTNoError (ME.FData1 ME.SRead) (ME.FData1 $ ME.SState n) programMonadEffect

countdownMonadEffectDeep7 :: Int -> (Int, _)
countdownMonadEffectDeep7 n = ME.runIdentity $
    ME.runEffTNoError
      (ME.FData7 readUnit readUnit readUnit SRead readUnit readUnit readUnit) --readUnit SRead readUnit readUnit readUnit readUnit readUnit)
      (ME.FData7 rStateUnit rStateUnit rStateUnit (ME.SState n) rStateUnit rStateUnit rStateUnit) --rStateUnit rStateUnit rStateUnit)
        programMonadEffect
      where readUnit = ME.RRead ()
            rStateUnit = ME.RState

countdownMonadEffectDeep11 :: Int -> (Int, _)
countdownMonadEffectDeep11 n = ME.runIdentity $
    ME.runEffTNoError
      (ME.FData15 readUnit readUnit SRead readUnit readUnit readUnit readUnit readUnit readUnit readUnit readUnit readUnit readUnit readUnit readUnit)
      (ME.FData15 rStateUnit rStateUnit (ME.SState n) rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit rStateUnit)
        programMonadEffect
      where readUnit = ME.RRead ()
            rStateUnit = ME.RState

[makeRSModule|
Countdown
  State countdown :: !Int
|]

countdownMonadEffectTH :: Int -> (Int, _)
countdownMonadEffectTH n = ME.runIdentity $
    ME.runEffTNoError
      (ME.FData1 CountdownRead)
      (ME.FData1 $ CountdownState n)
        programMonadEffectTH'

testEffStateFPoly :: _ => EffT' flist '[RModule (), SModule Int, SModule Bool] NoError IO ()
testEffStateFPoly = do
  x <- getS @Int
  modifyS not
  when (x < 1_000_000) $ putS (x + 1) >> testEffStateFPoly

testMtlState :: S.StateT ((), Int, Bool) IO ()
testMtlState = do
  x <- S.gets (\(_, x, _) -> x)
  S.modify (\(_, x', b) -> ((), x', not b))
  when (x < 1_000_000) $ do
      S.modify (\(_, _, b) -> ((), x + 1, b))
      testMtlState

testEffStateAs :: Eff '[SModule Int] NoError ()
testEffStateAs = asStateT @Int $ S.mapStateT liftIO loop
  where loop = do
          x <- S.get
          when (x < 1_000_000) $ do
              S.put (x + 1)
              loop

testEffEmbed :: _ => EffT' flist '[RModule (), SModule Int, SModule Bool] NoError IO ()
testEffEmbed = do
  x <- embedMods @'[SModule Int] $ do
    x <- getS @Int
    putS (x + 1)
    return x
  when (x < 1_000_000) $ do
      modifyS not
      testEffEmbed

main :: IO ()
main = do
  putStrLn "Some sanity checks"

  let testFList :: FList Maybe '[Int]
      testFList = Just 1 :** FNil

      unConsHead = fst . unConsF $ testFList

  print unConsHead

  print $ getIn @_ @Int testFList

  _ <- runEffTNoError
        (SRead :** FNil)
        (SState (0 :: Int) :** FNil)
        ( do
            liftIO $ putStrLn "Running initial state test"
            x <- getS @Int
            liftIO $ putStrLn $ "Initial state get: " ++ show x
            putS (x + 1)
            liftIO $ putStrLn "State updated"
            y <- getS @Int
            liftIO $ putStrLn $ "State after update: " ++ show y
            if y == x + 1
              then liftIO $ putStrLn "State updated correctly"
              else liftIO $ putStrLn "State updated incorrectly"
        )

  defaultMain
    [ bgroup "Bind"
      [ bench "bind" $ whnfIO $ runEffTNoError
          (SRead :** FNil)
          (SState (0 :: Int) :** FNil)
          ( do
              x <- getS @Int
              putS (x + 1)
              y <- getS @Int
              if y == x + 1
                then return ()
                else liftIO $ putStrLn "State updated incorrectly"
          )
      ]
    , bgroup "Countdown"
      [ bench "Monad Effect" $ nf countdownMonadEffect 1_000_000
      , bench "Monad Effect Deep 7" $ nf countdownMonadEffectDeep7 1_000_000
      , bench "Monad Effect Deep 11" $ nf countdownMonadEffectDeep11 1_000_000
      , bench "Monad Effect TH" $ nf (fst . countdownMonadEffectTH) 1_000_000
      ]
    , bgroup "State Effect Eff"
      [ bench "FList" $ whnfIO $ runEffTNoError
          (RRead () `FCons` SRead `FCons` SRead `FCons` FNil)
          (RState `FCons` SState 0 `FCons` SState False `FCons` FNil)
          testEffStateFPoly
      , bench "FData" $ whnfIO $ runEffTNoError
          (FData3 (RRead ()) SRead SRead)
          (FData3 RState (SState 0) (SState False))
          testEffStateFPoly
      ]
    , bgroup "Embed Eff functionality"
      [ bench "FList" $ whnfIO $ runEffTNoError
          (RRead () `FCons` SRead `FCons` SRead `FCons` FNil)
          (RState `FCons` SState 0 `FCons` SState False `FCons` FNil)
          testEffEmbed
      , bench "FData" $ whnfIO $ runEffTNoError
          (FData3 (RRead ()) SRead SRead)
          (FData3 RState (SState 0) (SState False))
          testEffEmbed
      ]
    , bgroup "Mtl State"
      [ bench "StateT" $ whnfIO $ S.runStateT testMtlState ((), 0, False)
      ]
    , bgroup "Embed tight computation in Eff"
      [ bench "EmbedEff" $ whnfIO $ runEffTNoError FNil FNil $ liftIO $ S.runStateT testMtlState ((), 0, False)
      ]
    , bgroup "State Effect As StateT"
      [ bench "Eff" $ whnfIO $ runEffTNoError
          (FData1 SRead)
          (FData1 $ SState 0)
          testEffStateAs
      ]
    ]