2016-07-05 9 views
6

だからtransformersに私はMonadIOがより一般的なMonadTransではなく、IO固有の理由は何ですか?

class (Monad m) => MonadIO m where 
    -- | Lift a computation from the 'IO' monad. 
    liftIO :: IO a -> m a 

instance MonadIO IO where 
    liftIO = id 

、見て私は、これはMonadTransと異なることの理由は、あなたが4つので構成されるモナド変圧器で作られたいくつかのM1T (M2T (M3T (M4T IO))) xを持っている場合、その後、あなたはlift . lift . lift . lift $ putStrLn "abc"が、あなたにはしたくないということであることを理解しますむしろちょうどliftIO $ putStrLn "abc"です。

しかし、IOのこの特異性は、上記の基本的な定義がliftIOのこの奇妙な再帰セットであると思われる場合、非常に奇妙なようです。 (ExceptT :~: MaybeT) IO xのようないくつかのコンビネータのためのnewtype宣言がなければならないのいずれかのようにそれはそう単一liftはあなたが必要とするすべてであると思われる、(私が?これはモナド変圧器の変圧器であると仮定)、または他のいくつかのマルチのparam型クラス

class (Monad m) => MonadEmbed e m 
    -- | Lift a computation from the `e` monad. 
    embed :: e a -> m a 

instance (Monad m) => MonadEmbed m m where 
    embed = id 

transformersは、MonadTransシーケンスをIOに根ざす必要がないように、これらのアプローチの1つを使用していないのはなぜですか?トランスフォーマーがすべての「その他の」エフェクトを処理して、一番下の唯一のものがIdentity(すでにreturn :: a -> m aで処理されています)またはIOのいずれかになりますか?または上記には、transformersライブラリには含まれていないUndecidableInstancesのようなものが必要ですか?または何?

+0

AFAIK 'トランスフォーマーズ 'は、マルチパラメータ型クラスを含む**ではありませんので、あなたの例はこの制限に従って"許可され​​ていません "。 'mtl'ライブラリは、' MonadState'のようないくつかの型付けと、何度も持ち上げることを避けるためのいくつかのインスタンスを提供します。 – Bakuriu

答えて

6

しかし、IOのためのこの特異性は非常に奇妙な

だ私は、これはIOに特異的であることを前提に挑戦。私はまた、mtlに他の多くのクラスがあります。たとえば、

class Monad m => MonadError e m | m -> e where 
    throwError :: e -> m a 
    catchError :: m a -> (e -> m a) -> m a 

class Monad m => MonadState s m | m -> s where 
    get :: m s 
    put :: s -> m() 
    state :: (s -> (a, s)) -> m a 

...などがあります。一般的に、モナドアクションを構築する "mtl"の方法は、これらの型 - 多型演算を使用することであるため、決してliftを必要としません - むしろ、適切な持ち上げられた型に演算を単モーフィングします。例えば、MonadError完全liftMaybe :: MonadMaybe m => Maybe a -> m a仮説置き換え:なくMaybe a値を持ち上げるよりも、一方はMaybe a値呼び出しthrowError、代わりNothingJustreturnのプロデューサを有することになります。

シングルリフトはあなたがこの提案に

を必要とするすべてである、あなたは(少なくとも)が必要になるように(ExceptT :~: MaybeT) IO xようないくつかのコンビネータのためのnewtype宣言がなければならないようにこれは、2つの異なるようです種類のリフト:m aからtrans m aへの1つのリフト、およびtrans m aから(trans' :~: trans) m aへの1つのリフト。両方の種類の持ち上げを処理する単一の操作がより均一であること。

一部のマルチのparam型クラスがなければならないようです。このアプローチは、最初は一見良さそうに見えます

class Monad m => MonadEmbed e m 
    -- | Lift a computation from the `e` monad. 
    embed :: e a -> m a 

instance Monad m => MonadEmbed m m where 
    embed = id 

、そうです。ただし、このクラスを作成して使用しようとすると、すべてのmtlクラスに機能的な依存関係が含まれている理由をすぐに知ることができます。インスタンスMonadEmbed m mは驚くほど選択しにくいです!

embed (Right()) :: Either String() 

のような非常に単純な例でさえ、あいまいなエラーです。結局のところ、aについてはRight 3 :: Either a()しか知りません - a ~ Stringはまだわかりませんので、MonadEmbed m mインスタンスを選択することはできません!)他のインスタンスのほとんどが同様の問題に遭遇すると思います。明白な関数の依存関係を追加すると、型推論の問題はなくなりますが、基底関数のチェックによって大幅に制限されます:望むかもしれないように、基本モナドからだけ持ち上げることができます。これは実際には痛い問題です(そして、 "mtl"の痛みは小さいので)mtlで行われていません。

つまり、transformers-baseパッケージを使用して楽しむことができます。非常に下部にある唯一のものは、どちらかIdentity(すでにreturn :: a -> m aで対応)またはIOになるように、変圧器はすべて「その他」の効果を取り扱うことだけで事実

ですか?

あなたが言うように、最も一般的な塩基はIO(我々はすでにMonadIOを持っている)、またはIdentity(1は、一般的にちょうどreturnと、純粋な計算ではなく、持ち上げたモナド計算を使用している)です。場合によってはSTが便利なベースモナドですが、以上のものを使用するのではなく、ST以上のトランスを使用するのは少し珍しいです。

5

Monad...クラスは、直接そのモナドは、変圧器のスタックの下に埋設されている場合にも動作するようにモナドの特徴的な動作を一般ほとんどに設計されたようです。これらの操作は通常、大きなセットではありません。 Stateは、必要ならば、putstatemodifyのいずれかが必要ですが、それだけです。

そうでないとIO; MonadIOクラスの多くの基本的なIO操作メソッドを作成することはむしろ非実用的です。あなたはもちろん、変換の機能としてliftIOを導入すればすべてを得ることができますが、これは常にハックのビットと考えられていました。

さらに:IOは、最も重要な非自明な基本モナドです。その理由だけで専用のリフト機能を持たせることは不適切だとは思わないでしょう。

:~:の問題は、トランスフォーマーが階層構造の単純なスタックではなくバイナリツリーを形成するということです。これにより、mtlクラスのアイデア全体がはるかに問題になります。

関連する問題