on master help sign in
Add README and an example
commit0ce4d7d65471ec6654d2d31997242b7105481a4f authorAndrzej Rybczak <andrzej@rybczak.net> dateFri 11 Jun 2021, 15:06 (5 years ago) parentc49ee87
2 files changed+90
README.md+27 examples/FileSystem.hs+63
README.mdnew file+27
@@ -0,0 +1,27 @@
1+# effective
2+
3+[![Build Status](https://github.com/arybczak/effective/workflows/Haskell-CI/badge.svg?branch=master)](https://github.com/arybczak/effective/actions?query=branch%3Amaster)
4+
5+An effect system based on the [ReaderT design pattern](https://www.fpcomplete.com/blog/2017/06/readert-design-pattern/).
6+
7+Main features:
8+
9+1. Very fast.
10+
11+2. Internals of the library are easy to reason about.
12+
13+3. Correct semantics in presence of runtime exceptions (no more lost or
14+ discarded state).
15+
16+4. Seamless integration with existing Haskell ecosystem (`exceptions`,
17+ `monad-control`, `unliftio-core`, `resourcet`).
18+
19+5. Effects can be defined for either static (as fast as it gets, single
20+ interpretation) or dynamic (slower, multiple interpretations) dispatch
21+ depending on your needs.
22+
23+## Example
24+
25+A `Filesystem` effect with two handlers, one that runs in `IO` and another that
26+uses an in-memory virtual file system can be found
27+[here](https://github.com/arybczak/effective/blob/master/examples/FileSystem.hs).
examples/FileSystem.hsnew file+63
@@ -0,0 +1,63 @@
1+module FileSystem
2+ ( FileSystem
3+ , FsError(..)
4+ , readFile
5+ , writeFile
6+ , runFileSystemIO
7+ , runFileSystemPure
8+ ) where
9+
10+import Control.Monad.Catch
11+import Prelude hiding (readFile, writeFile)
12+import qualified Control.Exception as E
13+import qualified Data.Map.Strict as M
14+import qualified System.IO as IO
15+
16+import Effective
17+import Effective.Error
18+
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+ }
25+
26+-- | File system error.
27+newtype FsError = FsError String
28+ deriving Show
29+instance Exception FsError
30+
31+----------------------------------------
32+-- Operations
33+
34+-- | Read contents of a file.
35+readFile :: (FileSystem :> es, Error FsError :> es) => FilePath -> String -> Eff es ()
36+readFile path contents = stateEffectM $ \FileSystem{..} -> do
37+ fs <- _writeFile _fs path contents
38+ pure ((), FileSystem { _fs = fs, .. })
39+
40+-- | Write contents to a file.
41+writeFile :: (FileSystem :> es, Error FsError :> es) => FilePath -> Eff es String
42+writeFile path = readerEffectM $ \FileSystem{..} -> _readFile _fs path
43+
44+----------------------------------------
45+-- Handlers
46+
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+ }
53+ 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+ }