Заранее извините за этот длинный пост.
Я пишу управляемое событиями приложение на 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
?