Это будет длинно, потому что я не уверен, что начал это в правильном настроении, поэтому я собираюсь изложить свои мысли как можно яснее на каждом этапе пути. У меня есть два фрагмента кода, которые настолько минимальны, насколько я могу их сделать, так что не стесняйтесь их использовать.
Я начал с одного преобразователя FitStateT m a, который просто хранит состояние программы в данный момент и позволяет сохранять на диск:
data FitState = FitState
newtype FitStateT m a = FitStateT (StateT FitState m a) deriving (Monad, MonadTrans)
В какой-то момент в проекте я решил добавить haskeline в проект, который имеет некоторые типы данных, подобные этому:
-- Stuff from haskeline. MonadException is something that haskeline requires for whatever reason.
class MonadIO m => MonadException m
newtype InputT m a = InputT (m a) deriving (Monad, MonadIO)
Итак, мои подпрограммы в моем основном файле будут выглядеть примерно так:
myMainRoutineFunc :: (MonadException m, MonadIO m) => FitStateT (InputT m) ()
myMainRoutineFunc = do
myFitStateFunc
lift $ myInputFunc
return ()
К сожалению, по мере роста моей программы с этим возник ряд проблем. Основная проблема заключалась в том, что для каждой функции ввода, которую я запускал, мне приходилось поднимать ее перед запуском. Другая проблема заключается в том, что для каждой функции, которая запускала команду ввода, мне требовалось ограничение MonadException m. Также для любой функции, которая запускала функцию, связанную с fitstate, требовалось ограничение MonadIO m.
Вот код: https://gist.github.com/4364920
Поэтому я решил создать несколько классов, чтобы все это лучше подходило друг другу и немного подчистил типы. Моя цель - написать что-то вроде этого:
myMainRoutineFunc :: (MonadInput t m, MonadFitState t m) => t m ()
myMainRoutineFunc = do
myFitStateFunc
myInputFunc
return ()
Сначала я создал класс MonadInput для обертывания типа InputT, а затем моя собственная процедура стала бы экземпляром этого класса.
-- Stuff from haskeline. MonadException is something that haskeline requires for whatever reason.
class MonadIO m => MonadException m
newtype InputT m a = InputT (m a) deriving (Monad, MonadIO)
-- So I add a new class MonadInput
class MonadException m => MonadInput t m where
liftInput :: InputT m a -> t m a
instance MonadException m => MonadInput InputT m where
liftInput = id
Я добавил ограничение MonadException, чтобы мне не приходилось указывать его отдельно для каждой функции, связанной с вводом. Это потребовало добавления классов multiparamtype и гибких экземпляров, но полученный код был именно тем, что я искал:
myInputFunc :: MonadInput t m => t m (Maybe String)
myInputFunc = liftInput $ undefined
Затем я сделал то же самое для FitState. Я снова добавил ограничение MonadIO:
-- Stuff from my own transformer. This requires that m be MonadIO because it needs to store state to disk
data FitState = FitState
newtype FitStateT m a = FitStateT (StateT FitState m a) deriving (Monad, MonadTrans, MonadIO)
class MonadIO m => MonadFitState t m where
liftFitState :: FitStateT m a -> t m a
instance MonadIO m => MonadFitState FitStateT m where
liftFitState = id
Который снова отлично работает.
myFitStateFunc :: MonadFitState t m => t m ()
myFitStateFunc = liftFitState $ undefined
Затем я завернул свою основную процедуру в оболочку нового типа, чтобы я мог создавать экземпляры этих двух классов:
newtype Routine m a = Routine (FitStateT (InputT m) a)
deriving (Monad, MonadIO)
А затем экземпляр MonadInput:
instance MonadException m => MonadInput Routine m where
liftInput = Routine . lift
Работает отлично. Теперь для MonadFitState:
instance MonadIO m => MonadFitState Routine m where
liftFitState = undefined
-- liftFitState = Routine -- This fails with an error.
Ах, дерьмо, это не удается.
Couldn't match type `m' with `InputT m'
`m' is a rigid type variable bound by
the instance declaration at Stack2.hs:43:18
Expected type: FitStateT m a -> Routine m a
Actual type: FitStateT (InputT m) a -> Routine m a
In the expression: Routine
In an equation for `liftFitState': liftFitState = Routine
И я не знаю, что сделать, чтобы это заработало. Я не очень понимаю ошибку. Означает ли это, что мне нужно сделать FitStateT экземпляром MonadInput? Это кажется очень странным, это два совершенно разных модуля, не имеющих ничего общего. Любая помощь будет оценена по достоинству. Есть ли лучший способ получить то, что я ищу?
Завершенный код с ошибкой: https://gist.github.com/4365046