Haskell Language
無料のMonads
サーチ…
フリーモナドは、モナド計算をデータ構造とインタプリタに分割します。
例えば、プロンプトから読み書きするコマンドを含む計算:
まず、Functorのデータ型としての計算の「コマンド」について説明します
{-# LANGUAGE DeriveFunctor #-}
data TeletypeF next
= PrintLine String next
| ReadLine (String -> next)
deriving Functor
次にFree
を使用して「Free Monad over TeletypeF
」を作成し、いくつかの基本操作を作成します。
import Control.Monad.Free (Free, liftF, iterM)
type Teletype = Free TeletypeF
printLine :: String -> Teletype ()
printLine str = liftF (PrintLine str ())
readLine :: Teletype String
readLine = liftF (ReadLine id)
f
がFunctor
であるときは、 Free f
はMonad
ので、標準Monad
( do
notationを含む)を使ってTeletype
計算を構築することができます。
import Control.Monad -- we can use the standard combinators
echo :: Teletype ()
echo = readLine >>= printLine
mockingbird :: Teletype a
mockingbird = forever echo
最後に、 Teletype a
価値のあるものに変える「通訳」を書いて、私たちはIO a
ように働く方法を知っています。
interpretTeletype :: Teletype a -> IO a
interpretTeletype = foldFree run where
run :: TeletypeF a -> IO a
run (PrintLine str x) = putStrLn *> return x
run (ReadLine f) = fmap f getLine
Teletype a
をIO
計算を「実行」するために使用できるもの
> interpretTeletype mockingbird
hello
hello
goodbye
goodbye
this will go on forever
this will go on forever
無料のMonadsは固定小数点のようです
Free
の定義とFix
の定義を比較する:
data Free f a = Return a
| Free (f (Free f a))
newtype Fix f = Fix { unFix :: f (Fix f) }
特に、 Free
コンストラクタの型とFix
コンストラクタの型を比較します。 Free
Fix
と同様のファンクタがありますが、 Free
には別のReturn a
caseがあります。
foldFreeとiterMはどのように機能しますか?
iterM :: (Functor f, Monad m) => (f (ma) -> ma) -> (Free fa -> ma)
: iterM :: (Functor f, Monad m) => (f (ma) -> ma) -> (Free fa -> ma)
とfoldFree :: Monad m => (forall x. fx -> mx) -> (Free fa -> ma)
iterM :: (Functor f, Monad m) => (f (ma) -> ma) -> (Free fa -> ma)
をfoldFree :: Monad m => (forall x. fx -> mx) -> (Free fa -> ma)
て、 Free
計算を別のモナドm
解釈することによってiterM :: (Functor f, Monad m) => (f (ma) -> ma) -> (Free fa -> ma)
foldFree :: Monad m => (forall x. fx -> mx) -> (Free fa -> ma)
彼らは何をしていますか?
まずTeletype a
関数をIO
手動で解釈させるために必要なことを見てみましょう。 Free fa
が定義されていると見ることができます
data Free f a
= Pure a
| Free (f (Free f a))
Pure
ケースは簡単です:
interpretTeletype :: Teletype a -> IO a
interpretTeletype (Pure x) = return x
interpretTeletype (Free teletypeF) = _
では、 Free
コンストラクタでビルドされたTeletype
計算をどのように解釈するのですか? teletypeF :: TeletypeF (Teletype a)
調べてIO a
型の値に到達したいと考えています。まず、 runIO :: TeletypeF a -> IO a
の1つのレイヤーをIO
アクションにマッピングする関数runIO :: TeletypeF a -> IO a
を記述します。
runIO :: TeletypeF a -> IO a
runIO (PrintLine msg x) = putStrLn msg *> return x
runIO (ReadLine k) = fmap k getLine
これで、 runIO
を使用して残りのinterpretTeletype
を記入することができます。 teletypeF :: TeletypeF (Teletype a)
は、残りのFree
計算を含むTeletypeF
ファンクタの層であることを思い出してください。私たちはrunIO
を使って最外層を解釈します( runIO teletypeF :: IO (Teletype a)
を持っていrunIO teletypeF :: IO (Teletype a)
)。そして、 IO
モナドの>>=
コンビネータを使って返されたTeletype a
を解釈します。
interpretTeletype :: Teletype a -> IO a
interpretTeletype (Pure x) = return x
interpretTeletype (Free teletypeF) = runIO teletypeF >>= interpretTeletype
定義foldFree
まさにそれのあるinterpretTeletype
ことを除いて、 runIO
機能は、因数分解されています。その結果、 foldFree
は、特定のベースファンクタとターゲットモナドとは独立して動作します。
foldFree :: Monad m => (forall x. f x -> m x) -> Free f a -> m a
foldFree eta (Pure x) = return x
foldFree eta (Free fa) = eta fa >>= foldFree eta
foldFree
はランク2型です: eta
は自然な変換です。 foldFree
与えられたMonad m => (f (Free fa) -> m (Free fa)) -> Free fa -> ma
であるが、 f
層内のFree
計算を検査する自由をeta
に与える。 foldFree
与えるfoldFree
このより制限的なタイプは、 eta
が一度に1つのレイヤーしか処理できないようにします。
iterM
は、折り畳み関数にサブ計算を検査する機能を与えます。以前の反復の(モナド)結果は、 f
のパラメータの中の次のもので利用できます。 iterM
に類似してparamorphism一方foldFree
似ているcatamorphism 。
iterM :: (Monad m, Functor f) => (f (m a) -> m a) -> Free f a -> m a
iterM phi (Pure x) = return x
iterM phi (Free fa) = phi (fmap (iterM phi) fa)
フリーヤーモナド
Freer(またはPrompt、Operational)モナドと呼ばれるフリーモナドの代替策があります。 Freerモナドは、基礎となる命令セットのためのFunctorインスタンスを必要とせず、標準フリーモナドよりもはっきりとリストのような構造を持っています。
Freerモナドは、プログラムを命令セットi :: * -> *
属するアトミック命令のシーケンスとして表します。各命令は、そのパラメータを使用して戻り値の型を宣言します。たとえば、 State
モナドの基本命令セットは次のとおりです。
data StateI s a where
Get :: StateI s s -- the Get instruction returns a value of type 's'
Put :: s -> StateI s () -- the Put instruction contains an 's' as an argument and returns ()
これらの命令をシーケンシングするには、 :>>=
コンストラクタを使用します。 :>>=
を返す単一の命令取りa
し、継続にその戻り値配管、プログラムの残りの部分にそれを付加します。換言すれば、復帰命令所与a
、そしてオンにする機能a
返すプログラムにb
、 :>>=
復帰プログラム産生するb
。
data Freer i a where
Return :: a -> Freer i a
(:>>=) :: i a -> (a -> Freer i b) -> Freer i b
a
は、 :>>=
コンストラクタで存在するように数量化されていることに注意しa
ください。通訳者は、GADT i
パターンマッチングによってa
何が何でa
かを知る唯一の方法があります。
そもそも 、共ヨナの補題は
Freer
がFree
同形であることを私たちに伝えています。CoYoneda
ファンクタの定義を思い出してください:data CoYoneda i b where CoYoneda :: i a -> (a -> b) -> CoYoneda i b
Freer i
はFree (CoYoneda i)
と同等です。Free
のコンストラクタをf ~ CoYoneda i
を設定すると、次のようになります。Pure :: a -> Free (CoYoneda i) a Free :: CoYoneda i (Free (CoYoneda i) b) -> Free (CoYonda i) b ~ i a -> (a -> Free (CoYoneda i) b) -> Free (CoYoneda i) b
そこから私たちは回復できる
Freer i
だけで設定することでのコンストラクタをFreer i ~ Free (CoYoneda i)
。
のでCoYoneda i
あるFunctor
任意のためi
、 Freer
あるMonad
任意のためi
も、 i
ないですFunctor
。
instance Monad (Freer i) where
return = Return
Return x >>= f = f x
(i :>>= g) >>= f = i :>>= fmap (>>= f) g -- using `(->) r`'s instance of Functor, so fmap = (.)
あるハンドラのモナドに命令をマッピングすることで、 Freer
用にインタプリタを構築することができます。
foldFreer :: Monad m => (forall x. i x -> m x) -> Freer i a -> m a
foldFreer eta (Return x) = return x
foldFreer eta (i :>>= f) = eta i >>= (foldFreer eta . f)
たとえば、通常のState s
モナドをハンドラとして使用してFreer (StateI s)
モナドを解釈できます。
runFreerState :: Freer (StateI s) a -> s -> (a, s)
runFreerState = State.runState . foldFreer toState
where toState :: StateI s a -> State s a
toState Get = State.get
toState (Put x) = State.put x