什么是免费的单子?


368

我见过的长期免费单子弹出每一个 现在 随后一段时间,但每个人似乎只是使用/讨论这些问题没有给予它们是什么解释。那么:什么是免费的monad?(我想说我对monad和Haskell基本知识很熟悉,但是对类别理论只有很粗略的了解。)


12
一个相当不错的解释是这里haskellforall.com/2012/06/...
罗杰Lindsjö

19
@Roger就是那种把我带到这里的页面。对我而言,该示例为名为“ Free”的类型定义了monad实例,仅此而已。
David

Answers:


295

爱德华·克梅特的答案显然是非常好的。但是,这有点技术性。这是一个更容易理解的解释。

免费的monad只是将函子变成monad的一种通用方法。也就是说,给定任何函子f Free f都是单子。这不是很有用,除非您有一对功能

liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r) -> Free f r -> r

其中第一个可以让您“进入”您的monad,第二个可以让您“摆脱”它。

更一般而言,如果X是带有一些额外填充P的Y,则“自由X”是从Y变为X而不获得任何额外收益的一种方式。

例子:一个monoid(X)是一个具有额外结构(P)的集合(Y),基本上说它具有一个运算(可以想到加法)和某些标识(例如零)。

所以

class Monoid m where
   mempty  :: m
   mappend :: m -> m -> m

现在,我们都知道列表

data [a] = [] | a : [a]

好吧,给定任何类型,t我们都知道这[t]是一个半定态

instance Monoid [t] where
  mempty   = []
  mappend = (++)

因此列表是集合(或Haskell类型)中的“自由monoid”。

好的,所以免费的monad是相同的想法。我们拿一个函子,还给一个单子。实际上,由于monad可以看作endofunctors类别中的monoid,因此列表的定义

data [a] = [] | a : [a]

看起来很像免费单子的定义

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

并且该Monad实例与Monoid列表的实例具有相似性

--it needs to be a functor
instance Functor f => Functor (Free f) where
  fmap f (Pure a) = Pure (f a)
  fmap f (Roll x) = Roll (fmap (fmap f) x)

--this is the same thing as (++) basically
concatFree :: Functor f => Free f (Free f a) -> Free f a
concatFree (Pure x) = x
concatFree (Roll y) = Roll (fmap concatFree y)

instance Functor f => Monad (Free f) where
  return = Pure -- just like []
  x >>= f = concatFree (fmap f x)  --this is the standard concatMap definition of bind

现在,我们完成了两个操作

-- this is essentially the same as \x -> [x]
liftFree :: Functor f => f a -> Free f a
liftFree x = Roll (fmap Pure x)

-- this is essentially the same as folding a list
foldFree :: Functor f => (f r -> r) -> Free f r -> r
foldFree _ (Pure a) = a
foldFree f (Roll x) = f (fmap (foldFree f) x)

12
这可能是我所见过的关于“免费”的最佳可理解的解释。特别是该段落以“更普遍”开头。
约翰L

16
我认为将Free f a = Pure a | Roll (f (Free f a))as视为有趣Free f a = a + fa + ffa + ...,即“ f适用于任何次数”。然后concatFree(即join)进行“将f应用于任何次数(f对a进行任意次数)”,并将两个嵌套的应用折叠为一个。并>>=采用“ f对a进行任意次数的应用”和“如何从a到(f对b进行任意次数的应用)得出”,并且基本上将后者应用于前者内部的a并折叠嵌套。现在我自己明白了!
2013年

1
concatFree基本join
rgrinberg 2014年

11
“这是一个也许更容易理解的解释。[…]事实上,由于单核单体可以看作是内函子类别中的类人,……”不过,我认为这是一个很好的答案。
Ruud

2
“ monads可以看作是内函子类别中的monoids ” <3(您应该链接到stackoverflow.com/a/3870310/1306877,因为每个散客都应该知道该引用!)
tlo

418

这里有一个甚至更简单的答案:Monad是通过折叠Monadic上下文join :: m (m a) -> m a>>=可以将其定义为x >>= y = join (fmap y x))来“计算”的东西。这就是Monad通过顺序计算链传递上下文的方式:因为在系列的每个点上,前一个调用的上下文都会与下一个调用折叠。

一个免费的monad满足所有Monad法则,但不做任何折叠(即计算)。它只是建立了一系列嵌套的上下文。创建这样的自由monadic值的用户负责对这些嵌套上下文进行操作,以便可以将这种组合的含义推迟到monadic值创建之后。


8
您的段落对Philip的帖子非常有用。
David

20
我真的很喜欢这个答案。
danidiaz 2013年

5
免费的monad可以代替Monad类型的类吗?也就是说,我可以只使用免费的monad的return和bind编写程序,然后使用我更喜欢的Mwybe或List或任何东西将结果合并,甚至生成一个绑定/合并的函数调用序列的多个monadic视图。就是忽略底部和非终止。
misterbee 2014年

2
这个答案很有帮助,但是如果我在NICTA课程上没有遇到“加入”并阅读haskellforall.com/2012/06/…,那我会感到困惑。因此,对我而言,理解的窍门是阅读大量答案,直到陷入困境为止。(NICTA参考:github.com/NICTA/course/blob/master/src/Course/Bind.hs
Martin Capodici

1
这个答案是有史以来最好的
Curycu '16

142

一个免费的foo恰好是满足所有“ foo”法则的最简单的东西。也就是说,它完全满足成为foo所必需的法律,而没有其他要求。

一个健忘的仿函数是一个将结构的一部分从一个类别转移到另一个类别时“忘记”它的部分。

鉴于函子F : D -> C,和G : C -> D我们说F -| GF留给伴随到G,或者G是对伴随以F每当FORALL A,B:F a -> b是同构的a -> G b,其中的箭头来自适当的类别。

正式地,一个自由函子与健忘函子相连。

免费的半身像

让我们从一个简单的示例开始,即自由monoid。

以一个monoid(由某个载波集定义)T,一个将一对元素混在一起的二进制函数f :: T → T → T和一个unit :: T(使您具有关联律和一个身分律)来定义f(unit,x) = x = f(x,unit)

您可以U从monoid类别(其中箭头是monoid同态,即确保它们映射unitunit另一个monoid上,并且您可以在映射到另一个monoid之前或之后撰写而不更改含义)中构成一个函子。的集合(其中箭头只是功能箭头)“忘记”了操作和unit,只为您提供了载体集。

然后,您可以F从集合的类别定义到仿函数的类别,该类别与该仿函数保持相邻关系,从而定义一个仿函数。该函子是将集合映射a到monoid 的函子[a],其中unit = []mappend = (++)

因此,以伪Haskell来看到目前为止的示例:

U : Mon  Set -- is our forgetful functor
U (a,mappend,mempty) = a

F : Set  Mon -- is our free functor
F a = ([a],(++),[])

然后要显示F是免费的,我们需要证明它与U,是一个健忘的函子相邻,也就是说,如上所述,我们需要证明

F a → b 同构 a → U b

现在,请记住的目标F是在Monmonoid 类别中,其中箭头是monoid同态,因此我们需要a来证明from的monoid同态[a] → b可以用from的函数精确描述a → b

在Haskell中,我们称其存在的那一侧SetHask即,我们假装的Haskell类型的类别为Set),just foldMap,当从from Data.Foldable到Lists 专门化时,其类型为type Monoid m => (a → m) → [a] → m

这是附属的后果。值得注意的是,如果您忘记了然后免费建立起来,然后又忘记了,就像您忘记一次一样,我们可以使用它来建立单子连接。由于UFUFU(FUF)UF,我们可以在身份幺同态,从传递[a][a]通过定义我们的红利同构,获取从列表同构[a] → [a]是类型的函数a -> [a],而这仅仅是返回列表。

您可以通过使用以下术语描述列表来更直接地构成所有这些内容:

newtype List a = List (forall b. Monoid b => (a -> b) -> b)

免费的单子

那么什么是免费的Monad

好吧,我们做着与以前相同的事情,我们从健忘的函子U开始,从箭头是单子同态的monads类别到箭头是自然变换的endofunctors类别,然后寻找与之相邻的函子对此。

那么,这与通常使用的免费monad的概念有何关系?

知道某物是自由的monad,就Free f告诉您,从给出一个monad同Free f -> m构与从给出一个自然变换(函子同构)是同一件事(同构)f -> m。请记住,F a -> b必须a -> U b使F与U 保持同构。U在此将单子映射为函子。

F至少与Freefree在黑客程序包中使用的类型同构。

我们还可以通过定义以下内容,使其与上述免费列表的代码更紧密地类似

class Algebra f x where
  phi :: f x -> x

newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x)

Cofree Comonads

我们可以通过看似健忘的仿函数的正确伴随(如果存在)来构造类似的东西。cofree函子与健忘的functor只是简单地/正确地伴随/对称,因此,知道某物是cofree共模子,就等于知道从中给出comonad同构性与从中w -> Cofree f给出自然变换一样w -> f


12
@PauloScardine,您不必担心这一切。我的问题来自对了解一些高级数据结构的兴趣,或者一眼就可以了解一下Haskell开发中的前沿技术-到目前为止,这根本没有必要或不能代表Haskell实际编写的内容。(要注意,一旦您再次超过IO学习阶段,情况就会变得更好)
David

8
@PauloScardine即使使用免费的monad,也不需要上面的答案即可在Haskell进行高效编程。实际上,我不建议以这种方式攻击没有类别理论背景的人。有很多方法可以从操作角度来讨论它,并且在不深入类别理论的情况下如何使用它。但是,如果不深入理论,我不可能回答有关它们来自何方的问题。自由构造是范畴论中的有力工具,但是您不需要使用此背景就可以使用它们。
爱德华·KMETT

18
@PauloScardine:您完全不需要演算就可以有效地利用Haskell,甚至不需要了解自己在做什么。抱怨“这种语言是数学”,这是很奇怪的,因为数学只是一种额外的好处,可用于娱乐和获利。您无法用大多数命令式语言获得这些东西。你为什么要抱怨演员?您可以选择不进行数学推理,并像对待其他任何新语言一样进行处理。
萨拉

3
@Sarah:我还没有看到有关Haskell的文档或IRC对话,它对计算机理论和Lambda微积分热学并不十分重视。
Paulo Scardine 2012年

11
@PauloScardine,这有点过时了,但是在Haskell的辩护中:类似的技术问题也适用于所有其他编程语言,只是Haskell具有如此好的编译效果,人们实际上可以喜欢谈论它们。为什么X如何成为单子,对许多人来说很有趣,关于IEEE浮点标准的讨论却没有。两种情况对大多数人都无关紧要,因为他们只能使用结果。
David

72

Free Monad(数据结构)对Monad(类)的作用类似于List(数据结构)对Monoid(类)的作用:这是一个微不足道的实现,您可以在其中决定以后如何组合内容。


您可能知道什么是Monad,并且每个Monad需要fmap+ join+ returnbind+ 的特定(遵守Monad-law的)实现return

让我们假设您有一个Functor(的实现fmap),但其余部分取决于运行时的值和选择,这意味着您希望能够使用Monad属性,但之后要选择Monad函数。

这可以使用Free Monad(数据结构)来完成,该结构以某种方式包装Functor(类型),从而使joina是那些函子的叠加而不是归约。

现在可以将实数returnjoin要使用的实数作为归约函数的参数foldFree

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a

为了解释类型,我们可以Functor fMonad m和替换b(m a)

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)

8
这个答案给我的印象是,我了解它们甚至可能对您有用。
大卫

59

Haskell免费monad是函子的列表。相比:

data List a   = Nil    | Cons  a (List a  )

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

Pure类似于NilFree类似Cons。一个免费的monad存储一个函子列表而不是一个值列表。从技术上讲,您可以使用其他数据类型实现免费的monad,但是任何实现都应与上述方法同构。

每当需要抽象语法树时,就可以使用免费的monad。自由monad的基本函子是语法树的每个步骤的形状。

我的帖子(已有人链接)提供了一些示例,说明如何使用免费的monad构建抽象语法树


6
我知道您只是在进行类比而不是进行定义,但是免费的monad肯定在任何意义上都不类似于函子列表。它更接近函子树。
汤姆·埃利斯

6
我坚持我的术语。例如,使用我的index-core包,您可以定义“自由monad理解”,其行为与列表monad相似,只是您绑定函子而不是值。从您将所有Haskell概念转化为函子类别的意义上讲,免费单子是函子的列表,然后列表将成为免费单子。真正的函子树变成完全不同的东西。
加布里埃尔·冈萨雷斯

4
您是正确的,从某种意义上说,monad是monoid概念的分类,因此,自由monad与自由monoid(即列表)类似。从这个意义上讲,您当然是正确的。但是,自由单价的值的结构不是列表。这是一棵树,我在下面详细说明
汤姆·埃利斯

2
@TomEllis从技术上讲,如果您的基本函子是产品函子,那么它只是一棵树。当您将求和仿函数用作基本仿函数时,它更像是堆栈计算机。
加布里埃尔·冈萨雷斯

21

我认为一个简单的具体例子会有所帮助。假设我们有一个函子

data F a = One a | Two a a | Two' a a | Three Int a a a

与显而易见的fmap。然后Free F a是树木,叶子有型的类型a,其节点标记OneTwoTwo'ThreeOne-节点有一个子节点,Two-和- Two'节点有两个子Three节点,- 节点有三个子节点,并带有标记Int

Free F是单子。 return映射x到只有值的叶子的树xt >>= f看着每片叶子,然后用树代替它们。当叶子具有价值时,y它将用树替换该叶子f y

图表使这一点更加清楚,但是我没有轻松绘制一个的功能!


14
你们在说的是,免费的monad具有仿函数本身的形状。因此,如果仿函数是树状的(产品),则自由monad是树状的;如果它是列表式(总和),则免费monad就是列表式;如果是函数式的,那么免费的monad就是函数式的;等。这对我来说很有意义。因此,就像在一个免费的monoid中一样,您始终将mappend的每个应用视为创建一个全新的元素。在免费的monad中,您将仿函数的每个应用视为一个全新的元素。
Bartosz Milewski 2013年

4
即使函子是“求和函子”,所得到的自由单子仍然是树状的。您最终在树中拥有不止一种类型的节点:对于总和的每个组成部分而言都是一种。如果您的“求和函子”是X-> 1 + X,那么实际上您会得到一个列表,它只是一棵简并的树。
汤姆·埃利斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.