22

FreeT / ProgramT によって作成されたモナド変換子の mtl のようなメカニズムはありますか?

私の歴史認識は以下の通りです。むかしむかし、モナド変換子が発明されました。その後、人々はモナド変換子を積み重ねるようになり、liftどこにでも挿入するのが煩わしいことに気づきました。その後、ask :: m r何人かの人々がモナドクラスを発明しましmMonadReader 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 などで詳しく説明しています。

Monad Transformer でリフトを回避する

次に、私の問題は、新しい Free モナドhttp://hackage.haskell.org/package/freeと Operational モナドhttp://hackage.haskell.org/package/operationalにあります。代数型として言語を定義するだけで、独自の DSL を記述してモナドとして使用できます(Operational はインスタンスdataさえ必要としません)。Functor良いニュースは、モナドとモナド変換子を無料で持てるということです。ではモナドクラスはどうだろうか?悪いニュースは、「独自のモナド変換子を定義することはめったにない」という仮定がもはや成り立たなくなったことです。

この問題を理解しようとして、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

私の質問は、どうすれば Operational モナドが互いに浸透しやすくなるかということです。または、不適切なポーズの操作モナドを貫通したいという私の願いです。

また、侵入の正しい専門用語も知りたいです:)

4

1 に答える 1

6

私は少し異なるアプローチを試みましたが、少なくとも部分的な答えが得られました。モナドを積み重ねると問題が発生する場合があり、すべてのモナドが何らかのデータ型から構築されていることがわかっているため、代わりにデータ型を結合しようとしました。

のほうが使いやすいMonadFreeので使ってみましたが、同様のアプローチも使えるのではないでしょうかOperational

データ型の定義から始めましょう。

{-# 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

フリーモナドで使用するために 2 つのファンクターを結合するために、それらの共積を定義しましょう。

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

上のフリーモナドを作成するEitherF f gと、両方からコマンドを呼び出すことができます。このプロセスを透過的にするために、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'

追加のインスタンスを定義せずに。


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

基本ファンクターが 1 つしかない場合は、次のように実行できます。

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'

2 つある場合は、両方の状態をスレッド化する必要があります。

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'

iter :: Functor f => (f a -> a) -> Free f a -> a1つの可能性は、 from freeを使用してファンクターの実行を表現し、同様の組み合わせ関数を作成することだと思います

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

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

于 2013-08-02T11:22:10.193 に答える