如何将 Mask 添加到 monadic 堆栈
How to add Mask to a monadic stack
我正在尝试使用 Exception.Safe 包中的括号函数,该包的 return 类型为
forall m a b c. MonadMask m => m a -> (a -> m b) -> (a -> m c) -> m c
这意味着我的 monad 堆栈必须将 Mask monad 添加到堆栈中?
我的调用函数如下所示
sendMessage :: String -> Config.KafkaP (Either KafkaError ())
sendMessage msg=do
getProperties <- producerProps
let
mkProducer = newProducer getProperties
clProducer (Left _) = return ()
clProducer (Right prod) = closeProducer prod
runHandler (Left err) = return $ Left err
runHandler (Right prod) = messageSender prod msg
res <- bracket mkProducer clProducer runHandler
return res
并且 config.KakfaP 具有类型
ReaderT ProducerConfig IO a
我得到的错误是,
No instance for (exceptions-0.10.0:Control.Monad.Catch.MonadMask
Config.KafkaP)
arising from a use of ‘bracket’
这是否意味着 monad 堆栈需要像这样
Mask (ReaderT ProducerConfig IO a)
理想情况下,我希望函数 return 运行 处理程序 returns 是 Config.KafkaP(KafkaError ())或任何更强大的东西。
根据答案添加解决方案
sendMessage :: String -> Config.KafkaP (Either KafkaError ())
sendMessage msg=do
getProperties <- producerProps
let
mkProducer = newProducer getProperties
--newProducer :: MonadIO m => ProducerProperties -> m (Either KafkaError KafkaProducer)
--mkProducer :: Config.KafkaP (Either KafkaError KafkaProducer)
clProducer (Left _) = return ()
clProducer (Right prod) = closeProducer prod
--closeProducer :: MonadIO m => KafkaProducer -> m ()
--clProducer :: Config.KafkaP (Either () ()) -- ??
runHandler (Left err) = return $ Left err
runHandler (Right prod) = messageSender prod msg
--messageSender :: KafkaProducer -> String -> Config.KafkaP (Either KafkaError ())
--runHandler :: Config.KafkaP (Either KafkaError ()) -- ??
Config.KafkaP $ bracket (Config.runK mkProducer) (Config.runK .clProducer) (Config.runK .runHandler)
如果你直接使用 ReaderT ProducerConfig IO a
类型,不会有问题,因为 exceptions 包提供了一个实例
MonadMask IO
这表示您可以将 bracket
与 IO
一起使用,另一个实例
MonadMask m => MonadMask (ReaderT r m)
也就是说,如果基础 monad 是 MonadMask
的一个实例,那么该 monad 上的 ReaderT
也是 MonadMask
的一个实例。
请注意,MonadMask
不是属于 monad 堆栈的转换器。相反,它是一个约束 "this monad stack supports masking / bracketing operations".
如果您使用的是 type synonym 之类的
type KafkaP a = ReaderT ProducerConfig IO a
也不会有问题,因为类型同义词不会创建新类型,它们只是给现有类型一个别名。仍然可以使用该类型的所有现有类型类实例。
你在评论中提到KafkaP
是一个新类型。新类型是从另一个创建 新类型 的廉价方法。它基本上是一个包含原始类型值的构造函数。
这就是问题所在。因为它是一种新类型,所以它不会自动共享旧类型的所有类型类实例。事实上,在新类型中拥有 不同的 类型类实例是使用新类型的主要动机之一!
可以做什么?好吧,假设导出了新类型的构造函数(有时出于封装的目的隐藏它们),您可以在将 KafkaP
s 操作发送到 bracket
之前将它们解包到 ReaderT ProducerConfig IO a
,然后重新包装结果又变成了KafkaP
。一些参数是 KafkaP
返回函数,因此您可能还需要加入一些函数组合。也许是这样的(假设 KafkaP
是构造函数的名称,而 runKafkaP
是相应访问器的名称):
KafkaP $ bracket (runKafkaP mkProducer) (runKafkaP . clProducer) (runKafkaP . runHandler)
所有这些包装和展开都很乏味;有时使用 Data.Coerce
中的 coerce
会有所帮助。但这仅在导出新类型构造函数时有效。
您可能还会考虑使用上述技术为 KafkaP
定义您自己的 MonadMask
实例。这可以做到,但是既没有在定义类型类的模块中也没有在定义类型的模块中定义的实例被称为 orphan instances 并且有点不受欢迎。
我正在尝试使用 Exception.Safe 包中的括号函数,该包的 return 类型为
forall m a b c. MonadMask m => m a -> (a -> m b) -> (a -> m c) -> m c
这意味着我的 monad 堆栈必须将 Mask monad 添加到堆栈中? 我的调用函数如下所示
sendMessage :: String -> Config.KafkaP (Either KafkaError ())
sendMessage msg=do
getProperties <- producerProps
let
mkProducer = newProducer getProperties
clProducer (Left _) = return ()
clProducer (Right prod) = closeProducer prod
runHandler (Left err) = return $ Left err
runHandler (Right prod) = messageSender prod msg
res <- bracket mkProducer clProducer runHandler
return res
并且 config.KakfaP 具有类型
ReaderT ProducerConfig IO a
我得到的错误是,
No instance for (exceptions-0.10.0:Control.Monad.Catch.MonadMask
Config.KafkaP)
arising from a use of ‘bracket’
这是否意味着 monad 堆栈需要像这样
Mask (ReaderT ProducerConfig IO a)
理想情况下,我希望函数 return 运行 处理程序 returns 是 Config.KafkaP(KafkaError ())或任何更强大的东西。
根据答案添加解决方案
sendMessage :: String -> Config.KafkaP (Either KafkaError ())
sendMessage msg=do
getProperties <- producerProps
let
mkProducer = newProducer getProperties
--newProducer :: MonadIO m => ProducerProperties -> m (Either KafkaError KafkaProducer)
--mkProducer :: Config.KafkaP (Either KafkaError KafkaProducer)
clProducer (Left _) = return ()
clProducer (Right prod) = closeProducer prod
--closeProducer :: MonadIO m => KafkaProducer -> m ()
--clProducer :: Config.KafkaP (Either () ()) -- ??
runHandler (Left err) = return $ Left err
runHandler (Right prod) = messageSender prod msg
--messageSender :: KafkaProducer -> String -> Config.KafkaP (Either KafkaError ())
--runHandler :: Config.KafkaP (Either KafkaError ()) -- ??
Config.KafkaP $ bracket (Config.runK mkProducer) (Config.runK .clProducer) (Config.runK .runHandler)
如果你直接使用 ReaderT ProducerConfig IO a
类型,不会有问题,因为 exceptions 包提供了一个实例
MonadMask IO
这表示您可以将 bracket
与 IO
一起使用,另一个实例
MonadMask m => MonadMask (ReaderT r m)
也就是说,如果基础 monad 是 MonadMask
的一个实例,那么该 monad 上的 ReaderT
也是 MonadMask
的一个实例。
请注意,MonadMask
不是属于 monad 堆栈的转换器。相反,它是一个约束 "this monad stack supports masking / bracketing operations".
如果您使用的是 type synonym 之类的
type KafkaP a = ReaderT ProducerConfig IO a
也不会有问题,因为类型同义词不会创建新类型,它们只是给现有类型一个别名。仍然可以使用该类型的所有现有类型类实例。
你在评论中提到KafkaP
是一个新类型。新类型是从另一个创建 新类型 的廉价方法。它基本上是一个包含原始类型值的构造函数。
这就是问题所在。因为它是一种新类型,所以它不会自动共享旧类型的所有类型类实例。事实上,在新类型中拥有 不同的 类型类实例是使用新类型的主要动机之一!
可以做什么?好吧,假设导出了新类型的构造函数(有时出于封装的目的隐藏它们),您可以在将 KafkaP
s 操作发送到 bracket
之前将它们解包到 ReaderT ProducerConfig IO a
,然后重新包装结果又变成了KafkaP
。一些参数是 KafkaP
返回函数,因此您可能还需要加入一些函数组合。也许是这样的(假设 KafkaP
是构造函数的名称,而 runKafkaP
是相应访问器的名称):
KafkaP $ bracket (runKafkaP mkProducer) (runKafkaP . clProducer) (runKafkaP . runHandler)
所有这些包装和展开都很乏味;有时使用 Data.Coerce
中的 coerce
会有所帮助。但这仅在导出新类型构造函数时有效。
您可能还会考虑使用上述技术为 KafkaP
定义您自己的 MonadMask
实例。这可以做到,但是既没有在定义类型类的模块中也没有在定义类型的模块中定义的实例被称为 orphan instances 并且有点不受欢迎。