如何将Mask添加到monadic堆栈

问题描述 投票:0回答:1

我试图使用Exception.Safe包中的括号函数,其返回类型为

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) 

理想情况下,我希望函数返回运行Handler返回的内容,即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)
haskell monad-transformers
1个回答
1
投票

如果您直接使用ReaderT ProducerConfig IO a类型,则不会有问题,因为exceptions包提供了一个实例

MonadMask IO

这表示你可以使用bracketIO,以及另一个实例

MonadMask m => MonadMask(ReaderT r m)

这就是说,如果基地monad是MonadMask的一个实例,那么ReaderT超过那个monad也是MonadMask的一个例子。

请注意,MonadMask不是作为monad堆栈一部分的变换器。相反,它是一个约束,说“这个monad堆栈支持屏蔽/包围操作”。


如果你使用的是type synonym

type KafkaP a = ReaderT ProducerConfig IO a

也没有问题,因为类型同义词不会创建新类型,它们只是为现有类型提供别名。人们仍然可以使用该类型的所有现有类型类实例。


你在评论中提到KafkaP是一个新类型。新类型是一种廉价的方式,可以创建另一种新类型。它基本上是一个构造函数,它包含原始类型的值。

这就是问题所在。因为它是一种新类型,所以它不会自动共享旧类型的所有类型类实例。实际上,在newtypes中使用不同的类型类实例是使用newtypes的主要动机之一!

可以做些什么?好吧,假设newtype构造函数被导出(有时它们被隐藏用于封装)你可以将KafkaPs动作解包到ReaderT ProducerConfig IO a中,然后再将它们发送到bracket,然后再将结果重新包装到KafkaP中。一些参数是KafkaP返回函数,所以你可能还需要输入一些函数组合。也许是这样的(假设KafkaP是构造函数的名称,runKafkaP是相应访问器的名称):

KafkaP $ bracket (runKafkaP mkProducer) (runKafkaP . clProducer) (runKafkaP . runHandler)

所有这些包装和展开都是乏味的;有时使用coerceData.Coerce可以提供帮助。但这仅在导出newtype构造函数时有效。


您可能还会考虑使用上述技术为MonadMask定义自己的KafkaP实例。这可以完成,但是在定义类型类的模块中或在定义类型的模块中既未定义的实例也称为orphan instances,并且有点不受欢迎。

© www.soinside.com 2019 - 2024. All rights reserved.