haskell - 在 Haskell 中存储多态回调

标签 haskell callback monads polymorphism

提前,很抱歉这篇长文。

我正在用 Haskell 编写一个事件驱动的应用程序,因此我需要存储几个回调函数以供进一步使用。我希望这样的回调是:

  • 丰富:使用ReaderT , ErrorT , StateT而不是光秃秃的IO小号;
  • 多态:类型(MonadIO m, MonadReader MyContext m, MonadState MyState m, MonadError MyError m) => m () ,而不是 ReaderT MyContext (StateT MyState (ErrorT MyError IO)))

  • 让我们忘记 StateError层,为了简单起见。

    我开始编写所有回调的记录,存储在 MyContext 中, 就像是:
        data MyContext = MyContext { _callbacks :: Callbacks {- etc -} }
    
        -- In this example, 2 callbacks only
        data Callbacks = Callbacks {
            _callback1 :: IORef (m ()),
            _callback2 :: IORef (m ())}
    

    主要问题是:在哪里放置 m 的类型类约束?我尝试了以下,但没有编译:
  • 我想我可以参数化 Callbacksm如 :
    data (MonadIO m, MonadReader (MyContext m) m) => Callbacks m = Callbacks {
       _callback1 :: IORef (m ()),
       _callback2 :: IORef (m ())}
    

    CallbacksMyContext 的一部分,后者也必须参数化,这会导致无限类型问题(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 ()
    

    我找不到解决方法。

  • 如果有人能启发我,我将不胜感激。如果有的话,设计这个的好方法是什么?

    预先感谢您的意见。

    [编辑] 据我了解 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 ?

    最佳答案

    简单的改编(使东西编译):

    data MyContext m = MyContext { _callbacks :: Callbacks m }
    
    data Callbacks m = Callbacks {
      _callback1 :: IORef (m ()),
      _callback2 :: IORef (m ())}
    
    -- Needs FlexibleContexts:
    register :: (MonadIO m, MonadReader (MyContext m) m) => m () -> m ()
    register f = do
      (Callbacks { _callback1 = ref1 }) <- asks _callbacks
      liftIO $ modifyIORef ref1 (const f)
    

    但是 -XFlexibleContexts 是必需的。

    你真的需要 IORef 吗?为什么不使用简单的状态单子(monad)?
    import Control.Monad.State
    import Control.Monad.Reader.Class
    import Control.Monad.Trans
    
    data Callbacks m = Callbacks {
      _callback1 :: m (),
      _callback2 :: m ()
      }
    
    -- Create a "new" MonadTransformer layer (specialization of StateT):
    
    class Monad m => MonadCallback m where
      getCallbacks :: m (Callbacks m)
      setCallbacks :: Callbacks m -> m ()
    
    newtype CallbackT m a = CallbackT (StateT (Callbacks (CallbackT m) ) m a)
    
    unwrap (CallbackT x) = x
    
    instance Monad m => Monad (CallbackT m) where
      CallbackT x >>= f = CallbackT (x >>= f')
        where f' x = unwrap $ f x
      return a =  CallbackT $ return a
    instance Monad m => MonadCallback (CallbackT m) where
      getCallbacks = CallbackT $ get
      setCallbacks c = CallbackT $ put c
    instance MonadIO m => MonadIO (CallbackT m) where
      liftIO m = CallbackT $ liftIO m
    instance MonadTrans (CallbackT) where
      lift m = CallbackT $ lift m
    -- TODO, add other instances
    
    -- Helpers:
    
    getCallback1 = do
      c <- getCallbacks
      return $ _callback1 c
    
    -- This is you "register" function:
    setCallback1 :: (Monad m, MonadCallback m) => m () -> m ()
    setCallback1 f = do
      callbacks <- getCallbacks
      setCallbacks $ callbacks { _callback1 = f }   
    
    -- Test:
    
    test :: CallbackT IO ()
    test = do
      c <- getCallbacks
      _callback1 c
      _callback2 c
    
    main = runCallbackT test s
      where s = Callbacks { _callback1 = lift $ print "a" (), _callback2 = lift $ print "b" }
    

    即使没有 MonadIO,此代码代码也可以工作。

    定义“默认”似乎工作正常:
    instance (MonadIO m, MonadCallback m) => Default (Callbacks m) where
    def = Callbacks {
      _callback1 = getCallbacks >>= \c -> setCallbacks $ c { _callback2 = _callback1 c },
      _callback2 = return ()}
    

    关于haskell - 在 Haskell 中存储多态回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12044490/

    相关文章:

    haskell - Haskell中的这些方括号是什么?

    haskell - 为什么我可以在不提供 monad 的情况下调用 monadic 函数?

    haskell - 简单的 TCP 客户端

    haskell - Haskell相互递归的澄清

    javascript - Node js将html变量写入jade模板

    javascript - 如何在回调中访问正确的“this”?

    algorithm - 生成所有唯一的有向图,每个节点有 2 个输入

    haskell - Haskell 中的类型签名中的 `... -> t a ->...` 是什么意思?

    javascript - 有没有一种方法可以仅通过引用来执行 JavaScript 代码?

    haskell - Monads 本质上不是 "conceptual"糖吗?