52

モナド変換子 (または 1 つのモナド変換子) のスタックが を超える問題がありますIO。すべてのアクションの前にリフトを使用するのがひどく面倒であることを除いて、すべてが良いです! 本当にどうしようもないと思いますが、とにかく聞いてみようと思いました。

ブロック全体を持ち上げることは承知していますが、コードが実際に混合タイプの場合はどうなるでしょうか? GHC が何らかの構文糖衣 (たとえば、<-$= <- lift) を入れたらいいと思いませんか?

4

2 に答える 2

61

すべての標準mtlモナドについては、まったく必要ありませんliftgetputasktell— それらはすべて、スタック内のどこかに適切な変換子を持つ任意のモナドで機能します。欠落している部分はIOでありliftIO、任意の IO アクションを任意の数のレイヤーに持ち上げます。

これは、提供されている各「効果」の型クラスで行われます。たとえば、MonadStateprovidesgetput. newtypeTransformer スタックの周りに独自のラッパーを作成する場合deriving (..., MonadState MyState, ...)は、GeneralizedNewtypeDeriving拡張機能を使用するか、独自のインスタンスをロールすることができます。

instance MonadState MyState MyMonad where
  get = MyMonad get
  put s = MyMonad (put s)

これを使用して、いくつかのインスタンスを定義し、他のインスタンスを定義しないことにより、結合された変圧器のコンポーネントを選択的に表示または非表示にすることができます。

(このアプローチは、独自の型クラスを定義し、標準のトランスフォーマーのボイラープレート インスタンスを提供することで、独自に定義したまったく新しいモナド効果に簡単に拡張できますが、まったく新しいモナドはまれです。ほとんどの場合、単純にやり遂げることができます。 mtl.が提供する標準セットを構成します。)

于 2012-01-29T16:45:06.860 に答える
52

具体的なモナド スタックの代わりに型クラスを使用することで、関数をモナド非依存にすることができます。

たとえば、次の関数があるとします。

bangMe :: State String ()
bangMe = do
  str <- get
  put $ str ++ "!"
  -- or just modify (++"!")

もちろん、トランスフォーマーとしても機能することがわかっているので、次のように書くことができます。

bangMe :: Monad m => StateT String m ()

ただし、別のスタックを使用する関数がある場合は、たとえばReaderT [String] (StateT String IO) ()、または何でも、恐ろしいlift関数を使用する必要があります! では、それはどのように回避されるのでしょうか。

State秘訣は、モナドがモナドスタックのどこにでも現れることができるように、関数のシグネチャをさらに汎用的にすることです。これは次のように行われます。

bangMe :: MonadState String m => m ()

これにより、モナド スタック内のどこでも (仮想的に) 状態をサポートするモナドになることが強制mされ、関数はそのようなスタックを持ち上げることなく機能します。

ただし、1 つ問題があります。IOは の一部ではないためmtl、デフォルトではトランスフォーマー (例: IOT) も便利な型クラスもありません。では、IO アクションを任意に持ち上げたい場合はどうすればよいでしょうか?

救助に来るMonadIO!などとほとんど同じように動作しますがMonadStateMonadReader唯一の違いは、持ち上げメカニズムがわずかに異なることです。これは次のように機能します: 任意のIOアクションを実行liftIOして、それをモナド不可知バージョンに変えるために使用できます。そう:

action :: IO ()
liftIO action :: MonadIO m => m ()

使用したいすべてのモナド アクションをこのように変換することにより、退屈な持ち上げを行うことなく、好きなだけモナドを絡み合わせることができます。

于 2012-01-29T16:48:12.057 に答える