这将是一个漫长的过程,因为我不确定我是否以正确的心态进行了讨论,所以我将在每一步中尽可能清楚地概述我的想法。我有两个尽可能少的代码片段,所以请随意使用它们。
我从单个转换器 FitStateT ma 开始,它只保存当时程序的状态并允许保存到磁盘:
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 约束,这样我就不必在每个与输入相关的函数上单独指定它。这需要添加多参数类型类和灵活实例,但生成的代码正是我想要的:
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