免费monad和仿函数的固定点之间的区别?

时间:2013-06-25 20:59:38

标签: haskell category-theory

我正在阅读http://www.haskellforall.com/2013/06/from-zero-to-cooperative-threads-in-33.html,其中抽象语法树派生为表示一组指令的仿函数的自由monad。我注意到免费monad Free与仿函数Fix上的fixpoint运算符没有太大区别。

本文使用monad操作和do语法以简洁的方式构建这些AST(fixpoints)。我想知道这是免费monad实例的唯一好处吗?它还有其他有趣的应用程序吗?

2 个答案:

答案 0 :(得分:17)

(N.B。这与我和@ Gabriel上面的评论结合起来。)

Fix的{​​{1}} ed点的每个居民都有可能是无限的,即Functorlet x = (Fix (Id x)) in x === (Fix (Id (Fix (Id ...))))唯一居民}。 Fix Identity立即与Free不同,因为它确保至少有一个有限居民Fix。事实上,如果Free f有无限的居民,那么Fix f就会拥有无限多的有限居民。

这种无限制的另一个直接副作用是Free f不再是Functor f => Fix f。我们需要实施Functor,但fmap :: Functor f => (a -> b) -> (f a -> f b)已填充Fix中曾经包含f a的所有漏洞,因此我们不再拥有a a 1}}将我们的fmap'd函数应用于。

这对于创建Monad很重要,因为我们想要实施return :: a -> Free f a并且比如说这个法律持有fmap f . return = return . f,但它甚至没有意义Functor f => Fix f

那么Free如何“修复”这些Fix点的弱点?它使用Pure构造函数“扩充”我们的基础仿函数。因此,对于所有Functor fPure :: a -> Free f a。这是我们保证有限的类型居民。它还立即为我们提供了return的良好定义。

return = Pure

所以你可能会认为这个添加是由Functor创建的嵌套Fix的潜在无限“树”并混合在一些“活”芽中,由{{1}表示}。我们使用Pure创建新芽,这可能被解释为稍后“返回”到该芽的承诺并添加更多计算。事实上,这正是return所做的。给定可以应用于类型flip (>>=) :: (a -> Free f b) -> (Free f a -> Free f b)的“延续”函数f :: a -> Free f b,我们向下递归我们的树,返回到每个a并将其替换为计算为Pure a的连续。这让我们“成长”了我们的树。


现在,f a显然比Free更为通用。要开车回家,可以看到任何类型Fix作为相应Functor f => Fix f的子类型!只需选择我们Free f a所在的a ~ Void(即,无法构造的类型,是空类型,没有实例)。

为了更清楚,我们可以使用data Void = Void Void打破Fix'd Functor,然后尝试使用break :: Fix f -> Free f a将其反转。

affix :: Free f Void -> Fix f

首先请注意,break (Fix f) = Free (fmap break f) affix (Free f) = Fix (fmap affix f) 不需要处理affix案例,因为在这种情况下Pure x因此 不能存在,所以{{1我是荒谬的,我们会忽略它。

另请注意,x :: Void的返回类型有点微妙,因为Pure x类型仅出现在返回类型break中,因此a的任何用户都无法访问它{1}}。 “完全无法访问”和“无法实例化”给我们第一个提示,尽管类型Free f abreak是反转的,但我们可以证明它。

affix

应该显示(共同归纳,或者只是直觉,或许)break是一个身份。另一个方向完全相同。

所以,希望这表明所有(break . affix) (Free f) === [definition of affix] break (Fix (fmap affix f)) === [definition of break] Free (fmap break (fmap affix f)) === [definition of (.)] Free ( (fmap break . fmap affix) f ) === [functor coherence laws] Free (fmap (break . affix) f) (break . affix)大于Free f


那么为什么要使用Fix f?好吧,由于分层Functor f的一些副作用,有时您只需要Fix的属性。在这种情况下,调用它Free f Void可以更清楚地表明我们不应该尝试fFix f类型。此外,由于(>>=)只是fmap,编译器可能更容易“编译”Fix层,因为它只会起到语义作用。


  • 注意:我们可以更正式地讨论newtypeFix如何是同构类型,以便更清楚地了解Voidforall a. a的类型是如何和谐的。例如,我们affixbreakabsurd :: Void -> aabsurd (Void v) = absurd v。但这些有点傻。

答案 1 :(得分:7)

有一种深刻且“简单”的联系。

这是adjoint functor theorem的结果,左邻接保留了初始对象:L 0 ≅ 0

从分类上看,Free f是一个类别到其F代数的仿函数(Free f与另一个方向的健忘仿函数相邻)。在 Hask 中工作,我们的初始代数是Void

Free f Void ≅ 0

和F代数类别中的初始代数是Fix fFree f Void ≅ Fix f

import Data.Void
import Control.Monad.Free

free2fix :: Functor f => Free f Void -> Fix f
free2fix (Pure void) = absurd void
free2fix (Free body) = Fix (free2fix <$> body)

fixToFree :: Functor f => Fix f -> Free f Void
fixToFree (Fix body) = Free (fixToFree <$> body)

类似地,右邻接(Cofree f,一个来自 Hask 的仿函数到F- co <​​/ strong>代数的类别)保留最终对象:R 1 ≅ 1

Hask 中,这是单位:(),F- co <​​/ strong>代数的最终对象也是Fix f(它们在 Hask )所以我们得到:Cofree f () ≅ Fix f

import Control.Comonad.Cofree

cofree2fix :: Functor f => Cofree f () -> Fix f
cofree2fix (() :< body) = Fix (cofree2fix <$> body)

fixToCofree :: Functor f => Fix f -> Cofree f ()
fixToCofree (Fix body) = () :< (fixToCofree <$> body)

看看定义有多相似!

newtype Fix f 
  = Fix (f (Fix f))

Fix fFree f,没有变数。

data Free f a 
  = Pure a 
  | Free (f (Free f a))

Fix fCofree f,带有虚拟值。

data Cofree f a 
  = a <: f (Cofree f a)