@@ -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+ }