4

正しい心構えでこれに取り組んだかどうかわからないので、これは長いものになるでしょう。それで、私は道の各段階で私の考えをできるだけ明確に概説するつもりです。作成できる限り最小限のコードスニペットが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

4

1 に答える 1

3

さて、最初に、ここにタイプがありliftFitStateます:

liftFitState :: MonadFitState t m => FitStateT m a -> t m a

そして、これが次のタイプですRoutine

Routine :: FitStateT (InputT m) a -> Routine m a

関数liftFitStateは、から変換される単一のラッパータイプを想定していますFitStateTが、Routineラップするトランスフォーマーの2つのレイヤーがあります。したがって、タイプは一致しません。

それを超えて、私はあなたがこれについて間違った方法で行っているのではないかと本当に疑っています。

まず、ライブラリではなくアプリケーションを作成している場合は、必要なすべてのモナド変換子を1つの大きなスタックにラップして、それをどこでも使用するのが一般的です。Identity通常、トランスフォーマーとして残す唯一の理由は、限られた数のベースモナド(、、、、、など)IOを切り替えるためSTですSTM。しかし、トランスフォーマースタックに必要なものすべてにIOが必要であり、STまたはを使用するつもりがない場合は、それでもやり過ぎですSTM

あなたの場合、最も単純なアプローチは明らかに次のようになります。

newtype App a = App { getApp :: StateT FitState (InputT IO) a }

MonadFoo...次に、必要なクラス(たとえば)を派生または手動で実装し、MonadIOそのスタックをどこでも使用します。

複数のレイヤーをいじくり回す代わりに、この方法で行うことの利点は、後で別のトランスフォーマーを追加する必要がある場合に、Haskelineを追加した方法ReaderT(たとえば、ある種のグローバルデータリソース用にを追加することを決定した場合)です。ラップされたスタックに追加するだけで、現在スタックを使用しているすべてのコードで違いがわかりません。


一方、実際に現在のアプローチを採用したい場合は、モナド変換子のリフティングイディオムが少し間違っています。基本的な持ち上げ操作はMonadTrans、すでに導出しているから行う必要があります。MonadFooクラスは一般に、各モナドの基本的な操作を一般的に提供するgetためputのものですMonadState

あなたは模倣しようとしているようです。これは、スタックの一番下から実際のモナドに到達するのに十分な時間までliftIO「ずっと持ち上げる」操作です。これは、スタックのどこにでも現れる可能性のあるトランスフォーマーにはあまり意味がありません。liftIO

独自のクラスが必要な場合は、のMonadFooようなクラスのソースを調べて、MonadStateそれらがどのように機能するかを確認してから、同じパターンに従うことをお勧めします。

于 2012-12-23T19:03:17.847 に答える