使用freer-simple之类的库时,如何合并效果解释器?

时间:2018-08-25 10:54:42

标签: haskell free-monad

我正在玩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"]
 )

关于上述口译员,我有几个问题:

  1. 要编译的顺序我必须使类型如下:

    fileSystemDocInterpreter :: FileSystem ~> Eff '[Writer [String], effs] 
    
    errorDocInterpreter :: AppError ~> Eff '[Writer [String]]
    

    并在errorDocInterpreter之后致电fileSystemDocInterpreter,因为 fileSystemDocInterpreter的效果不佳,errorDocInterpreter的效果不佳。

    有没有一种方法可以更改类型签名或调用它们,所以没关系 父母口译员首先需要哪个?

  2. fileSystemDocInterpreter和errorDocInterpreter都使用Writer [String]效果。 有没有一种方法可以将它们组合在一起,因此runWriter仅被调用一次,因此故障和文件系统 消息出现在一个日志中?

2 个答案:

答案 0 :(得分:1)

Eff类型的文档指出

  

通常,效果的具体列表不用于参数化Eff。而是使用一个或多个Member约束来表达对效果列表的约束,而无需将计算耦合到具体的效果列表。

因此,为了最大限度地提高灵活性,我们可以将fileSystemDocInterpretererrorDocInterpreter的签名更改为:

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]放在列表的末尾,两个interpretFileSystemAppError的效果发送给作者。无需有单独的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 "_"
相关问题