on master help sign in
Add support for GADTs
commit005c8c42532a32e8ec619485399dcbf84a0e761f authorAndrzej Rybczak <andrzej@rybczak.net> dateTue 15 Jun 2021, 20:59 (5 years ago) parentd261519
21 files changed+706−375
bench/Countdown.hs+2−2 bench/Main.hs+36−29 effectful.cabal+6−11 examples/FileSystem.hs+34−32 src/Effectful.hs−34 src/Effectful/Class/Reader.hs+1−1 src/Effectful/Class/State.hs+5−2 src/Effectful/Class/Writer.hs+1−1 src/Effectful/Error.hs+14−13 src/Effectful/Internal/Has.hs → src/Effectful/Internal/Effect.hs+13−13 src/Effectful/Internal/Env.hs+247−60 src/Effectful/Internal/Monad.hs+50−58 src/Effectful/Interpreter.hs+109 src/Effectful/Monad.hs+14 src/Effectful/Reader.hs+11−6 src/Effectful/Resource.hs+7−6 src/Effectful/State.hs+24−16 src/Effectful/State/Dynamic.hs+52−56 src/Effectful/State/MVar.hs+23−18 src/Effectful/Writer.hs+21−11 tests/Main.hs+36−6
bench/Countdown.hs+2−2
@@ -9,11 +9,11 @@ import qualified Control.Effect as L
99#endif
1010
1111-- effectful
12−import qualified Effectful as E
12+import qualified Effectful.Monad as E
1313import qualified Effectful.Reader as E
1414import qualified Effectful.State as E
15−import qualified Effectful.State.MVar as ME
1615import qualified Effectful.State.Dynamic as DE
16+import qualified Effectful.State.MVar as ME
1717
1818-- freer-simple
1919#ifdef VERSION_freer_simple
bench/Main.hs+36−29
@@ -1,54 +1,61 @@
11{-# LANGUAGE CPP #-}
22module Main (main) where
33
4+#ifdef VERSION_criterion
5+import Criterion
6+import Criterion.Main
7+#endif
8+
9+#ifdef VERSION_tasty_bench
410import Test.Tasty.Bench
11+#endif
512
613import Countdown
714
815main :: IO ()
916main = defaultMain
10− [ bgroup "countdown"
11− [ bgroup "reference" $ mkCountdown nf countdownRef
12− , bgroup "shallow"
13− [ bgroup "efectful (SP)" $ mkCountdown nfAppIO countdownEffectfulStatic
14− , bgroup "efectful (SM)" $ mkCountdown nfAppIO countdownEffectfulMVar
15− , bgroup "efectful (DP)" $ mkCountdown nfAppIO countdownEffectfulDynPure
16− , bgroup "efectful (DM)" $ mkCountdown nfAppIO countdownEffectfulDynMVar
17+ [ bgroup "countdown" $ map countdown [1000, 10000]
18+ ]
19+
20+countdown :: Integer -> Benchmark
21+countdown n = bgroup (show n)
22+ [ bgroup "shallow"
23+ [ bench "reference" $ nf countdownRef n
24+ , bench "effectful (PS)" $ nfAppIO countdownEffectfulStatic n
25+ , bench "effectful (PD)" $ nfAppIO countdownEffectfulDynPure n
26+ , bench "effectful (MS)" $ nfAppIO countdownEffectfulMVar n
27+ , bench "effectful (MD)" $ nfAppIO countdownEffectfulDynMVar n
1728#ifdef VERSION_freer_simple
18− , bgroup "freer-simple" $ mkCountdown nf countdownFreerSimple
29+ , bench "freer-simple" $ nf countdownFreerSimple n
1930#endif
2031#ifdef VERSION_eff
21− , bgroup "eff" $ mkCountdown nf countdownEff
32+ , bench "eff" $ nf countdownEff n
2233#endif
23− , bgroup "mtl" $ mkCountdown nf countdownMtl
34+ , bench "mtl" $ nf countdownMtl n
2435#ifdef VERSION_fused_effects
25− , bgroup "fused-effects" $ mkCountdown nf countdownFusedEffects
36+ , bench "fused-effects" $ nf countdownFusedEffects n
2637#endif
2738#ifdef VERSION_polysemy
28− , bgroup "polysemy" $ mkCountdown nf countdownPolysemy
29−#endif
30− ]
31− , bgroup "deep"
32− [ bgroup "efectful (SP)" $ mkCountdown nfAppIO countdownEffectfulStaticDeep
33− , bgroup "efectful (SM)" $ mkCountdown nfAppIO countdownEffectfulMVarDeep
34− , bgroup "efectful (DP)" $ mkCountdown nfAppIO countdownEffectfulDynPureDeep
35− , bgroup "efectful (DM)" $ mkCountdown nfAppIO countdownEffectfulDynMVarDeep
36−#ifdef VERSION_eff
37− , bgroup "eff" $ mkCountdown nf countdownEffDeep
39+ , bench "polysemy" $ nf countdownPolysemy n
3840#endif
41+ ]
42+ , bgroup "deep"
43+ [ bench "effectful (PS)" $ nfAppIO countdownEffectfulStaticDeep n
44+ , bench "effectful (PD)" $ nfAppIO countdownEffectfulDynPureDeep n
45+ , bench "effectful (MS)" $ nfAppIO countdownEffectfulMVarDeep n
46+ , bench "effectful (MD)" $ nfAppIO countdownEffectfulDynMVarDeep n
3947#ifdef VERSION_freer_simple
40− , bgroup "freer-simple" $ mkCountdown nf countdownFreerSimpleDeep
48+ , bench "freer-simple" $ nf countdownFreerSimpleDeep n
49+#endif
50+#ifdef VERSION_eff
51+ , bench "eff" $ nf countdownEffDeep n
4152#endif
4253#ifdef VERSION_polysemy
43− , bgroup "polysemy" $ mkCountdown nf countdownPolysemyDeep
54+ , bench "polysemy" $ nf countdownPolysemyDeep n
4455#endif
45− , bgroup "mtl" $ mkCountdown nf countdownMtlDeep
56+ , bench "mtl" $ nf countdownMtlDeep n
4657#ifdef VERSION_fused_effects
47− , bgroup "fused-effects" $ mkCountdown nf countdownFusedEffectsDeep
58+ , bench "fused-effects" $ nf countdownFusedEffectsDeep n
4859#endif
49− ]
5060 ]
5161 ]
52−
53−mkCountdown :: (nf -> Integer -> Benchmarkable) -> nf -> [Benchmark]
54−mkCountdown f g = map (\n -> bench (show n) $ f g n) [100, 1000, 10000]
effectful.cabal+6−11
@@ -15,8 +15,7 @@ description: A simple, performant extensible effects library with seamless
1515extra-source-files: CHANGELOG.md
1616 README.md
1717
18−tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.4
19− || ==9.0.1 || ==9.2.0.20210422
18+tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4 || ==9.0.1 || ==9.2.0.20210422
2019
2120bug-reports: https://github.com/arybczak/effectful/issues
2221source-repository head
@@ -31,14 +30,10 @@ common language
3130 default-extensions: BangPatterns
3231 ConstraintKinds
3332 DataKinds
34− DefaultSignatures
35− DeriveFunctor
3633 FlexibleContexts
3734 FlexibleInstances
3835 GADTs
39− GeneralizedNewtypeDeriving
4036 LambdaCase
41− NamedFieldPuns
4237 MultiParamTypeClasses
4338 RankNTypes
4439 RecordWildCards
@@ -47,14 +42,13 @@ common language
4742 StrictData
4843 TupleSections
4944 TypeApplications
50− TypeInType
5145 TypeFamilies
5246 TypeOperators
5347
5448library
5549 import: language
5650
57− build-depends: base <5
51+ build-depends: base >= 4.12 && <5
5852 , containers
5953 , exceptions
6054 , ghc-prim
@@ -67,15 +61,16 @@ library
6761
6862 hs-source-dirs: src
6963
70− exposed-modules: Effectful
71− Effectful.Class.Reader
64+ exposed-modules: Effectful.Class.Reader
7265 Effectful.Class.State
7366 Effectful.Class.Writer
7467 Effectful.Error
68+ Effectful.Internal.Effect
7569 Effectful.Internal.Env
76− Effectful.Internal.Has
7770 Effectful.Internal.Monad
7871 Effectful.Internal.Utils
72+ Effectful.Interpreter
73+ Effectful.Monad
7974 Effectful.Reader
8075 Effectful.Resource
8176 Effectful.State
examples/FileSystem.hs+34−32
@@ -1,5 +1,5 @@
11module FileSystem
2− ( FileSystem
2+ ( FileSystem(..)
33 , FsError(..)
44 , readFile
55 , writeFile
@@ -7,23 +7,24 @@ module FileSystem
77 , runFileSystemPure
88 ) where
99
10+import Control.Exception (IOException)
1011import Control.Monad.Catch
12+import Control.Monad.IO.Class
1113import Prelude hiding (readFile, writeFile)
12−import qualified Control.Exception as E
1314import qualified Data.Map.Strict as M
1415import qualified System.IO as IO
1516
16−import Effectful
1717import Effectful.Error
18+import Effectful.Interpreter
19+import Effectful.Monad
20+import Effectful.State
1821
19−-- | Effect for reading and writing files.
20−data FileSystem = forall fs. FileSystem
21− { _fs :: fs
22− , _readFile :: forall es. Error FsError :> es => fs -> FilePath -> Eff es String
23− , _writeFile :: forall es. Error FsError :> es => fs -> FilePath -> String -> Eff es fs
24− }
22+-- | An effect for reading and writing files.
23+data FileSystem :: Effect where
24+ ReadFile :: FilePath -> FileSystem m String
25+ WriteFile :: FilePath -> String -> FileSystem m ()
2526
26−-- | File system error.
27+--- | File system error.
2728newtype FsError = FsError String
2829 deriving Show
2930instance Exception FsError
@@ -32,32 +33,33 @@ instance Exception FsError
3233-- Operations
3334
3435-- | Read contents of a file.
35−readFile :: (FileSystem :> es, Error FsError :> es) => FilePath -> Eff es String
36−readFile path = readerEffectM $ \FileSystem{..} -> _readFile _fs path
36+readFile :: FileSystem :> es => FilePath -> Eff es String
37+readFile = send . ReadFile
3738
3839-- | Write contents to a file.
39−writeFile :: (FileSystem :> es, Error FsError :> es) => FilePath -> String -> Eff es ()
40−writeFile path contents = stateEffectM $ \FileSystem{..} -> do
41− fs <- _writeFile _fs path contents
42− pure ((), FileSystem { _fs = fs, .. })
40+writeFile :: FileSystem :> es => FilePath -> String -> Eff es ()
41+writeFile path = send . WriteFile path
4342
4443----------------------------------------
4544-- Handlers
4645
47−runFileSystemIO :: Eff (FileSystem : es) a -> Eff es a
48−runFileSystemIO = evalEffect $ FileSystem
49− { _fs = ()
50− , _readFile = \() path -> wrap $ IO.readFile path
51− , _writeFile = \() path contents -> wrap $ IO.writeFile path contents
52− }
46+runFileSystemIO
47+ :: (IOE :> es, Error FsError :> es)
48+ => Eff (FileSystem : es) a
49+ -> Eff es a
50+runFileSystemIO = interpret $ \case
51+ ReadFile path -> adapt $ IO.readFile path
52+ WriteFile path contents -> adapt $ IO.writeFile path contents
5353 where
54− wrap m = impureEff_ m `catch` \(e::E.IOException) -> throwError . FsError $ show e
55−
56−runFileSystemPure :: M.Map FilePath String -> Eff (FileSystem : es) a -> Eff es a
57−runFileSystemPure fs0 = evalEffect $ FileSystem
58− { _fs = fs0
59− , _readFile = \fs path -> case path `M.lookup` fs of
60− Just contents -> pure contents
61− Nothing -> throwError . FsError $ "File not found: " ++ show path
62− , _writeFile = \fs path contents -> pure $ M.insert path contents fs
63− }
54+ adapt m = liftIO m `catch` \(e::IOException) -> throwError . FsError $ show e
55+
56+runFileSystemPure
57+ :: Error FsError :> es
58+ => M.Map FilePath String
59+ -> Eff (FileSystem : es) a
60+ -> Eff es a
61+runFileSystemPure fs0 = reinterpret (evalState fs0) $ \case
62+ ReadFile path -> gets (M.lookup path) >>= \case
63+ Just contents -> pure contents
64+ Nothing -> throwError . FsError $ "File not found: " ++ show path
65+ WriteFile path contents -> modify $ M.insert path contents
src/Effectful.hsdeleted−34
@@ -1,34 +0,0 @@
1−module Effectful
2− ( -- * The 'Eff' monad
3− Eff
4− , runEff
5− , Effect
6− , (:>)
7−
8− -- * The IO effect
9− , IOE
10− , runIOE
11−
12− -- * Execution of IO in effect handlers
13− , impureEff
14− , impureEff_
15−
16− -- * Helpers for defining new effects
17−
18− -- ** Running
19− , runEffect
20− , evalEffect
21− , execEffect
22−
23− -- ** Retrieval and modification
24− , getEffect
25− , putEffect
26− , stateEffect
27− , localEffect
28− , listenEffect
29− , readerEffectM
30− , stateEffectM
31− ) where
32−
33−import Effectful.Internal.Has
34−import Effectful.Internal.Monad
src/Effectful/Class/Reader.hs+1−1
@@ -7,7 +7,7 @@ module Effectful.Class.Reader
77import Control.Monad.Trans.Class
88import Control.Monad.Trans.Control
99
10−import Effectful.Internal.Has
10+import Effectful.Internal.Effect
1111import Effectful.Internal.Monad
1212import qualified Effectful.Reader as R
1313
src/Effectful/Class/State.hs+5−2
@@ -3,9 +3,9 @@ module Effectful.Class.State where
33
44import Control.Monad.Trans.Class
55
6−import Effectful.Internal.Has
6+import Effectful.Internal.Effect
77import Effectful.Internal.Monad
8−import qualified Effectful.State.Dynamic as S
8+import qualified Effectful.State as S
99
1010-- | Compatiblity layer for a transition period from MTL-style effect handling
1111-- to 'Effectful.Eff'.
@@ -39,5 +39,8 @@ instance S.State s :> es => MonadState s (Eff es) where
3939 put = S.put
4040 state = S.state
4141
42+gets :: MonadState s m => (s -> a) -> m a
43+gets f = f <$> get
44+
4245modify :: MonadState s m => (s -> s) -> m ()
4346modify f = state (\s -> ((), f s))
src/Effectful/Class/Writer.hs+1−1
@@ -4,7 +4,7 @@ module Effectful.Class.Writer where
44import Control.Monad.Trans.Class
55import Control.Monad.Trans.Control
66
7−import Effectful.Internal.Has
7+import Effectful.Internal.Effect
88import Effectful.Internal.Monad
99import qualified Effectful.Writer as W
1010
src/Effectful/Error.hs+14−13
@@ -11,40 +11,41 @@ import Control.Exception
1111import Data.Typeable
1212import GHC.Stack
1313
14+import Effectful.Internal.Effect
1415import Effectful.Internal.Env
15−import Effectful.Internal.Has
1616import Effectful.Internal.Monad
1717import Effectful.Internal.Utils
1818
19−type role Error nominal
20−data Error e = Error
19+type role Error nominal phantom phantom
20+data Error e :: Effect where
21+ Error :: Error e m r
2122
2223runError
2324 :: forall e es a. Exception e
2425 => Eff (Error e : es) a
2526 -> Eff es (Either ([String], e) a)
26−runError (Eff m) = impureEff $ \es0 -> mask $ \release -> do
27+runError (Eff m) = unsafeEff $ \es0 -> mask $ \release -> do
2728 size0 <- sizeEnv es0
28− es <- unsafeConsEnv (Error @e) es0
29+ es <- unsafeConsEnv (IdE (Error @e)) noRelinker es0
2930 try (release $ m es) >>= \case
30− Right a -> Right a <$ unsafeTailEnv size0 es
31− Left (WrapE cs e) -> Left (cs, e) <$ unsafeTailEnv size0 es
31+ Right a -> Right a <$ unsafeTailEnv size0 es
32+ Left (WrapErr cs e) -> Left (cs, e) <$ unsafeTailEnv size0 es
3233
3334throwError
3435 :: (HasCallStack, Exception e, Error e :> es)
3536 => e
3637 -> Eff es a
37−throwError e = impureEff_ $ do
38− throwIO $ WrapE (ppCallStack <$> getCallStack callStack) e
38+throwError e = unsafeEff_ $ do
39+ throwIO $ WrapErr (ppCallStack <$> getCallStack callStack) e
3940
4041catchError
4142 :: (Exception e, Error e :> es)
4243 => Eff es a
4344 -> ([String] -> e -> Eff es a)
4445 -> Eff es a
45−catchError (Eff m) handler = impureEff $ \es -> do
46+catchError (Eff m) handler = unsafeEff $ \es -> do
4647 size <- sizeEnv es
47− m es `catch` \(WrapE e cs) -> do
48+ m es `catch` \(WrapErr e cs) -> do
4849 checkSizeEnv size es
4950 unEff (handler e cs) es
5051
@@ -57,6 +58,6 @@ tryError m = (Right <$> m) `catchError` \es e -> pure (Left (es, e))
5758----------------------------------------
5859-- WrapE
5960
60−data WrapE e = WrapE [String] e
61+data WrapErr e = WrapErr [String] e
6162 deriving Show
62−instance (Show e, Typeable e) => Exception (WrapE e)
63+instance (Show e, Typeable e) => Exception (WrapErr e)
src/Effectful/Internal/Has.hs → src/Effectful/Internal/Effect.hsrenamed+13−13
@@ -5,31 +5,31 @@
55--
66-- This module is intended for internal use only, and may change without warning
77-- in subsequent releases.
8−module Effectful.Internal.Has
8+module Effectful.Internal.Effect
99 ( Effect
10− , (:>)
11− , ixEnv
10+ , (:>)(..)
11+ , IdE(..)
1212 ) where
1313
1414import Data.Kind
1515
16−type Effect = Type
16+type Effect = (Type -> Type) -> Type -> Type
17+
18+-- | Adapter for statically dispatched effects.
19+newtype IdE (e :: Effect) where
20+ IdE :: (forall m r. e m r) -> IdE e
1721
1822class (e :: Effect) :> (es :: [Effect]) where
1923 -- | Get position of @e@ in @es@.
2024 --
2125 -- /Note:/ GHC is kind enough to cache these values as they're top level CAFs,
2226 -- so the lookup is amortized @O(1)@ without any language level tricks.
23− ixOf :: Int
24− ixOf = -- Don't show "minimal complete definition" in haddock.
25− error "unimplemented"
27+ reifyIndex :: Int
28+ reifyIndex = -- Don't show "minimal complete definition" in haddock.
29+ error "unimplemented"
2630
2731instance {-# OVERLAPPING #-} e :> (e : es) where
28− ixOf = 0
32+ reifyIndex = 0
2933
3034instance e :> es => e :> (x : es) where
31− ixOf = 1 + ixOf @e @es
32−
33−-- | Get position of @e@ in the 'Effectful.Internal.Env.Env'.
34−ixEnv :: forall e es. e :> es => Int -> Int
35−ixEnv n = n - ixOf @e @es - 1
35+ reifyIndex = 1 + reifyIndex @e @es
src/Effectful/Internal/Env.hs+247−60
@@ -1,14 +1,18 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
12{-# OPTIONS_HADDOCK not-home #-}
2−-- | The enviroment for 'Effectful.Internal.Monad.Eff'.
3+-- | The environment for 'Effectful.Internal.Monad.Eff'.
34--
45-- This module is intended for internal use only, and may change without warning
56-- in subsequent releases.
67module Effectful.Internal.Env
78 ( Env
9+ , Relinker(..)
10+ , noRelinker
811
912 -- * Safe operations
1013 , emptyEnv
1114 , cloneEnv
15+ , forkEnv
1216 , sizeEnv
1317 , getEnv
1418 , checkSizeEnv
@@ -30,8 +34,9 @@ import Data.Primitive.SmallArray
3034import GHC.Exts (Any)
3135import GHC.Stack
3236import Unsafe.Coerce
37+import qualified Data.Map.Strict as M
3338
34−import Effectful.Internal.Has
39+import Effectful.Internal.Effect
3540
3641type role Env nominal
3742
@@ -48,61 +53,192 @@ type role Env nominal
4853--
4954-- - Modification of a specific element: /O(1)/.
5055--
51−newtype Env (es :: [Effect]) = Env (IORef EnvRef)
56+data Env (es :: [Effect]) = Env Forks (IORef EnvRef)
5257
53−data EnvRef = EnvRef Int (SmallMutableArray RealWorld Any)
58+-- | A fork is uniquely determined by its base index and depth.
59+data ForkId = ForkId
60+ { baseIx :: Int
61+ , depth :: Int
62+ } deriving (Eq, Ord, Show)
63+
64+-- | Implicit 'ForkId' of the 'EnvRef' directly available from 'Env'.
65+globalFid :: ForkId
66+globalFid = ForkId 0 0
67+
68+-- | Local forks of the environment.
69+data Forks = Forks ForkId (IORef EnvRef) Forks | NoFork
70+
71+data EnvRef = EnvRef
72+ { _capacity :: Int
73+ , _data :: SmallMutableArray RealWorld Any
74+ , _relinkers :: SmallMutableArray RealWorld Any
75+ }
76+
77+newtype Relinker i (e :: Effect) where
78+ Relinker
79+ :: ((forall es. Env es -> IO (Env es)) -> i e -> IO (i e))
80+ -> Relinker i e
81+
82+noRelinker :: Relinker i e
83+noRelinker = Relinker $ \_ -> pure
5484
5585-- | Create an empty environment.
5686emptyEnv :: IO (Env '[])
57−emptyEnv = fmap Env . newIORef . EnvRef 0
58− =<< newSmallArray 0 (error "undefined field")
87+emptyEnv = Env NoFork <$> emptyEnvRef
5988
6089-- | Clone the environment.
61−cloneEnv :: Env es -> IO (Env es)
62−cloneEnv (Env ref) = do
63− EnvRef n es <- readIORef ref
64− fmap Env . newIORef . EnvRef n
65− =<< cloneSmallMutableArray es 0 (sizeofSmallMutableArray es)
90+cloneEnv :: HasCallStack => Env es -> IO (Env es)
91+cloneEnv (Env NoFork gref0) = do
92+ cache <- newIORef M.empty
93+ gref <- cloneEnvRef cache globalFid gref0
94+ pure $ Env NoFork gref
95+cloneEnv _ = error "Cloning of forked enviroment is not supported yet"
96+{-# NOINLINE cloneEnv #-}
97+
98+----------------------------------------
99+-- Utils for cloning
100+
101+type EnvRefCache = IORef (M.Map ForkId (IORef EnvRef))
102+
103+-- | Clone the 'EnvRef' and put it in a cache.
104+cloneEnvRef
105+ :: EnvRefCache
106+ -> ForkId
107+ -> IORef EnvRef
108+ -> IO (IORef EnvRef)
109+cloneEnvRef cache fid ref0 = do
110+ EnvRef n es0 fs0 <- readIORef ref0
111+ es <- cloneSmallMutableArray es0 0 (sizeofSmallMutableArray es0)
112+ fs <- cloneSmallMutableArray fs0 0 (sizeofSmallMutableArray fs0)
113+ ref <- newIORef $ EnvRef n es fs
114+ modifyIORef' cache $ M.insert fid ref
115+ relinkData cache es fs n
116+ pure ref
117+
118+-- | Relink local environments hiding in the interpreters.
119+relinkData
120+ :: EnvRefCache
121+ -> SmallMutableArray RealWorld Any
122+ -> SmallMutableArray RealWorld Any
123+ -> Int
124+ -> IO ()
125+relinkData cache es fs = \case
126+ 0 -> pure ()
127+ n -> do
128+ let i = n - 1
129+ Relinker f <- fromAny <$> readSmallArray fs i
130+ readSmallArray es i
131+ >>= f relinkEnv . fromAny
132+ >>= writeSmallArray es i . toAny
133+ relinkData cache es fs i
134+ where
135+ relinkEnv :: Env es -> IO (Env es)
136+ relinkEnv (Env forks _) = do
137+ Just gref <- M.lookup globalFid <$> readIORef cache
138+ Env <$> relinkForks cache forks <*> pure gref
139+
140+relinkForks :: EnvRefCache -> Forks -> IO Forks
141+relinkForks cache = \case
142+ NoFork -> pure NoFork
143+ Forks fid lref0 forks -> (M.lookup fid <$> readIORef cache) >>= \case
144+ Just lref -> Forks fid <$> pure lref
145+ <*> relinkForks cache forks
146+ Nothing -> Forks fid <$> cloneEnvRef cache fid lref0
147+ <*> relinkForks cache forks
148+
149+----------------------------------------
150+
151+-- | Create a local fork of the environment for interpreters.
152+forkEnv :: HasCallStack => Env es -> IO (Env es)
153+forkEnv env@(Env NoFork gref) = do
154+ size <- sizeEnv env
155+ let fid = ForkId
156+ { baseIx = size
157+ , depth = 1
158+ }
159+ Env <$> (Forks fid <$> emptyEnvRef <*> pure NoFork)
160+ <*> pure gref
161+forkEnv (Env forks@(Forks fid0 lref0 olderForks) gref) = do
162+ EnvRef n _ _ <- readIORef lref0
163+ -- If the fork is empty, replace it as no data is lost.
164+ if n == 0
165+ then do
166+ lref <- emptyEnvRef
167+ let fid = fid0
168+ { depth = depth fid0 + 1
169+ }
170+ pure $ Env (Forks fid lref olderForks) gref
171+ else do
172+ lref <- emptyEnvRef
173+ let fid = fid0
174+ { baseIx = baseIx fid0 + n
175+ , depth = depth fid0 + 1
176+ }
177+ pure $ Env (Forks fid lref forks) gref
178+{-# NOINLINE forkEnv #-}
66179
67180-- | Get the current size of the environment.
68181sizeEnv :: Env es -> IO Int
69−sizeEnv (Env ref) = do
70− EnvRef n _ <- readIORef ref
182+sizeEnv (Env NoFork ref) = do
183+ EnvRef n _ _ <- readIORef ref
71184 pure n
185+sizeEnv (Env (Forks fid lref _) _) = do
186+ EnvRef n _ _ <- readIORef lref
187+ pure $ baseIx fid + n
188+{-# NOINLINE sizeEnv #-}
72189
73190-- | Extract a specific data type from the environment.
74−getEnv :: forall e es. (HasCallStack, e :> es) => Env es -> IO e
75−getEnv (Env ref) = do
76− EnvRef n es <- readIORef ref
77− fromAny <$> readSmallArray es (ixEnv @e @es n)
191+getEnv :: forall e es i. (HasCallStack, e :> es) => Env es -> IO (i e)
192+getEnv env = do
193+ (i, es) <- getLocation @e env
194+ fromAny <$> readSmallArray es i
78195
79196-- | Check that the size of the environment is the same as the expected value.
80197checkSizeEnv :: HasCallStack => Int -> Env es -> IO ()
81−checkSizeEnv k (Env ref) = do
82− EnvRef n _ <- readIORef ref
83− when (k /= n) . error $ "k (" ++ show k ++ ") /= n (" ++ show n ++ ")"
198+checkSizeEnv k (Env NoFork ref) = do
199+ EnvRef n _ _ <- readIORef ref
200+ when (k /= n) $ do
201+ error $ "k (" ++ show k ++ ") /= n (" ++ show n ++ ")"
202+checkSizeEnv k (Env (Forks fid lref _) _) = do
203+ EnvRef n _ _ <- readIORef lref
204+ when (k /= baseIx fid + n) $ do
205+ error $ "k (" ++ show k ++ ") /= baseIx + n (baseIx: "
206+ ++ show (baseIx fid) ++ ", n: " ++ show n ++ ")"
207+{-# NOINLINE checkSizeEnv #-}
84208
85209----------------------------------------
86210-- Extending and shrinking
87211
88212-- | Extend the environment with a new data type (in place).
89−unsafeConsEnv :: HasCallStack => e -> Env es -> IO (Env (e : es))
90−unsafeConsEnv e (Env ref) = do
91− EnvRef n es0 <- readIORef ref
92− let len0 = sizeofSmallMutableArray es0
93− case n `compare` len0 of
94− GT -> error $ "n (" ++ show n ++ ") > len0 (" ++ show len0 ++ ")"
95− LT -> do
96− e `seq` writeSmallArray es0 n (toAny e)
97− writeIORef ref $! EnvRef (n + 1) es0
98− EQ -> do
99− let len = doubleCapacity len0
100− es <- newSmallArray len (error "undefined field")
101− copySmallMutableArray es 0 es0 0 len0
102− e `seq` writeSmallArray es n (toAny e)
103− writeIORef ref $! EnvRef (n + 1) es
104− pure $ Env ref
213+unsafeConsEnv :: HasCallStack => i e -> Relinker i e -> Env es -> IO (Env (e : es))
214+unsafeConsEnv e f (Env fork gref) = case fork of
215+ NoFork -> do
216+ extendEnvRef gref
217+ pure $ Env NoFork gref
218+ Forks base lref forks -> do
219+ extendEnvRef lref
220+ pure $ Env (Forks base lref forks) gref
105221 where
222+ extendEnvRef :: IORef EnvRef -> IO ()
223+ extendEnvRef ref = do
224+ EnvRef n es0 fs0 <- readIORef ref
225+ let len0 = sizeofSmallMutableArray es0
226+ case n `compare` len0 of
227+ GT -> error $ "n (" ++ show n ++ ") > len0 (" ++ show len0 ++ ")"
228+ LT -> do
229+ e `seq` writeSmallArray es0 n (toAny e)
230+ f `seq` writeSmallArray fs0 n (toAny f)
231+ writeIORef ref $! EnvRef (n + 1) es0 fs0
232+ EQ -> do
233+ let len = doubleCapacity len0
234+ es <- newSmallArray len (error "undefined field")
235+ copySmallMutableArray es 0 es0 0 len0
236+ e `seq` writeSmallArray es n (toAny e)
237+ fs <- newSmallArray len (error "undefined field")
238+ copySmallMutableArray fs 0 fs0 0 len0
239+ f `seq` writeSmallArray fs n (toAny f)
240+ writeIORef ref $! EnvRef (n + 1) es fs
241+
106242 doubleCapacity :: Int -> Int
107243 doubleCapacity n = max 1 n * 2
108244{-# NOINLINE unsafeConsEnv #-}
@@ -110,14 +246,23 @@ unsafeConsEnv e (Env ref) = do
110246-- | Shrink the environment by one data type (in place). Makes sure the size of
111247-- the environment is as expected.
112248unsafeTailEnv :: HasCallStack => Int -> Env (e : es) -> IO (Env es)
113−unsafeTailEnv k (Env ref) = do
114− EnvRef n es <- readIORef ref
115− if k /= n - 1
116− then error $ "k (" ++ show k ++ ") /= n - 1 (" ++ show (n - 1) ++ ")"
117− else do
118− writeSmallArray es k (error "undefined field")
119− writeIORef ref $! EnvRef k es
120− pure $ Env ref
249+unsafeTailEnv len (Env fork gref) = case fork of
250+ NoFork -> do
251+ shrinkEnvRef len gref
252+ pure $ Env NoFork gref
253+ Forks fid lref forks -> do
254+ shrinkEnvRef (len - baseIx fid) lref
255+ pure $ Env (Forks fid lref forks) gref
256+ where
257+ shrinkEnvRef :: Int -> IORef EnvRef -> IO ()
258+ shrinkEnvRef k ref = do
259+ EnvRef n es fs <- readIORef ref
260+ if k /= n - 1
261+ then error $ "k (" ++ show k ++ ") /= n - 1 (" ++ show (n - 1) ++ ")"
262+ else do
263+ writeSmallArray es k (error "undefined field")
264+ writeSmallArray fs k (error "undefined field")
265+ writeIORef ref $! EnvRef k es fs
121266{-# NOINLINE unsafeTailEnv #-}
122267
123268----------------------------------------
@@ -125,34 +270,32 @@ unsafeTailEnv k (Env ref) = do
125270
126271-- | Replace the data type in the environment with a new value (in place).
127272unsafePutEnv
128− :: forall e es. (HasCallStack, e :> es)
129− => e
273+ :: forall e es i. (HasCallStack, e :> es)
274+ => i e
130275 -> Env es
131276 -> IO ()
132−unsafePutEnv e (Env ref) = do
133− EnvRef n es <- readIORef ref
134− e `seq` writeSmallArray es (ixEnv @e @es n) (toAny e)
277+unsafePutEnv e env = do
278+ (i, es) <- getLocation @e env
279+ e `seq` writeSmallArray es i (toAny e)
135280
136281-- | Modify the data type in the environment (in place).
137282unsafeModifyEnv
138− :: forall e es. (HasCallStack, e :> es)
139− => (e -> e)
283+ :: forall e es i. (HasCallStack, e :> es)
284+ => (i e -> i e)
140285 -> Env es
141286 -> IO ()
142−unsafeModifyEnv f (Env ref) = do
143− EnvRef n es <- readIORef ref
144− let i = ixEnv @e @es n
287+unsafeModifyEnv f env = do
288+ (i, es) <- getLocation @e env
145289 e <- f . fromAny <$> readSmallArray es i
146290 e `seq` writeSmallArray es i (toAny e)
147291
148−-- | Modify the data type in the enviroment (in place) and return a value.
292+-- | Modify the data type in the environment (in place) and return a value.
149293unsafeStateEnv
150− :: forall e es a. (HasCallStack, e :> es)
151− => (e -> (a, e))
294+ :: forall e es i a. (HasCallStack, e :> es)
295+ => (i e -> (a, i e))
152296 -> Env es -> IO a
153−unsafeStateEnv f (Env ref) = do
154− EnvRef n es <- readIORef ref
155− let i = ixEnv @e @es n
297+unsafeStateEnv f env = do
298+ (i, es) <- getLocation @e env
156299 (a, e) <- f . fromAny <$> readSmallArray es i
157300 e `seq` writeSmallArray es i (toAny e)
158301 pure a
@@ -160,6 +303,50 @@ unsafeStateEnv f (Env ref) = do
160303----------------------------------------
161304-- Internal helpers
162305
306+emptyEnvRef :: IO (IORef EnvRef)
307+emptyEnvRef = do
308+ es <- newSmallArray 0 (error "undefined field")
309+ fs <- newSmallArray 0 (error "undefined field")
310+ newIORef $ EnvRef 0 es fs
311+
312+-- | Determine location of the data type in the environment.
313+getLocation
314+ :: forall e es. e :> es
315+ => Env es
316+ -> IO (Int, SmallMutableArray RealWorld Any)
317+getLocation (Env fork ref) = do
318+ EnvRef n es _ <- readIORef ref
319+ -- Optimized for the common access pattern:
320+ --
321+ -- - Most application code has no access to forks, in which case we look at
322+ -- the global EnvRef.
323+ --
324+ -- - Interpreters will most likely access the newest fork for reinterpretation
325+ -- of effects that are there.
326+ case fork of
327+ NoFork -> pure (ix n, es)
328+ Forks fid lref forks -> do
329+ EnvRef ln les _ <- readIORef lref
330+ let i = ix (baseIx fid + ln)
331+ if i >= baseIx fid
332+ then pure (i - baseIx fid, les)
333+ else go es i forks
334+ where
335+ go :: SmallMutableArray RealWorld Any
336+ -> Int
337+ -> Forks
338+ -> IO (Int, SmallMutableArray RealWorld Any)
339+ go es i = \case
340+ NoFork -> pure (i, es)
341+ Forks fid lref forks -> do
342+ EnvRef _ les _ <- readIORef lref
343+ if i >= baseIx fid
344+ then pure (i - baseIx fid, les)
345+ else go es i forks
346+
347+ ix :: Int -> Int
348+ ix n = n - reifyIndex @e @es - 1
349+
163350toAny :: a -> Any
164351toAny = unsafeCoerce
165352
src/Effectful/Internal/Monad.hs+50−58
@@ -8,9 +8,9 @@ module Effectful.Internal.Monad
88 ( -- * Monad
99 Eff(..)
1010 , runEff
11− , impureEff
12− , impureEff_
13− , impureUnliftEff
11+ , unsafeEff
12+ , unsafeEff_
13+ , unsafeUnliftEff
1414
1515 -- * IO
1616 , IOE
@@ -24,7 +24,6 @@ module Effectful.Internal.Monad
2424 , putEffect
2525 , stateEffect
2626 , localEffect
27− , listenEffect
2827 , readerEffectM
2928 , stateEffectM
3029 ) where
@@ -37,8 +36,8 @@ import Control.Monad.IO.Unlift
3736import Control.Monad.Trans.Control
3837import GHC.Magic (oneShot)
3938
39+import Effectful.Internal.Effect
4040import Effectful.Internal.Env
41−import Effectful.Internal.Has
4241
4342type role Eff nominal representational
4443
@@ -47,15 +46,15 @@ newtype Eff (es :: [Effect]) a = Eff { unEff :: Env es -> IO a }
4746runEff :: Eff '[] a -> IO a
4847runEff (Eff m) = m =<< emptyEnv
4948
50−impureEff :: (Env es -> IO a) -> Eff es a
51−impureEff m = Eff (oneShot m)
49+unsafeEff :: (Env es -> IO a) -> Eff es a
50+unsafeEff m = Eff (oneShot m)
5251
53−impureEff_ :: IO a -> Eff es a
54−impureEff_ m = impureEff $ \_ -> m
52+unsafeEff_ :: IO a -> Eff es a
53+unsafeEff_ m = unsafeEff $ \_ -> m
5554
56−-- | Run 'Eff' computations in 'IO'.
57−impureUnliftEff :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
58−impureUnliftEff f = impureEff $ \es -> do
55+-- | Lower 'Eff' computations into 'IO'.
56+unsafeUnliftEff :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
57+unsafeUnliftEff f = unsafeEff $ \es -> do
5958 tid0 <- myThreadId
6059 f $ \(Eff m) -> do
6160 tid <- myThreadId
@@ -70,14 +69,14 @@ impureUnliftEff f = impureEff $ \es -> do
7069-- Base
7170
7271instance Functor (Eff es) where
73− fmap f (Eff m) = impureEff $ \es -> f <$> m es
72+ fmap f (Eff m) = unsafeEff $ \es -> f <$> m es
7473
7574instance Applicative (Eff es) where
76− pure = impureEff_ . pure
77− Eff mf <*> Eff mx = impureEff $ \es -> mf es <*> mx es
75+ pure = unsafeEff_ . pure
76+ Eff mf <*> Eff mx = unsafeEff $ \es -> mf es <*> mx es
7877
7978instance Monad (Eff es) where
80− Eff m >>= k = impureEff $ \es -> do
79+ Eff m >>= k = unsafeEff $ \es -> do
8180 a <- m es
8281 unEff (k a) es
8382
@@ -85,23 +84,23 @@ instance Monad (Eff es) where
8584-- Exception
8685
8786instance MonadThrow (Eff es) where
88− throwM = impureEff_ . throwM
87+ throwM = unsafeEff_ . throwM
8988
9089instance MonadCatch (Eff es) where
91− catch (Eff m) handler = impureEff $ \es -> do
90+ catch (Eff m) handler = unsafeEff $ \es -> do
9291 size <- sizeEnv es
9392 m es `catch` \e -> do
9493 checkSizeEnv size es
9594 unEff (handler e) es
9695
9796instance MonadMask (Eff es) where
98− mask k = impureEff $ \es -> mask $ \restore ->
99− unEff (k $ \(Eff m) -> impureEff $ restore . m) es
97+ mask k = unsafeEff $ \es -> mask $ \restore ->
98+ unEff (k $ \(Eff m) -> unsafeEff $ restore . m) es
10099
101− uninterruptibleMask k = impureEff $ \es -> uninterruptibleMask $ \restore ->
102− unEff (k $ \(Eff m) -> impureEff $ restore . m) es
100+ uninterruptibleMask k = unsafeEff $ \es -> uninterruptibleMask $ \restore ->
101+ unEff (k $ \(Eff m) -> unsafeEff $ restore . m) es
103102
104− generalBracket acquire release use = impureEff $ \es -> mask $ \restore -> do
103+ generalBracket acquire release use = unsafeEff $ \es -> mask $ \restore -> do
105104 size <- sizeEnv es
106105 resource <- unEff acquire es
107106 b <- restore (unEff (use resource) es) `catch` \e -> do
@@ -115,77 +114,70 @@ instance MonadMask (Eff es) where
115114----------------------------------------
116115-- IO
117116
118−data IOE = IOE
117+data IOE :: Effect where
118+ IOE :: IOE m r
119119
120120runIOE :: Eff '[IOE] a -> Eff '[] a
121−runIOE = evalEffect IOE
121+runIOE = evalEffect (IdE IOE)
122122
123123instance IOE :> es => MonadIO (Eff es) where
124− liftIO = impureEff_
124+ liftIO = unsafeEff_
125125
126126instance IOE :> es => MonadBase IO (Eff es) where
127− liftBase = impureEff_
127+ liftBase = unsafeEff_
128128
129129instance IOE :> es => MonadBaseControl IO (Eff es) where
130130 type StM (Eff es) a = a
131− liftBaseWith = impureUnliftEff
131+ liftBaseWith = unsafeUnliftEff
132132 restoreM = pure
133133
134134instance IOE :> es => MonadUnliftIO (Eff es) where
135− withRunInIO = impureUnliftEff
135+ withRunInIO = unsafeUnliftEff
136136
137137----------------------------------------
138138-- Helpers
139139
140−runEffect :: e -> Eff (e : es) a -> Eff es (a, e)
141−runEffect e0 (Eff m) = impureEff $ \es0 -> do
140+runEffect :: i e -> Eff (e : es) a -> Eff es (a, i e)
141+runEffect e0 (Eff m) = unsafeEff $ \es0 -> do
142142 size0 <- sizeEnv es0
143− bracket (unsafeConsEnv e0 es0)
143+ bracket (unsafeConsEnv e0 noRelinker es0)
144144 (unsafeTailEnv size0)
145145 (\es -> (,) <$> m es <*> getEnv es)
146146
147−evalEffect :: e -> Eff (e : es) a -> Eff es a
148−evalEffect e (Eff m) = impureEff $ \es0 -> do
147+evalEffect :: i e -> Eff (e : es) a -> Eff es a
148+evalEffect e (Eff m) = unsafeEff $ \es0 -> do
149149 size0 <- sizeEnv es0
150− bracket (unsafeConsEnv e es0)
150+ bracket (unsafeConsEnv e noRelinker es0)
151151 (unsafeTailEnv size0)
152152 (\es -> m es)
153153
154−execEffect :: e -> Eff (e : es) a -> Eff es e
155−execEffect e0 (Eff m) = impureEff $ \es0 -> do
154+execEffect :: i e -> Eff (e : es) a -> Eff es (i e)
155+execEffect e0 (Eff m) = unsafeEff $ \es0 -> do
156156 size0 <- sizeEnv es0
157− bracket (unsafeConsEnv e0 es0)
157+ bracket (unsafeConsEnv e0 noRelinker es0)
158158 (unsafeTailEnv size0)
159159 (\es -> m es *> getEnv es)
160160
161−getEffect :: e :> es => Eff es e
162−getEffect = impureEff $ \es -> getEnv es
161+getEffect :: e :> es => Eff es (i e)
162+getEffect = unsafeEff $ \es -> getEnv es
163163
164−putEffect :: e :> es => e -> Eff es ()
165−putEffect e = impureEff $ \es -> unsafePutEnv e es
164+putEffect :: e :> es => i e -> Eff es ()
165+putEffect e = unsafeEff $ \es -> unsafePutEnv e es
166166
167−stateEffect :: e :> es => (e -> (a, e)) -> Eff es a
168−stateEffect f = impureEff $ \es -> unsafeStateEnv f es
167+stateEffect :: e :> es => (i e -> (a, i e)) -> Eff es a
168+stateEffect f = unsafeEff $ \es -> unsafeStateEnv f es
169169
170−localEffect :: e :> es => (e -> e) -> Eff es a -> Eff es a
171−localEffect f (Eff m) = impureEff $ \es -> do
170+localEffect :: e :> es => (i e -> i e) -> Eff es a -> Eff es a
171+localEffect f (Eff m) = unsafeEff $ \es -> do
172172 bracket (unsafeStateEnv (\e -> (e, f e)) es)
173173 (\e -> unsafePutEnv e es)
174174 (\_ -> m es)
175175
176−listenEffect :: (e :> es, Monoid e) => Eff es a -> Eff es (a, e)
177−listenEffect (Eff m) = impureEff $ \es -> mask $ \restore -> do
178− e0 <- unsafeStateEnv (\e -> (e, mempty)) es
179− -- If an exception is thrown, restore e0 and keep parts of e1.
180− a <- restore (m es) `onException` unsafeModifyEnv (\e -> e0 `mappend` e) es
181− e1 <- unsafeStateEnv (\e -> (e, e0 `mappend` e)) es
182− pure (a, e1)
176+readerEffectM :: e :> es => (i e -> Eff es a) -> Eff es a
177+readerEffectM f = unsafeEff $ \es -> getEnv es >>= \e -> unEff (f e) es
183178
184−readerEffectM :: e :> es => (e -> Eff es a) -> Eff es a
185−readerEffectM f = impureEff $ \es -> getEnv es >>= \e -> unEff (f e) es
186−
187−stateEffectM :: e :> es => (e -> Eff es (a, e)) -> Eff es a
188−stateEffectM f = impureEff $ \es -> do
179+stateEffectM :: e :> es => (i e -> Eff es (a, i e)) -> Eff es a
180+stateEffectM f = unsafeEff $ \es -> do
189181 (a, e) <- (\e -> unEff (f e) es) =<< getEnv es
190182 unsafePutEnv e es
191183 pure a
src/Effectful/Interpreter.hsnew file+109
@@ -0,0 +1,109 @@
1+module Effectful.Interpreter
2+ ( -- * Sending commands
3+ send
4+
5+ -- * Interpretation
6+ , interpret
7+ , interpretM
8+ , interpretIO
9+
10+ -- * Reinterpretation
11+ , reinterpret
12+ , reinterpretM
13+ , reinterpretIO
14+ ) where
15+
16+import Control.Monad.Catch
17+
18+import Effectful.Internal.Effect
19+import Effectful.Internal.Env
20+import Effectful.Internal.Monad
21+
22+----------------------------------------
23+-- Interpreter
24+
25+data Interpreter (e :: Effect) = forall localEs. Interpreter
26+ { _env :: Env localEs
27+ , _interpret :: forall a es. (forall r. Eff es r -> IO r)
28+ -> e (Eff es) a -> Eff localEs a
29+ }
30+
31+runInterpreter :: Env es -> Interpreter e -> Eff (e : es) a -> IO a
32+runInterpreter es0 e (Eff m) = do
33+ size0 <- sizeEnv es0
34+ bracket (unsafeConsEnv e relinker es0)
35+ (unsafeTailEnv size0)
36+ (\es -> m es)
37+ where
38+ relinker :: Relinker Interpreter e
39+ relinker = Relinker $ \relink Interpreter{..} -> do
40+ env <- relink _env
41+ pure Interpreter { _env = env, .. }
42+
43+----------------------------------------
44+-- Sending commands
45+
46+-- | Send an operation of a given effect to the interpreter.
47+send :: e :> es => e (Eff es) a -> Eff es a
48+send op = readerEffectM $ \Interpreter{..} -> do
49+ unsafeUnliftEff $ \run -> unEff (_interpret run op) _env
50+
51+----------------------------------------
52+-- Interpretation
53+
54+-- | Interpret a first order effect.
55+interpret
56+ :: (forall r es. e (Eff es) r -> Eff baseEs r)
57+ -> Eff (e : baseEs) a
58+ -> Eff baseEs a
59+interpret f = interpretIO $ \_ -> f
60+
61+-- | Interpret a higher order effect with help of a natural transformation from
62+-- 'Eff' to 'Eff'.
63+interpretM
64+ :: (forall r es. (forall t. Eff es t -> Eff baseEs t) -> e (Eff es) r -> Eff baseEs r)
65+ -> Eff (e : baseEs) a
66+ -> Eff baseEs a
67+interpretM f = interpretIO $ \k -> f $ unsafeEff_ . k
68+
69+-- | Interpret a higher order effect with help of a natural transformation from
70+-- 'Eff' to 'IO'.
71+interpretIO
72+ :: (forall r es. (forall t. Eff es t -> IO t) -> e (Eff es) r -> Eff baseEs r)
73+ -> Eff (e : baseEs) a
74+ -> Eff baseEs a
75+interpretIO f m = unsafeEff $ \es -> do
76+ les <- forkEnv es
77+ runInterpreter es (Interpreter les f) m
78+
79+----------------------------------------
80+-- Reinterpretation
81+
82+-- | Reinterpret local effects as a first order effect.
83+reinterpret
84+ :: (Eff localEs a -> Eff baseEs b)
85+ -> (forall r es. e (Eff es) r -> Eff localEs r)
86+ -> Eff (e : baseEs) a
87+ -> Eff baseEs b
88+reinterpret runLocal f = reinterpretIO runLocal $ \_ -> f
89+
90+-- | Reinterpret local effects as a higher order effect with help of a natural
91+-- transformation from 'Eff' to 'Eff'.
92+reinterpretM
93+ :: (Eff localEs a -> Eff baseEs b)
94+ -> (forall r es. (forall t. Eff es t -> Eff localEs t) -> e (Eff es) r -> Eff localEs r)
95+ -> Eff (e : baseEs) a
96+ -> Eff baseEs b
97+reinterpretM runLocal f = reinterpretIO runLocal $ \k -> f $ unsafeEff_ . k
98+
99+-- | Reinterpret local effects as a higher order effect with help of a natural
100+-- transformation from 'Eff' to 'IO'.
101+reinterpretIO
102+ :: (Eff localEs a -> Eff baseEs b)
103+ -> (forall r es. (forall t. Eff es t -> IO t) -> e (Eff es) r -> Eff localEs r)
104+ -> Eff (e : baseEs) a
105+ -> Eff baseEs b
106+reinterpretIO runLocal f m = unsafeEff $ \es -> do
107+ les0 <- forkEnv es
108+ (`unEff` les0) . runLocal . unsafeEff $ \les -> do
109+ runInterpreter es (Interpreter les f) m
src/Effectful/Monad.hsnew file+14
@@ -0,0 +1,14 @@
1+module Effectful.Monad
2+ ( -- * The 'Eff' monad
3+ Eff
4+ , runEff
5+ , Effect
6+ , (:>)
7+
8+ -- * The IO effect
9+ , IOE
10+ , runIOE
11+ ) where
12+
13+import Effectful.Internal.Effect
14+import Effectful.Internal.Monad
src/Effectful/Reader.hs+11−6
@@ -8,23 +8,28 @@ module Effectful.Reader
88 , asks
99 ) where
1010
11−import Effectful.Internal.Has
11+import Effectful.Internal.Effect
1212import Effectful.Internal.Monad
1313
1414-- | Provide access to a read only value of type @r@.
15−newtype Reader r = Reader { unReader :: r }
15+newtype Reader r :: Effect where
16+ Reader :: r -> Reader r m a
1617
1718runReader :: r -> Eff (Reader r : es) a -> Eff es a
18−runReader r = evalEffect (Reader r)
19+runReader r = evalEffect (IdE (Reader r))
1920
2021ask :: Reader r :> es => Eff es r
21−ask = unReader <$> getEffect
22+ask = do
23+ IdE (Reader r) <- getEffect
24+ pure r
2225
2326local :: Reader r :> es => (r -> r) -> Eff es a -> Eff es a
24−local f = localEffect (Reader . f . unReader)
27+local f = localEffect $ \(IdE (Reader r)) -> IdE (Reader (f r))
2528
2629reader :: Reader r :> es => (r -> a) -> Eff es a
27−reader f = f . unReader <$> getEffect
30+reader f = do
31+ IdE (Reader r) <- getEffect
32+ pure $ f r
2833
2934asks :: Reader r :> es => (r -> a) -> Eff es a
3035asks = reader
src/Effectful/Resource.hs+7−6
@@ -17,20 +17,21 @@ import Control.Exception
1717import qualified Control.Monad.Trans.Resource as R
1818import qualified Control.Monad.Trans.Resource.Internal as RI
1919
20+import Effectful.Internal.Effect
2021import Effectful.Internal.Env
21−import Effectful.Internal.Has
2222import Effectful.Internal.Monad
2323
2424-- | Data tag for a resource effect.
25−newtype Resource = Resource R.InternalState
25+newtype Resource :: Effect where
26+ Resource :: R.InternalState -> Resource m r
2627
2728-- | Run the resource effect.
2829runResource :: Eff (Resource : es) a -> Eff es a
29−runResource (Eff m) = impureEff $ \es0 -> do
30+runResource (Eff m) = unsafeEff $ \es0 -> do
3031 size0 <- sizeEnv es0
3132 istate <- R.createInternalState
3233 mask $ \restore -> do
33− es <- unsafeConsEnv (Resource istate) es0
34+ es <- unsafeConsEnv (IdE (Resource istate)) noRelinker es0
3435 a <- restore (m es) `catch` \e -> do
3536 _ <- unsafeTailEnv size0 es
3637 RI.stateCleanupChecked (Just e) istate
@@ -40,5 +41,5 @@ runResource (Eff m) = impureEff $ \es0 -> do
4041 pure a
4142
4243instance (IOE :> es, Resource :> es) => R.MonadResource (Eff es) where
43− liftResourceT (RI.ResourceT m) = impureEff $ \es -> do
44− getEnv es >>= \(Resource istate) -> m istate
44+ liftResourceT (RI.ResourceT m) = unsafeEff $ \es -> do
45+ getEnv es >>= \(IdE (Resource istate)) -> m istate
src/Effectful/State.hs+24−16
@@ -2,12 +2,9 @@
22--
33-- Represented as a pure value underneath, therefore:
44--
5−-- - very fast
5+-- - very fast,
66--
7−-- - not suitable for sharing between multiple threads.
8−--
9−-- If you plan to do the latter, have a look at "Effectful.State.MVar" or
10−-- "Effectful.State.Dynamic".
7+-- - thread local (if you need sharing, have a look at "Effectful.State.MVar").
118--
129module Effectful.State
1310 ( State
@@ -15,6 +12,7 @@ module Effectful.State
1512 , evalState
1613 , execState
1714 , get
15+ , gets
1816 , put
1917 , state
2018 , modify
@@ -22,37 +20,47 @@ module Effectful.State
2220 , modifyM
2321 ) where
2422
25−import Data.Coerce
26−
27−import Effectful.Internal.Has
23+import Effectful.Internal.Effect
2824import Effectful.Internal.Monad
2925
3026-- | Provide access to a pure, mutable state of type @s@.
31−newtype State s = State { unState :: s }
27+newtype State s :: Effect where
28+ State :: s -> State s m r
3229
3330runState :: s -> Eff (State s : es) a -> Eff es (a, s)
34−runState s = coerce . runEffect (State s)
31+runState s0 m = do
32+ (a, IdE (State s)) <- runEffect (IdE (State s0)) m
33+ pure (a, s)
3534
3635evalState :: s -> Eff (State s : es) a -> Eff es a
37−evalState s = evalEffect (State s)
36+evalState s = evalEffect (IdE (State s))
3837
3938execState :: s -> Eff (State s : es) a -> Eff es s
40−execState s = coerce . execEffect (State s)
39+execState s0 m = do
40+ IdE (State s) <- execEffect (IdE (State s0)) m
41+ pure s
4142
4243get :: State s :> es => Eff es s
43−get = unState <$> getEffect
44+get = do
45+ IdE (State s) <- getEffect
46+ pure s
47+
48+gets :: State s :> es => (s -> a) -> Eff es a
49+gets f = f <$> get
4450
4551put :: State s :> es => s -> Eff es ()
46−put = putEffect . State
52+put s = putEffect (IdE (State s))
4753
4854state :: State s :> es => (s -> (a, s)) -> Eff es a
49−state f = stateEffect $ \(State s0) -> let (a, s) = f s0 in (a, State s)
55+state f = stateEffect $ \(IdE (State s0)) -> let (a, s) = f s0 in (a, IdE (State s))
5056
5157modify :: State s :> es => (s -> s) -> Eff es ()
5258modify f = state (\s -> ((), f s))
5359
5460stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
55−stateM f = stateEffectM $ \(State s0) -> coerce $ f s0
61+stateM f = stateEffectM $ \(IdE (State s0)) -> do
62+ (a, s) <- f s0
63+ pure (a, IdE (State s))
5664
5765modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
5866modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effectful/State/Dynamic.hs+52−56
@@ -5,13 +5,13 @@
55--
66-- - Share state between threads and need the synchonized version.
77--
8−-- - Don't share state between threads and are free to use the faster, pure
9−-- version.
8+-- - Don't share state between threads (or need them to be thread local) and are
9+-- free to use the faster, pure version.
1010--
1111-- However, let's include this for now.
1212--
1313module Effectful.State.Dynamic
14− ( State
14+ ( State(..)
1515
1616 -- * Pure
1717 , runState
@@ -25,6 +25,7 @@ module Effectful.State.Dynamic
2525
2626 -- * Operations
2727 , get
28+ , gets
2829 , put
2930 , state
3031 , modify
@@ -32,94 +33,89 @@ module Effectful.State.Dynamic
3233 , modifyM
3334 ) where
3435
35−import Control.Concurrent.MVar
36−
37−import Effectful.Internal.Has
36+import Effectful.Internal.Effect
3837import Effectful.Internal.Monad
38+import Effectful.Interpreter
39+
40+import qualified Effectful.State as SP
41+import qualified Effectful.State.MVar as SM
3942
4043-- | Provide access to a mutable state of type @s@.
4144--
42−-- Whether the state is represented as a pure value or an 'MVar' depends on the
43−-- interpretation.
44−data State s = forall ref. State
45− { _ref :: ref
46− , _get :: forall es. ref -> Eff es s
47− , _put :: forall es. ref -> s -> Eff es ref
48− , _state :: forall es a. ref -> (s -> Eff es (a, s)) -> Eff es (a, ref)
49− }
45+-- Whether the state is represented as a pure value or an 'MVar' underneath
46+-- depends on the interpretation.
47+data State s :: Effect where
48+ Get :: State s m s
49+ Put :: s -> State s m ()
50+ State :: (s -> (a, s)) -> State s m a
51+ StateM :: (s -> m (a, s)) -> State s m a
5052
5153----------------------------------------
5254-- Pure
5355
5456runState :: s -> Eff (State s : es) a -> Eff es (a, s)
55−runState s m = evalEffect (statePure s) $ (,) <$> m <*> get
57+runState s0 = reinterpretM (SP.runState s0) statePure
5658
5759evalState :: s -> Eff (State s : es) a -> Eff es a
58−evalState s = evalEffect (statePure s)
60+evalState s0 = reinterpretM (SP.evalState s0) statePure
5961
6062execState :: s -> Eff (State s : es) a -> Eff es s
61−execState s m = evalEffect (statePure s) $ m *> get
62−
63−statePure :: s -> State s
64−statePure s0 = State
65− { _ref = s0
66− , _get = pure
67− , _put = \_ -> pure
68− , _state = \s f -> f s
69− }
63+execState s0 = reinterpretM (SP.execState s0) statePure
64+
65+statePure
66+ :: SP.State s :> es
67+ => (forall r. Eff es0 r -> Eff es r)
68+ -> State s (Eff es0) a
69+ -> Eff es a
70+statePure run = \case
71+ Get -> SP.get
72+ Put s -> SP.put s
73+ State f -> SP.state f
74+ StateM f -> SP.stateM (run . f)
7075
7176----------------------------------------
7277-- MVar
7378
7479runStateMVar :: s -> Eff (State s : es) a -> Eff es (a, s)
75−runStateMVar s m = do
76− v <- impureEff_ $ newMVar s
77− evalEffect (stateMVar v) $ (,) <$> m <*> get
80+runStateMVar s0 = reinterpretM (SM.runState s0) stateMVar
7881
7982evalStateMVar :: s -> Eff (State s : es) a -> Eff es a
80−evalStateMVar s m = do
81− v <- impureEff_ $ newMVar s
82− evalEffect (stateMVar v) m
83+evalStateMVar s0 = reinterpretM (SM.evalState s0) stateMVar
8384
8485execStateMVar :: s -> Eff (State s : es) a -> Eff es s
85−execStateMVar s m = do
86− v <- impureEff_ $ newMVar s
87− evalEffect (stateMVar v) $ m *> get
88−
89−stateMVar :: MVar s -> State s
90−stateMVar v0 = State
91− { _ref = v0
92− , _get = impureEff_ . readMVar
93− , _put = \v s -> impureEff_ . modifyMVar v $ \_ ->
94− s `seq` pure (s, v)
95− , _state = \v f -> impureEff $ \es -> modifyMVar v $ \s0 -> do
96− (a, s) <- unEff (f s0) es
97− s `seq` pure (s, (a, v))
98− }
86+execStateMVar s0 = reinterpretM (SM.execState s0) stateMVar
87+
88+stateMVar
89+ :: SM.State s :> es
90+ => (forall r. Eff es0 r -> Eff es r)
91+ -> State s (Eff es0) a
92+ -> Eff es a
93+stateMVar run = \case
94+ Get -> SM.get
95+ Put s -> SM.put s
96+ State f -> SM.state f
97+ StateM f -> SM.stateM (run . f)
9998
10099----------------------------------------
101100-- Operations
102101
103102get :: State s :> es => Eff es s
104−get = readerEffectM $ \State{..} -> _get _ref
103+get = send Get
104+
105+gets :: State s :> es => (s -> a) -> Eff es a
106+gets f = f <$> get
105107
106108put :: State s :> es => s -> Eff es ()
107−put s = stateEffectM $ \State{..} -> do
108− ref <- _put _ref s
109− pure ((), State { _ref = ref, .. })
109+put = send . Put
110110
111−state :: State s :> es => (s -> (a, s)) -> Eff es a
112−state f = stateEffectM $ \State{..} -> do
113− (a, ref) <- _state _ref $ pure . f
114− pure (a, State { _ref = ref, .. })
111+state :: (State s :> es) => (s -> (a, s)) -> Eff es a
112+state = send . State
115113
116114modify :: State s :> es => (s -> s) -> Eff es ()
117115modify f = state (\s -> ((), f s))
118116
119117stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
120−stateM f = stateEffectM $ \State{..} -> do
121− (a, ref) <- _state _ref f
122− pure (a, State { _ref = ref, .. })
118+stateM = send . StateM
123119
124120modifyM :: State s :> es => (s -> Eff es s) -> Eff es ()
125121modifyM f = stateM (\s -> ((), ) <$> f s)
src/Effectful/State/MVar.hs+23−18
@@ -2,9 +2,9 @@
22--
33-- Represented as an MVar underneath, therefore:
44--
5−-- - slower than "Effectful.State"
5+-- - suitable for sharing between multiple threads,
66--
7−-- - suitable for sharing between multiple threads.
7+-- - slower than "Effectful.State".
88--
99module Effectful.State.MVar
1010 ( State
@@ -12,6 +12,7 @@ module Effectful.State.MVar
1212 , evalState
1313 , execState
1414 , get
15+ , gets
1516 , put
1617 , state
1718 , modify
@@ -21,49 +22,53 @@ module Effectful.State.MVar
2122
2223import Control.Concurrent.MVar
2324
24−import Effectful.Internal.Has
25+import Effectful.Internal.Effect
2526import Effectful.Internal.Monad
2627
2728-- | Provide access to a synchronized, mutable state of type @s@.
28−newtype State s = State (MVar s)
29+newtype State s :: Effect where
30+ State :: MVar s -> State s m r
2931
3032runState :: s -> Eff (State s : es) a -> Eff es (a, s)
3133runState s m = do
32− v <- impureEff_ $ newMVar s
33− evalEffect (State v) $ (,) <$> m <*> get
34+ v <- unsafeEff_ $ newMVar s
35+ evalEffect (IdE (State v)) $ (,) <$> m <*> get
3436
3537evalState :: s -> Eff (State s : es) a -> Eff es a
3638evalState s m = do
37− v <- impureEff_ $ newMVar s
38− evalEffect (State v) m
39+ v <- unsafeEff_ $ newMVar s
40+ evalEffect (IdE (State v)) m
3941
4042execState :: s -> Eff (State s : es) a -> Eff es s
4143execState s m = do
42− v <- impureEff_ $ newMVar s
43− evalEffect (State v) $ m *> get
44+ v <- unsafeEff_ $ newMVar s
45+ evalEffect (IdE (State v)) $ m *> get
4446
4547get :: State s :> es => Eff es s
4648get = do
47− State v <- getEffect
48− impureEff_ $ readMVar v
49+ IdE (State v) <- getEffect
50+ unsafeEff_ $ readMVar v
51+
52+gets :: State s :> es => (s -> a) -> Eff es a
53+gets f = f <$> get
4954
5055put :: State s :> es => s -> Eff es ()
5156put s = do
52− State v <- getEffect
53− impureEff_ . modifyMVar_ v $ \_ -> s `seq` pure s
57+ IdE (State v) <- getEffect
58+ unsafeEff_ . modifyMVar_ v $ \_ -> s `seq` pure s
5459
5560state :: State s :> es => (s -> (a, s)) -> Eff es a
5661state f = do
57− State v <- getEffect
58− impureEff_ . modifyMVar v $ \s0 -> let (a, s) = f s0 in s `seq` pure (s, a)
62+ IdE (State v) <- getEffect
63+ unsafeEff_ . modifyMVar v $ \s0 -> let (a, s) = f s0 in s `seq` pure (s, a)
5964
6065modify :: State s :> es => (s -> s) -> Eff es ()
6166modify f = state (\s -> ((), f s))
6267
6368stateM :: State s :> es => (s -> Eff es (a, s)) -> Eff es a
6469stateM f = do
65− State v <- getEffect
66− impureEff $ \es -> modifyMVar v $ \s0 -> do
70+ IdE (State v) <- getEffect
71+ unsafeEff $ \es -> modifyMVar v $ \s0 -> do
6772 (a, s) <- unEff (f s0) es
6873 s `seq` pure (s, a)
6974
src/Effectful/Writer.hs+21−11
@@ -9,33 +9,43 @@ module Effectful.Writer
99 , listens
1010 ) where
1111
12−import Data.Coerce
13−import qualified Data.Semigroup as S
12+import Control.Monad.Catch
1413
15−import Effectful.Internal.Has
14+import Effectful.Internal.Effect
15+import Effectful.Internal.Env
1616import Effectful.Internal.Monad
1717
1818-- | Provide access to a write only value of type @w@.
19−newtype Writer w = Writer w
20− deriving (S.Semigroup, Monoid)
19+newtype Writer w :: Effect where
20+ Writer :: w -> Writer w m r
2121
2222runWriter :: Monoid w => Eff (Writer w : es) a -> Eff es (a, w)
23−runWriter = fmap coerce . runEffect mempty
23+runWriter m = do
24+ (a, IdE (Writer w)) <- runEffect (IdE (Writer mempty)) m
25+ pure (a, w)
2426
2527execWriter :: Monoid w => Eff (Writer w : es) a -> Eff es w
26−execWriter = fmap coerce . execEffect mempty
28+execWriter m = do
29+ IdE (Writer w) <- execEffect (IdE (Writer mempty)) m
30+ pure w
2731
2832writer :: (Writer w :> es, Monoid w) => (a, w) -> Eff es a
29−writer (a, w) = stateEffect $ \w0 -> (a, w0 `mappend` Writer w)
33+writer (a, w) = stateEffect $ \(IdE (Writer w0)) -> (a, IdE (Writer (w0 `mappend` w)))
3034
3135tell :: (Writer w :> es, Monoid w) => w -> Eff es ()
32−tell w = stateEffect $ \w0 -> ((), w0 `mappend` Writer w)
36+tell w = stateEffect $ \(IdE (Writer w0)) -> ((), IdE (Writer (w0 `mappend` w)))
3337
3438listen
35− :: (Writer w :> es, Monoid w)
39+ :: forall w es a. (Writer w :> es, Monoid w)
3640 => Eff es a
3741 -> Eff es (a, w)
38−listen = fmap (\(a, Writer w) -> (a, w)) . listenEffect
42+listen (Eff m) = unsafeEff $ \es -> mask $ \restore -> do
43+ w0 <- unsafeStateEnv (\(IdE (Writer w)) -> (w, IdE (Writer mempty))) es
44+ -- If an exception is thrown, restore e0 and keep parts of e1.
45+ a <- restore (m es) `onException`
46+ unsafeModifyEnv (\(IdE (Writer w)) -> IdE (Writer (w0 `mappend` w))) es
47+ w1 <- unsafeStateEnv (\(IdE (Writer w)) -> (w, IdE (Writer (w0 `mappend` w)))) es
48+ pure (a, w1)
3949
4050listens
4151 :: (Writer w :> es, Monoid w)
tests/Main.hs+36−6
@@ -8,8 +8,9 @@ import qualified Control.Monad.Catch as E
88import qualified Control.Exception.Lifted as LE
99import qualified UnliftIO.Exception as UE
1010
11−import Effectful
12−import Effectful.State
11+import Effectful.Interpreter
12+import Effectful.State.Dynamic
13+import Effectful.Monad
1314
1415main :: IO ()
1516main = defaultMain $ testGroup "effectful"
@@ -20,6 +21,7 @@ main = defaultMain $ testGroup "effectful"
2021 , testCase "deep stack" test_deepStack
2122 , testCase "exceptions" test_exceptions
2223 , testCase "concurrency" test_concurrentState
24+ , testCase "local effects" test_localEffects
2325 ]
2426 ]
2527
@@ -88,7 +90,7 @@ test_exceptions = runEffIO $ do
8890 modify @Int (+2)
8991
9092test_concurrentState :: Assertion
91−test_concurrentState = runEffIO . evalState x $ do
93+test_concurrentState = runEffIO . runHasInt x $ do
9294 replicateConcurrently_ 2 $ do
9395 r <- goDownward 0
9496 assertEqual_ "x = n" x r
@@ -96,13 +98,41 @@ test_concurrentState = runEffIO . evalState x $ do
9698 x :: Int
9799 x = 1000000
98100
99− goDownward :: State Int :> es => Int -> Eff es Int
100− goDownward acc = get @Int >>= \case
101+ goDownward :: HasInt :> es => Int -> Eff es Int
102+ goDownward acc = getInt >>= \case
101103 0 -> pure acc
102104 n -> do
103− put $ n - 1
105+ putInt $ n - 1
104106 goDownward $ acc + 1
105107
108+test_localEffects :: Assertion
109+test_localEffects = runEffIO $ do
110+ x <- runHasInt 0 $ do
111+ putInt 1
112+ execState () $ do
113+ putInt 2
114+ execState () $ do
115+ putInt 4
116+ getInt
117+ assertEqual_ "correct x" 4 x
118+
119+runHasInt :: Int -> Eff (HasInt : es) a -> Eff es a
120+runHasInt n =
121+ -- reinterpret with redundant local effects
122+ reinterpret (evalState () . evalState n . evalState True) $ \case
123+ GetInt -> get
124+ PutInt i -> put i
125+
126+data HasInt :: Effect where
127+ GetInt :: HasInt m Int
128+ PutInt :: Int -> HasInt m ()
129+
130+getInt :: HasInt :> es => Eff es Int
131+getInt = send GetInt
132+
133+putInt :: HasInt :> es => Int -> Eff es ()
134+putInt = send . PutInt
135+
106136----------------------------------------
107137-- Helpers
108138