Freer-Simple Freer Monads 如何统一 IO 异常处理和错误效果
Freer-Simple Freer Monads How do I Unify IO Exception Handling with Error Effect
我正在使用 freer-simple 编写一个超级简单的 DSL。它所做的只是读取一个文件。
我有一个关于文件名的规则,它们不能包含字母 x。任何尝试打开其中包含字母 x 的文件的结果都会是:Left (AppError "No Xs allowed in file name")
。
在 fileSystemIOInterpreter
中读取文件时如何捕获 IO 错误并将其作为应用程序错误抛出? IE。我正在尝试将选定的 IO 异常转换为 AppErrors(请参阅 ??????
)。
{- File System Lang -}
data FileSystem r where
ReadFile :: Path a File -> FileSystem StrictReadResult
readFile :: Members '[FileSystem, Error AppError] effs => Path a File -> Eff effs StrictReadResult
readFile path = let
pthStr = toStr $ toFilePath path
in
F.elem 'x' pthStr
? throwError (AppError "No Xs allowed in file name")
$ send $ ReadFile path
{- Errors -}
newtype AppError = AppError String deriving Show
runAppError :: Eff (Error AppError ': r) a -> Eff r (Either AppError a)
runAppError = runError
{- File System IO Interpreter -}
fileSystemIOInterpreter :: forall effs a. (Members '[Error AppError] effs, LastMember IO effs) => Eff (FileSystem ': effs) a -> Eff effs a
fileSystemIOInterpreter = interpretM $ \case
ReadFile path -> F.readFileUTF8 path
-- ??????
-- this compiles: fileSystemIOInterpreter effs = throwError $ AppError "BLahh"
application :: Members '[FileSystem, Error AppError] effs => Path a File -> Eff effs StrictReadResult
application = readFile
ioApp :: Path a File -> IO (Either AppError StrictReadResult)
ioApp path = runM
$ runAppError
$ fileSystemIOInterpreter
$ application path
-- running the app
demoPassApp = ioApp [absfile|C:\Vids\SystemDesign\VidList.md|]
>> Right (Right "Text content of VidList.md")
demoFailApp = ioApp [absfile|C:\Vids\SystemDesign\VidList.txt|]
>> Left (AppError "No Xs allowed in file name")
demoFailIOApp = ioApp [absfile|C:\Vids\SystemDesign\MissingFile.md|]
>> *** Exception: C:\Vids\SystemDesign\MissingFile.md: openBinaryFile: does not exist (No such file or directory)
-- I want to turn this into an AppError
interpretM
在 IO
中有一个解释器(its first argument has type eff ~> m
和 m ~ IO
在这里),所以这不允许你通过 AppError
抛出Members '[Error AppError] effs
约束。
您可以使用 interpret
,完全访问 effs
。大致看起来像:
fileSystemIOInterpreter
:: forall effs a
. (Members '[Error AppError] effs, LastMember IO effs)
=> Eff (FileSystem ': effs) a -> Eff effs a
fileSystemIOInterpreter = interpret $ \case
ReadFile path -> do
r <- sendM (try (F.readFileUTF8 path))
case r of
Left (e :: IOException) -> throwError (ioToAppErr e)
Right f -> pure f
-- for some value of
ioToAppErr :: IOException -> AppError
这是一个完整的工作示例:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Data.String
import qualified Data.ByteString.Char8 as B
import Control.Exception
import Data.List
import Data.Text (Text, pack, unpack)
import Data.Text.IO
import Data.Text.Encoding (decodeUtf8)
import Control.Natural (type (~>))
import qualified Control.Monad.Freer.Error as ER
import Control.Monad.Freer
(
Eff
, LastMember
, Member
, Members
, interpret
, send
, sendM
, runM
)
readFileUTF8 :: String -> IO Text
readFileUTF8 path = decodeUtf8 <$> B.readFile path
ioToAppErr :: IOException -> AppError
ioToAppErr ioe = AppError (displayException ioe)
newtype AppError = AppError String deriving Show
data FileSystem r where
ReadFile :: FilePath -> FileSystem Text
readFile :: Members '[FileSystem, ER.Error AppError] effs => FilePath -> Eff effs Text
readFile fpath = if (elem 'x' fpath)
then (ER.throwError (AppError "No Xs allowed in file name"))
else(send $ ReadFile fpath)
runAppError :: Eff (ER.Error AppError ': r) a -> Eff r (Either AppError a)
runAppError = ER.runError
fileSystemIOInterpreter
:: (Members '[ER.Error AppError] effs, LastMember IO effs)
=> Eff (FileSystem ': effs) a -> Eff effs a
fileSystemIOInterpreter = interpret $ \case
ReadFile path -> do
r <- sendM (try (readFileUTF8 path))
case r of
Left (e :: IOException) -> ER.throwError (ioToAppErr e)
Right f -> pure f
application :: Members '[FileSystem, ER.Error AppError] effs => FilePath -> Eff effs Text
application = Main.readFile
ioApp :: FilePath -> IO (Either AppError Text)
ioApp path = runM
$ runAppError
$ fileSystemIOInterpreter
$ application path
main :: IO ()
main = do
let pathX = "C:\text.info"
let pathNoX = "C:\simple.t"
let pathNoSuchAFile = "C:\habrahabr.bib"
result <- ioApp pathX
Data.Text.IO.putStrLn $ pack (show result)
依赖项:
- 基础 >= 4.7 && < 5
- 文字
- 更简单
- 自然变换
- 字节串
我正在使用 freer-simple 编写一个超级简单的 DSL。它所做的只是读取一个文件。
我有一个关于文件名的规则,它们不能包含字母 x。任何尝试打开其中包含字母 x 的文件的结果都会是:Left (AppError "No Xs allowed in file name")
。
在 fileSystemIOInterpreter
中读取文件时如何捕获 IO 错误并将其作为应用程序错误抛出? IE。我正在尝试将选定的 IO 异常转换为 AppErrors(请参阅 ??????
)。
{- File System Lang -}
data FileSystem r where
ReadFile :: Path a File -> FileSystem StrictReadResult
readFile :: Members '[FileSystem, Error AppError] effs => Path a File -> Eff effs StrictReadResult
readFile path = let
pthStr = toStr $ toFilePath path
in
F.elem 'x' pthStr
? throwError (AppError "No Xs allowed in file name")
$ send $ ReadFile path
{- Errors -}
newtype AppError = AppError String deriving Show
runAppError :: Eff (Error AppError ': r) a -> Eff r (Either AppError a)
runAppError = runError
{- File System IO Interpreter -}
fileSystemIOInterpreter :: forall effs a. (Members '[Error AppError] effs, LastMember IO effs) => Eff (FileSystem ': effs) a -> Eff effs a
fileSystemIOInterpreter = interpretM $ \case
ReadFile path -> F.readFileUTF8 path
-- ??????
-- this compiles: fileSystemIOInterpreter effs = throwError $ AppError "BLahh"
application :: Members '[FileSystem, Error AppError] effs => Path a File -> Eff effs StrictReadResult
application = readFile
ioApp :: Path a File -> IO (Either AppError StrictReadResult)
ioApp path = runM
$ runAppError
$ fileSystemIOInterpreter
$ application path
-- running the app
demoPassApp = ioApp [absfile|C:\Vids\SystemDesign\VidList.md|]
>> Right (Right "Text content of VidList.md")
demoFailApp = ioApp [absfile|C:\Vids\SystemDesign\VidList.txt|]
>> Left (AppError "No Xs allowed in file name")
demoFailIOApp = ioApp [absfile|C:\Vids\SystemDesign\MissingFile.md|]
>> *** Exception: C:\Vids\SystemDesign\MissingFile.md: openBinaryFile: does not exist (No such file or directory)
-- I want to turn this into an AppError
interpretM
在 IO
中有一个解释器(its first argument has type eff ~> m
和 m ~ IO
在这里),所以这不允许你通过 AppError
抛出Members '[Error AppError] effs
约束。
您可以使用 interpret
,完全访问 effs
。大致看起来像:
fileSystemIOInterpreter
:: forall effs a
. (Members '[Error AppError] effs, LastMember IO effs)
=> Eff (FileSystem ': effs) a -> Eff effs a
fileSystemIOInterpreter = interpret $ \case
ReadFile path -> do
r <- sendM (try (F.readFileUTF8 path))
case r of
Left (e :: IOException) -> throwError (ioToAppErr e)
Right f -> pure f
-- for some value of
ioToAppErr :: IOException -> AppError
这是一个完整的工作示例:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Data.String
import qualified Data.ByteString.Char8 as B
import Control.Exception
import Data.List
import Data.Text (Text, pack, unpack)
import Data.Text.IO
import Data.Text.Encoding (decodeUtf8)
import Control.Natural (type (~>))
import qualified Control.Monad.Freer.Error as ER
import Control.Monad.Freer
(
Eff
, LastMember
, Member
, Members
, interpret
, send
, sendM
, runM
)
readFileUTF8 :: String -> IO Text
readFileUTF8 path = decodeUtf8 <$> B.readFile path
ioToAppErr :: IOException -> AppError
ioToAppErr ioe = AppError (displayException ioe)
newtype AppError = AppError String deriving Show
data FileSystem r where
ReadFile :: FilePath -> FileSystem Text
readFile :: Members '[FileSystem, ER.Error AppError] effs => FilePath -> Eff effs Text
readFile fpath = if (elem 'x' fpath)
then (ER.throwError (AppError "No Xs allowed in file name"))
else(send $ ReadFile fpath)
runAppError :: Eff (ER.Error AppError ': r) a -> Eff r (Either AppError a)
runAppError = ER.runError
fileSystemIOInterpreter
:: (Members '[ER.Error AppError] effs, LastMember IO effs)
=> Eff (FileSystem ': effs) a -> Eff effs a
fileSystemIOInterpreter = interpret $ \case
ReadFile path -> do
r <- sendM (try (readFileUTF8 path))
case r of
Left (e :: IOException) -> ER.throwError (ioToAppErr e)
Right f -> pure f
application :: Members '[FileSystem, ER.Error AppError] effs => FilePath -> Eff effs Text
application = Main.readFile
ioApp :: FilePath -> IO (Either AppError Text)
ioApp path = runM
$ runAppError
$ fileSystemIOInterpreter
$ application path
main :: IO ()
main = do
let pathX = "C:\text.info"
let pathNoX = "C:\simple.t"
let pathNoSuchAFile = "C:\habrahabr.bib"
result <- ioApp pathX
Data.Text.IO.putStrLn $ pack (show result)
依赖项:
- 基础 >= 4.7 && < 5
- 文字
- 更简单
- 自然变换
- 字节串