正しい心構えでこれに取り組んだかどうかわからないので、これは長いものになるでしょう。それで、私は道の各段階で私の考えをできるだけ明確に概説するつもりです。作成できる限り最小限のコードスニペットが2つあるので、それらを自由に使用してください。
私は、その時点でのプログラムの状態を保持し、ディスクに保存できる単一のトランスフォーマーFitStateTmaから始めました。
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 ()
残念ながら、私のプログラムが成長するにつれて、これには多くの問題がありました。主な問題は、実行したすべての入力関数について、実行する前に持ち上げる必要があることでした。もう1つの問題は、入力コマンドを実行するすべての関数について、MonadExceptionm制約が必要だったことです。また、fitstate関連の関数を実行する関数の場合、MonadIOm制約が必要でした。
コードは次のとおりです:https ://gist.github.com/4364920
そこで、これをもう少しうまく組み合わせて、タイプを少しクリーンアップするために、いくつかのクラスを作成することにしました。私の目標は、次のようなものを書くことができるようにすることです。
myMainRoutineFunc :: (MonadInput t m, MonadFitState t m) => t m ()
myMainRoutineFunc = do
myFitStateFunc
myInputFunc
return ()
最初に、InputT型をラップアラウンドするMonadInputクラスを作成しました。次に、自分のルーチンがこのクラスのインスタンスになります。
-- 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制約を追加して、入力関連の関数ごとに個別に指定する必要がないようにしました。これにはmultiparamtypeclassesとflexibleinstancesを追加する必要がありましたが、結果のコードはまさに私が探していたものでした。
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ラッパーにラップして、次の2つのクラスのインスタンスを作成できるようにしました。
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のインスタンスにする必要があるということですか?それは本当に奇妙に思えます、それらは共通点がない2つの完全に異なるモジュールです。どんな助けでもいただければ幸いです。私が探しているものを手に入れるためのより良い方法はありますか?
エラーのある完成したコード:https ://gist.github.com/4365046