4

だから私は最初の本格的な haskell プロジェクト全体にこの種のコードを持っています:

f :: (MonadTrans t) => ExceptT () (t (StateT A B)) C
f = do mapExceptT lift $ do
    lift $ do
        ...
        lift $ do
            ...
            r <- ...
            ...
            return r
    >>= \r -> ...

私の目標を達成しようとする方法には間違いなく何か問題があるかもしれません (もっと簡単な方法があるかもしれません) が、現在、モナド変換子のスタックをより良い方法で処理する方法を学ぶことに興味があります。rこれは、スタック内のより高いモナドにコンテキストを取得してB持ち上げる方法を見つけた唯一の方法です。最初のステートメントの代わりにブロック全体を持ち上げることは、私が自分でできる限りです。

私がしばしば行き着くのは、深いモナドがliftである場合に回避できることがわかったチェーンです。ただし、他のモナドの一般的な方法については知りません。liftIOIO

そのようなスタックを処理し、あるレベルで 1 つの値を抽出し、別のレベルで別の値を抽出し、これらを組み合わせて 2 つのレベルのいずれかに影響を与えたり、さらに別のレベルに影響を与えたりする必要がある場合に従うことができるパターンはありますか?

ブロック全体を持ち上げたり (letバインドされた変数の範囲を限定したり、内側のブロックに制限したりする)、lift . lift . ... lift個々のアクションを実行することなく、スタックを何らかの方法で操作できますか?

4

2 に答える 2

6

これは、一般にモナド変換子に関するよく知られた問題です。研究者はそれを処理するためのさまざまな方法を考案しましたが、どれも明らかに「最善」ではありません。既知のソリューションには次のものがあります。

  • モナドのmtl型クラスを組み込みのモナド変換子 (および組み込みのモナド変換子のみ) に自動的に持ち上げるアプローチ。f :: (MonadState A m, MonadError () m) => m Cこれにより、関数が使用しているモナドの唯一の機能である場合にのみ書くことができます。移植性が極端に低いことと、その他のいくつかの理由により、mtl一般的に疑似非推奨と見なされます。詳細については、このページこの質問を参照してください。
  • 繰り返し使用している特定のモナド スタックがある場合は、それを でラップし、newtypeそれがサポートするさまざまなモナド型クラスのインスタンスを手動で記述することができます。、、、およびスタック内のトップレベル トランスフォーマーによって実装されるその他の型クラスについては、 を使用FunctorApplicativeて、コンパイラにインスタンスを自動的に書き込むようにさせることができます。他の型クラスの場合、各メソッドに適切な数の呼び出しを挿入する必要があります。このアプローチの利点は、呼び出しサイトで同じ柔軟性を提供しながら、より一般的で理解しやすいことです。MonadGeneralizedNewtypeDerivingliftmtl. このアプローチの大きな問題は、必要な操作のみを指定するのではなく、すべての操作に対して単一の「メガモナド」を使用することを推奨することです。スタックに新しいモナド変換子を追加するには、まったく新しいインスタンスのリストを作成する必要があるためです。
  • ほとんどの場合、「型の任意の状態A」と「任意の例外スロー機能」を持つモナドは本当に必要ありません。むしろ、モナド スタックによって提供されるさまざまな機能は、プログラムのメンタル モデルにおいて意味的な意味を持ちます。前のアプローチのバリエーションは、代わりに 'd モナドのカスタム型クラスの基本的なFunctorApplicativeMonadおよび write インスタンスを超えた効果のカスタム型クラスを作成することです。newtypeこれには、ここにリストされている他のアプローチよりも大きな利点があります。異なる位置に同じモナド変換子の複数のコピーを持つスタックを持つことができます。これは、これまで自分のプログラムで最も多く使用してきた戦略です。
  • まったく異なるアプローチがエフェクト システムです。通常、効果システムは言語の型システムに組み込む必要がありますが、効果システムを Haskell の型システムにエンコードすることは可能です。effect-monadsパッケージを参照してください。
于 2015-09-13T16:28:11.290 に答える
4

通常のアプローチは、直接mtl使用するのではなく、ライブラリを使用することです。transformersあなたの背後にある話が何であるかはわかりませんtが、通常のmtlアプローチは、定義サイトで非常に一般的な型シグネチャを使用することです。

foo :: (MonadError e m, MonadState s m) => m Int

次に、呼び出しサイトで実際のトランスフォーマー スタックを修正します。一般的な推奨事項は、スタックを newtype でラップして、使用されている場所を混乱させないようにすることです。

これがあなたのスタイルではない (そして万人向けではない) 場合でもmtl、明示的なトランスフォーマー スタックを提供しながら、メソッドを使用して操作を実行できます。これにより、手動で持ち上げる作業が大幅に削減されます。このアプローチの利点は、定義サイトでの効果の相互作用をよりよく把握できることです。欠点は、より多くのコードがすべての情報を必要とすることです。

于 2015-09-13T16:04:02.570 に答える