FreeT是否保留Free的等式推理优势?

时间:2013-02-16 06:05:11

标签: haskell

在这篇blog post中,作者解释了使用Free monad净化代码的等式推理优势。 Free monad变压器FreeT是否保留了这些优势,即使它包裹在IO?

1 个答案:

答案 0 :(得分:15)

是。 FreeT不依赖于基本monad的任何特定属性,除了它是monad这一事实。您可以为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

让我们对免费的monad变换器使用以下定义:

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块属于基础monad,但我们从不需要使用任何特定的源代码或基本monad的属性来对其进行操作。我们需要知道的唯一属性是它是一个monad(任何monad!)并遵守monad法律。

仅使用monad法则,我们仍然可以推导出exitSuccess >> m = exitSuccess。这就是monad法则重要的原因,因为它们允许我们在多态基monad上推理代码,只知道它是monad。

更一般地说,这就是人们说类型类应该始终具有相关法律(如monad法则,仿函数法则或类别法则)的原因,因为这些允许您推断使用该类型的代码没有咨询该类型类的特定实例的类。如果没有这些法则,类型类接口就不会真正成为松散耦合的接口,因为如果不咨询原始实例源代码,您就无法在其中进行等价推理。

此外,如果你想要一个额外剂量的范畴理论,我们可以很容易地证明,如果基础monad是多态的,那么Free所持有的每个属性也必须保持FreeT。我们所要做的就是证明:

(forall m. (Monad m) => FreeT f m r) ~ Free f r

~符号表示&#34;与&#34;同构,这意味着我们必须证明有两个函数fwbw

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

这是一个有趣的证据,我把它留作练习!