Хранить полиморфные обратные вызовы в Haskell

Заранее извините за этот длинный пост.

Я пишу управляемое событиями приложение на Haskell, поэтому мне нужно хранить несколько функций обратного вызова для дальнейшего использования. Я хотел бы, чтобы такие обратные вызовы были:

  • обогащены : использование ReaderT, ErrorT, StateT, а не голых IO с;
  • полиморфный : типа (MonadIO m, MonadReader MyContext m, MonadState MyState m, MonadError MyError m) => m (), а не ReaderT MyContext (StateT MyState (ErrorT MyError IO)))

Давайте для простоты забудем о слоях State и Error. 1137]

Я начал писать записи всех обратных вызовов, хранящихся внутри MyContext, что-то вроде:

    data MyContext = MyContext { _callbacks :: Callbacks {- etc -} }

    -- In this example, 2 callbacks only
    data Callbacks = Callbacks {
        _callback1 :: IORef (m ()),
        _callback2 :: IORef (m ())}

Основная проблема: где поместить ограничения классов типов для m? Я попробовал следующее, но ничего не скомпилировало:

  • Я подумал, что могу параметризировать Callbacks с m, например:

    data (MonadIO m, MonadReader (MyContext m) m) => Callbacks m = Callbacks {
       _callback1 :: IORef (m ()),
       _callback2 :: IORef (m ())}
    

    Как Callbacks часть MyContext, последняя также должна быть параметризована, и это приводит к проблеме бесконечного типа (MonadReader (MyContext m) m).

  • Затем я подумал об использовании экзистенциальных квантификаторов :

    data Callbacks = forall m . (MonadIO m, MonadReader MyContext m) => Callbacks {
       _callback1 :: IORef (m ()),
       _callback2 :: IORef (m ())}
    

    Это работало нормально, пока я не написал действительный код, который регистрирует новый обратный вызов в Callbacks:

    register :: (MonadIO m, MonadReader MyContext m) => m () -> m ()
    register f = do
      (Callbacks { _callback1 = ref1 }) <- asks _callbacks -- Note the necessary use of pattern matching
      liftIO $ modifyIORef ref1 (const f)
    

    Но я получил следующую ошибку (упрощенно здесь):

    Could not deduce (m ~ m1)
      from the context (MonadIO m, MonadReader MyContext m)
        bound by the type signature for
             register :: (MonadIO m, MonadReader MyContext m) => m () -> m ()
      or from (MonadIO m1, MonadReader MyContext m1)
        bound by a pattern with constructor
             Callbacks :: forall (m :: * -> *).
                       (MonadIO m, MonadReader MyContext m) =>
                       IORef (m ())
                       -> IORef (m ())
                       -> Callbacks,
      Expected type: m1 ()
      Actual type: m ()
    

    Мне не удалось найти обходной путь.

Я был бы очень благодарен, если бы кто-то мог просветить меня. Что было бы хорошим способом разработки этого, если таковые имеются?

Заранее благодарю за ваши комментарии.

[EDIT] Насколько я понял ответ ysdx, я пытался параметризовать свои типы данных с помощью m без наложения каких-либо ограничений класса типов, но затем я не смог сделать Callbacks экземпляром Data.Default. ]; что-то вроде этого:

instance (MonadIO m, MonadReader (MyContext m) m) => Default (Callbacks m) where
  def = Callbacks {
    _callback1 = {- something that makes explicit use of the Reader layer -},
    _callback2 = return ()}

... в результате GHC пожаловался:

Variable occurs more often in a constraint than in the instance head
  in the constraint: MonadReader (MyContext m) m

Он предлагает использовать UndecidableInstances, но я слышал, что это было очень плохо, хотя я и не не знаю почему. Значит ли это, что я должен отказаться от использования Data.Default?

10
задан koral 21 August 2012 в 17:01
поделиться