2013-07-30 6 views
21

FreeT/ProgramTで作成されたモナドトランスのためのmtlのようなメカニズムがありますか?フリー/動作可能なモナドトランスフォーマーを備えたモナドスタック貫入クラス?

私の歴史の理解は以下の通りです。かつて、モナド変圧器が発明されました。その後モナド変圧器を積み重ね始めてから、どこにでもliftを挿入するのは面倒です。次に、モナドクラスを発明した人が2人いました。任意のモナドにおけるask :: m rmMonadReader r mのようなものである。これはモナド変圧器のすべてのペアのインスタンス宣言の、このようなペアが必要

(Monoid w, MonadState s m) => MonadState s (WriterT w m)
MonadWriter w m => MonadWriter w (StateT s m)

のように、すべてのモナドクラスは、すべてのモナド変換子に浸透することによって可能であったので、Nモナドがあるとき変圧器があります。n^2がかかります。しかし、これは大きな問題ではありませんでした。なぜなら、人々はほとんどがあらかじめ定義されたモナドを使用し、自分自身を作成することはめったにないからです。これまでの話は分かりました。以下のQ & Aで:

Avoiding lift with Monad Transformers

その後、私の問題は、新しい無料モナドhttp://hackage.haskell.org/package/freeと運用モナドhttp://hackage.haskell.org/package/operationalです。彼らは、ある種の代数的な型(操作上はFunctorのインスタンスも必要ない)として言語を定義するだけで、私たち自身のDSLを書くことができ、それをモナドとして使うことができます。良いニュースは、私たちが無料でモナドとモナド変圧器を持つことができるということです。モナドクラスはどうですか?悪い知らせは、「私たちはモナド変圧器をほとんど定義しない」という仮定がもはや成り立たないという仮定であるということです。

この問題を理解しようとすると、私は2つのProgramTを作り、互いに浸透させました。

https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs

operationalパッケージには、モナドのクラスをサポートしていませんので、私は別の実装minioperationalを取って、私が必要として動作するように修正。 https://github.com/nushio3/minioperational

それでも、私は専門的なインスタンス宣言

instance (Monad m, Operational ILang m) => Operational ILang (ProgramT SLang m) where

次の形式の一般的な宣言は決定不能インスタンスにつながるため

を必要としていました。

instance (Monad m, Operational f m) => Operational f (ProgramT g m) where

私の質問はどのように我々はそれが簡単に私たちの業務モナドがお互いに浸透させるために作ることができるということです。または、私の望みは、操作上のモナドが病気になっていることを浸透させることです。

私も浸透 :)

答えて

6

の正しい専門用語を知りたいのですが、私は、少なくとも部分的な答えを与える少し異なるアプローチを、試してみました。モナドを積み重ねることは時々問題になることがあり、すべてのモナドが何らかのデータ型から構築されていることを知っているので、代わりにデータ型を組み合わせようとしました。

私はMonadFreeの方が快適だと感じましたので使用しましたが、同様のアプローチをOperationalでも使用できると思います。

私たちのデータ型の定義から始めましょう:のは、彼らの副生成物を定義でき、自由なモナドでそれらを使用するための2つのファンクタを組み合わせるために

{-# LANGUAGE DeriveFunctor, FlexibleContexts, 
      FlexibleInstances, FunctionalDependencies #-} 
import Control.Monad 
import Control.Monad.Free 

data SLang x = ReadStr (String -> x) | WriteStr String x 
    deriving Functor 
data ILang x = ReadInt (Int -> x) | WriteInt Int x 
    deriving Functor 

data EitherF f g a = LeftF (f a) | RightF (g a) 
    deriving Functor 

我々場合EitherF f g上に無料のモナドを作成すると、両方からコマンドを呼び出すことができます。このプロセスを透明にするために、我々はターゲット1にファンクタのそれぞれからの変換を許可するようにMPTCを使用することができます。

class Lift f g where 
    lift :: f a -> g a 
instance Lift f f where 
    lift = id 

instance Lift f (EitherF f g) where 
    lift = LeftF 
instance Lift g (EitherF f g) where 
    lift = RightF 

今、私たちはただliftを呼び出し、副生成物の中にいずれかの部分を変換することができます。

ヘルパー関数

wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a 
wrapLift = wrap . lift . fmap return 

で、私たちは最終的に私たちがファンクタに持ち上げることができるものからコマンドを呼び出すことができるように汎用的な機能を作成することができます。そして、

readStr :: (Lift SLang f, MonadFree f m) => m String 
readStr = wrapLift $ ReadStr id 

writeStr :: (Lift SLang f, MonadFree f m) => String -> m() 
writeStr x = wrapLift $ WriteStr x() 

readInt :: (Lift ILang f, MonadFree f m) => m Int 
readInt = wrapLift $ ReadInt id 

writeInt :: (Lift ILang f, MonadFree f m) => Int -> m() 
writeInt x = wrapLift $ WriteInt x() 

を、プログラムを表現することができますさらなるインスタンスを定義することなく、

myProgram :: (Lift ILang f, Lift SLang f, MonadFree f m) => m() 
myProgram = do 
    str <- readStr 
    writeStr "Length of that str is" 
    writeInt $ length str 
    n <- readInt 
    writeStr "you wanna have it n times; here we go:" 
    writeStr $ replicate n 'H' 

とすることができます。


上記のすべてがうまく動作しますが、問題は、そのように構成されたフリーモナドを一般的にどのように実行するかです。完全に包括的な、構成可能なソリューションを持つことも可能かどうかわかりません。

我々はただ一つの基地ファンクタを持っている場合は、我々は2を持っている場合は、我々は彼らの両方の状態をスレッドする必要が

runSLang :: Free SLang x -> String -> (String, x) 
runSLang = f 
    where 
    f (Pure x)    s = (s, x) 
    f (Free (ReadStr g)) s = f (g s) s 
    f (Free (WriteStr s' x)) _ = f x s' 

としてそれを実行することができます。

runBoth :: Free (EitherF SLang ILang) a -> String -> Int -> ((String, Int), a) 
runBoth = f 
    where 
    f (Pure x)      s i = ((s, i), x) 
    f (Free (LeftF (ReadStr g)))  s i = f (g s) s i 
    f (Free (LeftF (WriteStr s' x))) _ i = f x s' i 
    f (Free (RightF (ReadInt g)))  s i = f (g i) s i 
    f (Free (RightF (WriteInt i' x))) s _ = f x s i' 

私は1つを推測可能であれば、iter :: Functor f => (f a -> a) -> Free f a -> afreeから使用してファンクタを実行し、同様の結合機能を作成することが可能です。

iter2 :: (Functor f, Functor g) 
     => (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a 

しかし、私はそれを試してみる時間がありませんでした。

+0

ありがとうございます、Petr。あなたの助けを借りて、(* - > *)を使って2つの型のコンストラクタを組み合わせる方法を理解しました。 https://github.com/nushio3/practice/blob/master/operational/exe-src/test-06.hs コンポジット可能なインタプリタの作成も簡単です: https://github.com/nushio3/practice/ blob/master/operational/exe-src/test-07.hs 「OverlappingInstances」を犠牲にして、2つ以上の言語を作成することもできます。 https://github.com/nushio3/practice/blob/master/operational/exe-src/test-08.hs – nushio

関連する問題