gpt4 book ai didi

haskell - 如何使用 exceptT 替换大量 IO(a b)

转载 作者:行者123 更新时间:2023-12-05 00:52:44 25 4
gpt4 key购买 nike

我有一个连接到数据库的函数,然后运行查询。这些步骤中的每一个都会导致 IO (Either SomeErrorType SomeResultType)

我真正喜欢在学习 Haskell 时使用 Either 和类似的 monad 的一件事是能够使用像 >>= 这样的 monad 函数和像这样的组合子mapLeft 来简化许多预期错误状态的处理。

我在这里阅读博客文章、Control.Monad.Trans 文档以及关于 SO 的其他答案的期望是,我必须以某种方式使用转换器/升降机从 IO 上下文到 Either 上下文。

This answer特别是真的很好,但我很难将它应用到我自己的案例中。

我的代码的一个更简单的例子:

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

我的问题是 (a) 我并没有真正理解 ExceptT 如何将我带到与 mapLeft handleErrors $ anyErrorOrResult >>= someOtherErrorOrResult >>= 类似的地方的机制打印世界; (b) 我不确定如何确保始终以最好的方式释放连接(即使在我上面的简单示例中),尽管我想我会使用 bracket pattern .

我确信每个(相对)新的 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)

如果您忽略 newtype包装和展开,它变得更加简单:

m >>= k = do
a <- m
case a of
Left e -> return (Left e)
Right x -> k x

或者,您似乎不喜欢使用 do :

m >>= k = m >>= \a -> case a of
Left e -> return (Left e)
Right x -> k x

你觉得这段代码很眼熟吗?这与您的代码之间的唯一区别是您编写 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)

除了我所说的更改之外,您还必须粘贴 Right <$>最后从 IO () 转换对 IO (Either Err ()) 采取行动行动。 (稍后会详细介绍。)

好的,让我们尝试替换我们的 ExceptT从上面绑定(bind) IO绑定(bind)。我将添加一个 '区分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)

这已经是一种改进,一些空白更改使它变得更好。我还将包括一个 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)

对我来说,这看起来很干净!当然,在 main ,你还是想printErrorAndExit ,如:

main = do
v <- runExceptT (simpleVersion 0 defaultConfig)
either printErrorAndExit pure v

现在,关于 Right <$> (...) ...我说我想从 IO a 转换至IO (Either Err a) .好吧,这种事情就是为什么 MonadTrans类存在;让我们看看 ExceptT 的实现:

instance MonadTrans (ExceptT e) where
lift = ExceptT . liftM Right

好吧,liftM(<$>)是同一个函数,名字不同。所以如果我们忽略 newtype包装和展开,我们得到

lift m = Right <$> m

!所以:

simpleVersion id c = do
conn <- connect c
r <- run . query id $ conn
lift (print r >> release conn)

您也可以选择使用 liftIO如果你喜欢。区别在于 lift总是通过一个变压器提升一元 Action ,但适用于任何一对包裹类型和变压器类型;而liftIO解除IO为您的 monad 变压器堆栈通过尽可能多的变压器进行操作,但仅适用于 IO行动。

当然,到目前为止,我们已经省略了所有 newtype包装和展开。对于simpleVersion要像我们最后一个示例中一样漂亮,您需要更改 connectrun酌情包含这些包装器。

关于haskell - 如何使用 exceptT 替换大量 IO(a b),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69327798/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com