如何将蒙版添加到单子堆栈

时间:2019-03-24 17:53:21

标签: haskell monad-transformers

我正在尝试使用Exception.Safe Package中的括号函数,该函数的返回类型为

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) 

理想情况下,我希望函数返回运行处理程序返回的内容,即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)

1 个答案:

答案 0 :(得分:1)

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

  

MonadMask IO

也就是说,您可以将bracketIO和另一个实例

一起使用
  

MonadMask m => MonadMask(ReaderT m)

也就是说,如果基本monad是MonadMask的实例,则该monad上的ReaderT也是MonadMask的实例。

请注意,MonadMask不是属于monad堆栈的变压器。相反,它是一个约束条件,说明“此monad堆栈支持屏蔽/包围操作”。


如果您使用的是type synonym之类的

type KafkaP a = ReaderT ProducerConfig IO a

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


您在评论中提到KafkaP是新类型。新类型是一种便宜的方法,可以轻松地从另一个类型创建新类型。基本上,它是一个保存原始类型值的构造函数。

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

该怎么办?好吧,假设已导出newtype构造函数(有时为了封装目的将它们隐藏),则可以先将KafkaP的动作包装到ReaderT ProducerConfig IO a中,然后再将其发送到bracket中,然后重新包装结果再次输入KafkaP。其中一些参数是KafkaP返回函数,因此您可能还需要抛出一些函数组合。也许类似(假设KafkaP是构造函数的名称,而runKafkaP是相应访问器的名称):

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

所有这些包裹和展开都是乏味的;有时使用Data.Coerce中的coerce会有所帮助。但这仅在导出newtype构造函数时有效。


您还可以考虑使用上述技术为MonadMask定义自己的KafkaP实例。可以做到这一点,但是在定义类型类的模块和定义类型的模块中都没有定义的实例称为orphan instances并且有些皱眉。