Как добавить маску в монадический стек - PullRequest
0 голосов
/ 24 марта 2019

Я пытаюсь использовать функцию скобок из Exception.Safe Package, который имеет тип возврата

forall m a b c. MonadMask m => m a -> (a -> m b) -> (a -> m c) -> m c

Это означает, что мой стек монад должен иметь монаду Маска, добавленную в стек?Моя вызывающая функция выглядит следующим образом

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’

Означает ли этостек монад должен быть примерно таким:

Mask (ReaderT ProducerConfig IO a) 

В идеале я бы хотел, чтобы функция возвращала то, что возвращает обработчик выполнения, например Config.KafkaP (E либо 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 Ответ

1 голос
/ 25 марта 2019

Если бы вы использовали тип ReaderT ProducerConfig IO a напрямую, проблем не было бы, потому что пакет exception предоставляет экземпляр

MonadMask IO

Это говорит о том, что вы можете использовать bracket с IO, а другой экземпляр

MonadMask m => MonadMask (ReaderT r m)

Это говорит о том, что если базовая монада является экземпляром MonadMask, то ReaderT над этой монадой также является экземпляром MonadMask.

Обратите внимание, что MonadMask не является преобразователем, который является частью стека монады. Вместо этого есть ограничение, которое говорит: «этот стек монад поддерживает операции маскирования / брекетинга».


Если вы использовали синоним типа как

type KafkaP a = ReaderT ProducerConfig IO a

проблем также не возникнет, потому что синонимы типа не создают новый тип, они просто дают псевдоним выходному. Все еще можно использовать все существующие экземпляры класса типов для типа.


Вы упоминаете в комментариях, что KafkaP - это новый тип. Новый тип - это дешевый способ создания нового типа из другого. В основном это конструктор, который содержит значение исходного типа.

И в этом проблема. Поскольку это новый тип, он не разделяет автоматически все экземпляры классов типов старого. Фактически, наличие различных экземпляров классов типов в новых типах является одним из основных мотивов использования новых типов!

Что можно сделать? Что ж, если предположить, что конструктор newtype экспортирован (иногда они скрыты для целей инкапсуляции), вы можете развернуть действия KafkaP s в ReaderT ProducerConfig IO a перед отправкой их в bracket, а затем снова преобразовать результат в KafkaP снова. Некоторые параметры являются KafkaP -возвратными функциями, поэтому вам, вероятно, придется добавить и некоторую композицию функций. Возможно, что-то вроде (при условии, что KafkaP - это имя конструктора, а runKafkaP - имя соответствующего метода доступа):

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

Вся эта обертка и распаковка утомительна; иногда использование coerce из Data.Coerce может помочь. Но это работает только при экспорте конструктора newtype.


Вы также можете подумать о том, чтобы определить свой собственный экземпляр MonadMask для KafkaP, используя описанную выше технику. Это может быть сделано, но экземпляры, которые не определены ни в модуле, который определяет класс типов, ни в модуле, который определяет тип, называются потерянными экземплярами и несколько недовольны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...