我正在玩freer-simple,并试图找出如何组合效果。
我有一个代数来表示一个简单的文件系统和用户调用的失败,如下所示:
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
还有一个称为“交互器”的函数中的“应用程序”,如下所示:
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
在这个阶段,我只对记录步骤的笨拙的“文档”解释器感兴趣,我什至不在乎 在控制流方面失败将导致什么:
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]
组合的解释器如下:
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
当我使用示例配置运行此程序时,我得到如下内容:
((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"]
)
关于上述口译员,我有几个问题:
要编译的顺序我必须使类型如下:
fileSystemDocInterpreter :: FileSystem ~> Eff '[Writer [String], effs]
errorDocInterpreter :: AppError ~> Eff '[Writer [String]]
并在errorDocInterpreter
之后致电fileSystemDocInterpreter
,因为
fileSystemDocInterpreter
的效果不佳,errorDocInterpreter
的效果不佳。
有没有一种方法可以更改类型签名或调用它们,所以没关系 父母口译员首先需要哪个?
fileSystemDocInterpreter和errorDocInterpreter都使用Writer [String]效果。 有没有一种方法可以将它们组合在一起,因此runWriter仅被调用一次,因此故障和文件系统 消息出现在一个日志中?
答案 0 :(得分:1)
Eff
类型的文档指出
通常,效果的具体列表不用于参数化Eff。而是使用一个或多个Member约束来表达对效果列表的约束,而无需将计算耦合到具体的效果列表。
因此,为了最大限度地提高灵活性,我们可以将fileSystemDocInterpreter
和errorDocInterpreter
的签名更改为:
fileSystemDocInterpreter :: Member (Writer [String]) effs => FileSystem ~> Eff effs
errorDocInterpreter :: Member (Writer [String]) effs => AppError ~> Eff effs
我们实际上并不关心Writer [String]
在类型级别列表上的位置是什么,列表上是否还有其他影响。我们只需要Writer [String]
就可以了。此更改将解决(1)。
对于(2),我们可以如下定义executeDocumented
:
executeDocumented :: forall a. Eff '[FileSystem, AppError, Writer [String]] a
-> (a, [String])
executeDocumented app = run $ runWriter
$ interpret errorDocInterpreter
$ interpret fileSystemDocInterpreter
$ app
在这里,我们正在使用解释器中定义计算时获得的灵活性。我们将Writer [String]
放在列表的末尾,两个interpret
将FileSystem
和AppError
的效果发送给作者。无需有单独的Writer [String]
层! (也就是说,如果在其他情况下我们在列表的最前面有两个相同类型的效果,则可以使用subsume
来删除重复项。)
答案 1 :(得分:1)
我试图还原源代码以观察其工作原理
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Data.Monoid
import Data.Functor
import Data.List
import Data.String
import Control.Natural (type (~>))
import Control.Monad.Freer.Writer (Writer, tell,runWriter)
import Control.Monad.Freer
(
Eff
, LastMember
, Member
, interpret
, interpretM
, send
, run
, runM
)
data FileSystem r where
ReadFile :: FilePath -> FileSystem String
WriteFile :: FilePath -> String -> FileSystem ()
readFile :: Member FileSystem effs => FilePath -> Eff effs String
readFile = send . ReadFile
writeFile :: Member FileSystem effs => FilePath -> 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
data ApState = ApState {filePath::String,fileText::String} deriving Show
data TestItem = Item {
pre :: String,
post :: String,
pathTI :: FilePath
}
data RunConfig = RunConfig {
environment :: String,
depth :: Integer,
pathRC :: FilePath
}
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 = pathRC (runConfig :: RunConfig)
Main.writeFile fullFilePath $ pre item <> post item
Main.fail "random error ~ its a glitch"
txt <- Main.readFile "C:\\Vids\\SystemDesign\\Wrong.txt"
pure $ ApState fullFilePath txt
fileSystemDocInterpreter :: Member (Writer [String]) effs => FileSystem ~> Eff effs
fileSystemDocInterpreter =
let
mockContents::String = "Mock File Contents"
in
\case
ReadFile path -> tell ["readFile: " <> show path] $> mockContents
WriteFile path str -> tell ["write file: " <>
show path <>
"\nContents:\n" <>
str]
errorDocInterpreter :: Member (Writer [String]) effs => AppError ~> Eff effs
errorDocInterpreter = \case
Ensure condition errMsg -> tell [if condition then "Ensure Check Passed" else ("Ensure Check Failed ~ " <> errMsg) ]
Fail errMsg -> tell ["Failure ~ " <> errMsg]
executeDocumented :: forall a. Eff '[FileSystem, AppError, Writer [String]] a
-> (a, [String])
executeDocumented app = run $ runWriter
$ interpret errorDocInterpreter
$ interpret fileSystemDocInterpreter
$ app
main :: IO ()
main = do
let ti = Item {pre="", post ="", pathTI =""}
let rc = RunConfig {environment ="", depth =1, pathRC ="C:\\Vids\\SystemDesign\\VidList.txt"}
let (apst,messages) = executeDocumented $ interactor ti rc
putStrLn $ show apst
mapM_ (\x->putStrLn x) messages
putStrLn "_"