GTF*_*GTF 4 error-handling monads haskell monad-transformers
我有一个连接到数据库然后运行查询的函数。每个步骤都会产生IO (Either SomeErrorType SomeResultType).
在学习 Haskell 时,我真正喜欢使用 monad 和类似 monad 的原因之一Either是能够使用 monad 函数(例如>>=和 组合器)mapLeft来简化对预期错误状态的大量处理。
通过阅读博客文章、Control.Monad.Trans文档和其他关于 SO 的答案,我的期望是我必须以某种方式使用转换器/电梯从一个IO上下文移动到另一个Either上下文。
这个答案特别好,但我正在努力将其应用到我自己的案例中。
我的代码的一个更简单的示例:
simpleVersion :: Integer -> Config -> IO ()
simpleVersion id c =
connect c >>= \case
(Left e) -> printErrorAndExit e
(Right conn) -> (run . query id $ conn)
>>= \case
(Left e) -> printErrorAndExit e
(Right r) -> print r
>> release conn
Run Code Online (Sandbox Code Playgroud)
ExceptT我的问题是(a)我并没有真正理解如何让我到达与世界相似的地方的机制mapLeft handleErrors $ eitherErrorOrResult >>= someOtherErrorOrResult >>= print;(b) 我不确定如何确保连接始终以最好的方式释放(即使在上面的简单示例中),尽管我想我会使用方括号模式。
我确信每个(相对)新的 Haskeller 都这么说,但我仍然真的不理解 monad 转换器,而且我读到的所有内容(除了前面链接的 SO 答案)对我来说都太不透明了(还)。
如何将上面的代码转换为删除所有这些嵌套和错误处理的代码?
我认为查看以下实例的来源非常有启发Monad性ExceptT:
newtype ExceptT e m a = ExceptT (m (Either e a))
instance (Monad m) => Monad (ExceptT e m) where
return a = ExceptT $ return (Right a)
m >>= k = ExceptT $ do
a <- runExceptT m
case a of
Left e -> return (Left e)
Right x -> runExceptT (k x)
Run Code Online (Sandbox Code Playgroud)
如果忽略newtype包装和展开,它会变得更简单:
m >>= k = do
a <- m
case a of
Left e -> return (Left e)
Right x -> k x
Run Code Online (Sandbox Code Playgroud)
或者,正如您似乎不喜欢使用do:
m >>= k = m >>= \a -> case a of
Left e -> return (Left e)
Right x -> k x
Run Code Online (Sandbox Code Playgroud)
你觉得那段代码很熟悉吗?它和您的代码之间的唯一区别是您编写printErrorAndExit而不是return . Left!因此,让我们将其移至printErrorAndExit顶层,并高兴地暂时记住错误而不是打印它。
simpleVersion :: Integer -> Config -> IO (Either Err ())
simpleVersion id c = connect c >>= \case (Left e) -> return (Left e)
(Right conn) -> (run . query id $ conn)
>>= \case (Left e) -> return (Left e)
(Right r) -> Right <$> (print r
>> release conn)
Run Code Online (Sandbox Code Playgroud)
除了我所说的更改之外,您还必须Right <$>在末尾添加一个以从一个IO ()动作转换为一个IO (Either Err ())动作。(稍后会详细介绍这一点。)
好的,让我们尝试ExceptT用上面的绑定替换上面的IO绑定。我将添加一个'来区分ExceptT版本和IO版本(例如>>=' :: IO (Either Err a) -> (a -> IO (Either Err b)) -> IO (Either Err b))。
simpleVersion id c = connect c >>=' \conn -> (run . query id $ conn)
>>=' \r -> Right <$> (print r
>> {- IO >>! -} release conn)
Run Code Online (Sandbox Code Playgroud)
这已经是一个进步了,一些空白的改变让它变得更好。我还将包含一个do版本。
simpleVersion id c =
connect c >>=' \conn ->
(run . query id $ conn) >>=' \r ->
Right <$> (print r >> release conn)
simpleVersion id c = do
conn <- connect c
r <- run . query id $ conn
Right <$> (print r >> release conn)
Run Code Online (Sandbox Code Playgroud)
对我来说,这看起来很干净!当然,在 中main,您仍然需要printErrorAndExit,如:
main = do
v <- runExceptT (simpleVersion 0 defaultConfig)
either printErrorAndExit pure v
Run Code Online (Sandbox Code Playgroud)
现在,关于这个Right <$> (...)......我说我想从 转换IO a为IO (Either Err a)。嗯,这种事情就是MonadTrans类存在的原因;让我们看看它的实现ExceptT:
instance MonadTrans (ExceptT e) where
lift = ExceptT . liftM Right
Run Code Online (Sandbox Code Playgroud)
嗯,liftM和(<$>)是相同的函数,但名称不同。因此,如果我们忽略newtype包装和展开,我们会得到
lift m = Right <$> m
Run Code Online (Sandbox Code Playgroud)
!所以:
simpleVersion id c = do
conn <- connect c
r <- run . query id $ conn
lift (print r >> release conn)
Run Code Online (Sandbox Code Playgroud)
liftIO如果您愿意,也可以选择使用。不同之处在于,lift总是通过一个变压器提升一元动作,但适用于任何一对包裹类型和变压器类型;while通过 monad 变压器堆栈所需的尽可能多的变压器提升动作,但仅适用于liftIO动作。IOIO
当然,到目前为止我们已经省略了所有的newtype包装和展开。为了simpleVersion像我们最后一个示例中一样漂亮,您需要更改connect并run适当地包含这些包装器。
| 归档时间: |
|
| 查看次数: |
1169 次 |
| 最近记录: |