I am playing around with freer-simple and trying to work out how to combine effects.
I have an algebra to represent a simple file system and user invoked failure as follows:
data FileSystem r where
ReadFile :: Path a File -> FileSystem String
WriteFile :: Path a File -> String -> FileSystem ()
readFile :: Member FileSystem effs => Path a File -> Eff effs String
readFile = send . ReadFile
writeFile :: Member FileSystem effs => Path a File -> String -> Eff effs ()
writeFile pth = send . WriteFile pth
data AppError r where
Ensure :: Bool -> String -> AppError ()
Fail :: String -> AppError ()
ensure :: Member AppError effs => Bool -> String -> Eff effs ()
ensure condition message = send $ Ensure condition message
fail :: Member AppError effs => String -> Eff effs ()
fail = send . Fail
And an "application" in a function called interactor as follows:
data TestItem = Item {
pre :: String,
post :: String,
path :: Path Abs File
}
data RunConfig = RunConfig {
environment :: String,
depth :: Integer,
path :: Path Abs File
}
type FileSys r = (Member FileSystem r)
type AppFailure r = (Member AppError r)
interactor :: TestItem -> RunConfig -> (AppFailure r, FileSys r) => Eff r ApState
interactor item runConfig = do
let fullFilePath = path (runConfig :: RunConfig)
writeFile fullFilePath $ pre item <> post item
fail "random error ~ its a glitch"
txt <- readFile [absfile|C:\Vids\SystemDesign\Wrong.txt|]
pure $ ApState fullFilePath txt
At this stage I am only interested in dumb "documentation" interpreters that log the steps, I don't even care what failing would do in terms of control flow:
fileSystemDocInterpreter :: FileSystem ~> Eff '[Writer [String], effs]
fileSystemDocInterpreter =
let
mockContents = "Mock File Contents"
in
\case
ReadFile path -> tell ["readFile: " <> show path] $> mockContents
WriteFile path str -> tell ["write file: " <>
show path <>
"\nContents:\n" <>
str]
errorDocInterpreter :: AppError ~> Eff '[Writer [String]]
errorDocInterpreter = \case
Ensure condition errMsg -> tell [condition ? "Ensure Check Passed" $
"Ensure Check Failed ~ " <> errMsg]
Fail errMsg -> tell ["Failure ~ " <> errMsg]
The combined interpreter is as follows:
type FileSys r = (Member FileSystem r)
type AppFailure r = (Member AppError r)
executeDocumented :: forall a. Eff '[FileSystem, AppError] a -> ((a, [String]), [String])
executeDocumented app = run $ runWriter
$ reinterpret errorDocInterpreter
$ runWriter
$ reinterpret fileSystemDocInterpreter app
When I run this with sample configs I get something like the following:
((ApState {
filePath = "C:\\Vids\\SystemDesign\\VidList.txt",
fileText = "Mock File Contents"
},
["write file: \"C:\\\\Vids\\\\SystemDesign\\\\VidList.txt\
"\nContents: I do a test the test runs",
"readFile: \"C:\\\\Vids\\\\SystemDesign\\\\Wrong.txt\""]
),
["Failure ~ random error ~ its a glitch"]
)
I have a couple of questions about the interpreters above:
The order this to compile I had to make the types as follows:
fileSystemDocInterpreter :: FileSystem ~> Eff '[Writer [String], effs] errorDocInterpreter :: AppError ~> Eff '[Writer [String]]and call
errorDocInterpreterafterfileSystemDocInterpreterbecause thefileSystemDocInterpreterhas trailing effs anderrorDocInterpreterdoesn't.Is there a way to change the type signatures or invoke these so it wouldn't matter which was needed first by the parent interpreter?
Both fileSystemDocInterpreter and errorDocInterpreter use the Writer [String] effect. Is there a way to combine these so runWriter is only called once so the failure and file system messages appear in one log?