このブログ投稿では、著者はFreeモナドを使用してコードを精製することの等式的な推論の利点について説明しています。Freeモナド変換子FreeTは、IOをラップした場合でも、これらの利点を保持しますか?
1 に答える
はい。 FreeT
それがモナドであるという事実を除いて、ベースモナドの特定のプロパティに依存しません。推論できるすべての方程式にFree f
は、の同等の証明がありFreeT f m
ます。
それを実証するために、私のブログ投稿で演習を繰り返しましょう。ただし、今回はFreeT
:を使用します。
data TeletypeF x
= PutStrLn String x
| GetLine (String -> x)
| ExitSuccess
deriving (Functor)
type Teletype = FreeT TeletypeF
exitSuccess :: (Monad m) => Teletype m r
exitSuccess = liftF ExitSuccess
無料のモナド変換子には次の定義を使用しましょう。
return :: (Functor f, Monad m) => r -> FreeT f m r
return r = FreeT (return (Pure r))
(>>=) :: (Functor f, Monad m) => FreeT f m a -> (a -> FreeT f m b) -> FreeT f m b
m >>= f = FreeT $ do
x <- runFreeT m
case x of
Free w -> return (Free (fmap (>>= f) w))
Pure r -> runFreeT (f r)
wrap :: (Functor f, Monad m) => f (FreeT f m r) -> FreeT f m r
wrap f = FreeT (return (Free f))
liftF :: (Functor f, Monad m) => f r -> FreeT f m r
liftF fr = wrap (fmap return fr)
等式推論を使用して、次のようになるものを推測できますexitSuccess
。
exitSuccess
-- Definition of 'exitSuccess'
= liftF ExitSuccess
-- Definition of 'liftF'
= wrap (fmap return ExitSuccess)
-- fmap f ExitSuccess = ExitSuccess
= wrap ExitSuccess
-- Definition of 'wrap'
= FreeT (return (Free ExitSuccess))
これで、次のことを証明できますexitSuccess >> m
= exitSuccess
:
exitSuccess >> m
-- m1 >> m2 = m1 >>= \_ -> m2
= exitSuccess >>= \_ -> m
-- exitSuccess = FreeT (return (Free ExitSuccess))
= FreeT (return (Free ExitSuccess)) >>= \_ -> m
-- use definition of (>>=) for FreeT
= FreeT $ do
x <- runFreeT $ FreeT (return (Free ExitSuccess))
case x of
Free w -> return (Free (fmap (>>= (\_ -> m)) w))
Pure r -> runFreeT ((\_ -> m) r)
-- runFreeT (FreeT x) = x
= FreeT $ do
x <- return (Free ExitSuccess)
case x of
Free w -> return (Free (fmap (>>= (\_ -> m)) w))
Pure r -> runFreeT ((\_ -> m) r)
-- Monad Law: Left identity
-- do { y <- return x; m } = do { let y = x; m }
= FreeT $ do
let x = Free ExitSuccess
case x of
Free w -> return (Free (fmap (>>= (\_ -> m)) w))
Pure r -> runFreeT ((\_ -> m) r)
-- Substitute in 'x'
= FreeT $ do
case Free ExitSuccess of
Free w -> return (Free (fmap (>>= (\_ -> m)) w))
Pure r -> runFreeT ((\_ -> m) r)
-- First branch of case statement matches 'w' to 'ExitSuccess'
= FreeT $ do
return (Free (fmap (>>= (\_ -> m)) ExitSuccess))
-- fmap f ExitSuccess = ExitSuccess
= FreeT $ do
return (Free ExitSuccess)
-- do { m; } desugars to 'm'
= FreeT (return (Free ExitSuccess))
-- exitSuccess = FreeT (return (Free ExitSuccess))
= exitSuccess
証明のdo
ブロックはベースモナドに属していましたが、それを等式的に操作するために、ベースモナドの特定のソースコードやプロパティを使用する必要はありませんでした。私たちが知る必要がある唯一の特性は、それがモナド(任意のモナド!)であり、モナドの法則に従っているということでした。
モナドの法則のみを使用して、それを推測することができましたexitSuccess >> m = exitSuccess
。それがモナド法が重要である理由です。なぜなら、それがモナドであることだけを知っているので、多形ベースのモナド上のコードについて推論することができるからです。
より一般的には、これが、型クラスには常に関連する法則(モナド法、ファンクター法、カテゴリ法など)が必要であると人々が言う理由です。これらの法則により、相談せずにその型クラスを使用するコードについて推論できるからです。その型クラスの特定のインスタンス。これらの種類の法則がなければ、元のインスタンスのソースコードを参照せずにそれについて方程式的に推論することはできないため、型クラスインターフェイスは実際には緩く結合されたインターフェイスにはなりません。
また、圏論の追加量が必要な場合は、基本モナドが多形である場合、保持するすべてのプロパティも保持するFree
必要があることを簡単に証明できます。FreeT
私たちがしなければならないのは、次のことを証明することだけです。
(forall m. (Monad m) => FreeT f m r) ~ Free f r
~
記号は「同型」を意味します。これは、、、および:の2つの関数があることを証明する必要があることを意味しfw
ますbw
。
fw :: (forall m . (Monad m) => FreeT f m r) -> Free f r
bw :: Free f r -> (forall m . (Monad m) => FreeT f m r)
... そのような:
fw . bw = id
bw . fw = id
これは興味深い証拠であり、演習として残しておきます。