提前,很抱歉这篇长文。
我正在用 Haskell 编写一个事件驱动的应用程序,因此我需要存储几个回调函数以供进一步使用。我希望这样的回调是:
- 丰富:使用
ReaderT
,ErrorT
,StateT
而不是裸IO
s ; - 多态:类型
(MonadIO m, MonadReader MyContext m, MonadState MyState m, MonadError MyError m) => m ()
,而不是ReaderT MyContext (StateT MyState (ErrorT MyError IO)))
为了简单起见,让我们忘记State
and层。Error
我开始写所有回调的记录,存储在里面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 ()
我找不到解决方法。
如果有人能启发我,我将不胜感激。如果有的话,设计这个的好方法是什么?
预先感谢您的意见。
[编辑]据我了解 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
?