我偶尔会看到"免费单子"这个词突然出现,但每个人似乎都在使用/讨论它们,而没有解释它们是什么。那么:什么是免费单子?(我想说我熟悉Monads和Haskell的基础知识,但对范畴理论只有很粗略的了解。)
- 这里有一个很好的解释:haskelforall.com/2012/06/…
- @罗杰,这就是我来这里的原因。对我来说,这个例子为一个名为"free"的类型定义了一个monad实例,就这样。
这里有一个更简单的答案:monad是当一元上下文被join :: m (m a) -> m a崩溃时"计算"的东西(回顾>>=可以定义为x >>= y = join (fmap y x))。这就是monads如何通过连续的计算链传递上下文:因为在序列中的每个点上,前一个调用的上下文都与下一个调用折叠在一起。
自由Monad满足所有Monad定律,但不会发生任何崩溃(即计算)。它只是构建一系列嵌套的上下文。创建这样一个自由的一元值的用户负责处理这些嵌套上下文,这样一个组合的含义可以延迟到创建一元值之后。
- 你的段落真的是菲利普文章的一个很好的补充。
- 我真的很喜欢这个答案。
- 由于这个解释,我终于得到了免费的单子。
- +1简明扼要。
- 自由monad可以替换monad类型类吗?也就是说,我可以只使用自由monad的返回和绑定来编写一个程序,然后使用我喜欢的mwybe或list或其他方式来连接结果,或者甚至生成一个绑定/concatted函数调用序列的多个单元视图。忽略底部和不终止,也就是说。
- 这个答案有帮助,但我想如果我没有在NICTA课程上遇到"加入"并阅读haskelforall.com/2012/06/…,我会感到困惑。所以对我来说,理解的诀窍是阅读大量的答案,直到它被吸收。(NICTA参考:github.com/nicta/course/blob/master/src/course/bind.hs)
- @这位先生,这个问题应该有它自己的主题。似乎您可以这样做,因为您使用的是一个可扩展的ADT,除了pure和roll之外,还可以处理添加put、get、local等。这可能会通过data types a la carte技术来实现。我敢打赌,我在关于模块化口译员的文献中看到过这一点。
- 这个答案是最好的
爱德华·凯密特的回答显然很好。但是,这有点技术性。下面是一个更容易理解的解释。
自由单子只是把函子变成单子的一般方法。也就是说,任何函数fFree f都是单子。这不是很有用,除非你有一对函数
1 2
| liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r ) -> Free f r -> r |
第一个让你"进入"你的单子,第二个让你"离开"它。
更一般地说,如果x是一个y,带有一些额外的东西p,那么"自由x"是一种从y到x的方法,而不获得任何额外的东西。
示例:单ID(x)是一个具有额外结构(p)的集合(y),它基本上表示它有一个操作(可以考虑加法)和一些标识(如零)。
所以
1 2 3
| class Monoid m where
mempty :: m
mappend :: m -> m -> m |
号
现在,我们都知道名单
好吧,考虑到任何类型的t,我们知道[t]是单体的。
1 2 3
| instance Monoid [t] where
mempty = []
mappend = (++) |
。
所以列表是集合(或者haskell类型)上的"自由单倍体"。
好吧,所以免费单子也是一样的想法。我们取了一个函子,还给了一个单子。事实上,由于单子可以看作是内函数类中的单子,因此列表的定义
看起来很像自由单子的定义
1
| data Free f a = Pure a | Roll (f (Free f a)) |
。
而Monad实例与清单的Monoid实例相似。
1 2 3 4 5 6 7 8 9 10 11 12 13
| --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 |
。
现在,我们有两个行动
1 2 3 4 5 6 7 8
| -- 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 ) |
- 这也许是我见过的最好的"自由"解释。尤其是以"更普遍"开头的段落。
- 在instance Monoid [t]中应该有mempty而不是mzero对吗?
- @是的。
- data Functor f => Functor (Free f) where应该是instance Functor f => Functor (Free f) where对吗?
- @很明显,是的。
- @丹诺洪修好了。
- 我认为把Free f a = Pure a | Roll (f (Free f a))看作Free f a = a + fa + ffa + ...是很有趣的,也就是说,"f适用于任何次数"。然后,concatFree(即join)取"f applied any number of times to(f applied any number of times to a)",将两个嵌套的应用程序折叠成一个。其中,>>=取"f对a的应用任意次数"和"如何从a到(b对f的应用任意次数)",基本上将后者应用于a的内部,并将嵌套折叠。现在我自己明白了!
- concatFree基本上是join吗?
- "所以名单是一套免费的monoid"显然不是根据这篇博客文章:comonad.com/reader/2015/free-monoids-in-haskell。
- @hjulle haskell类型不是集合,haskell"list"不是列表。所以,这真的不应该令人吃惊。[a]有额外的值,因为haskell列表可能是无限的,所以free monad没有额外的值。
- 所以单胞菌是一个函子,也是一个单胞菌?
- "这里有一个更容易理解的解释。[…]事实上,由于单子可以被看作是内函数范畴中的单子,…"尽管如此,我认为这是一个很好的答案。
- "monads在endo functors类中可以看作monoids"<3(您应该链接到stackoverflow.com/a/3870310/1306877,因为每个haskeller都应该知道该引用!)
一个自由的foo恰好是满足所有"foo"定律的最简单的东西。也就是说,它完全满足了成为一个foo所必需的法律,没有什么额外的。好的。
一个健忘函数是当结构从一个类别转到另一个类别时"健忘"结构的一部分。好的。
在给定函子F : D -> C和G : C -> D的情况下,我们称F -| G,F左与G相邻,或者G右与F相邻,每当a、b:F a -> b与a -> G b同构时,箭头都来自适当的类别。好的。
形式上,一个自由函子与一个健忘函子相伴随。好的。
自由单倍体好的。
让我们从一个简单的例子开始,自由单倍体。好的。
以一个单倍体为例,它由一些载流子集合T定义,一个二进制函数将一对元素混合在一起,f :: T → T → T和unit :: T,这样你就有了一个结合律和一个同一律:f(unit,x) = x = f(x,unit)。好的。
你可以使一个函子U从单倍体的类别(箭头是单倍体同态,也就是说,它们确保把unit映射到另一个单倍体的unit上,并且你可以在映射到另一个单倍体之前或之后组合而不改变意义)到集合的类别(箭头只是功能箭头)。)那"忘记"了操作和unit,只给了你一套载波机。好的。
然后,可以定义一个函子F,从集合的类别返回到与该函子相邻的单倍体的类别。该函子是将集合a映射到单体[a]的函子,其中unit = []和mappend = (++)。好的。
为了回顾我们目前的示例,在伪haskell中:好的。
1 2 3 4 5
| 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的目标是单倍体的Mon类,箭头是单倍体同态,所以我们需要a来证明,来自[a] → b的单倍体同态可以精确地用来自a → b的函数来描述。好的。
在haskell中,我们称之为生活在Set中的这一边(er,Hask,我们假装的haskell类型的类别是固定的),只是foldMap,当从Data.Foldable到list专门化时,它有Monoid m => (a → m) → [a] → m类型。好的。
作为一种附属物,随之而来的后果是。值得注意的是,如果您忘记了,那么用自由构建,然后再次忘记,就像您忘记了一次一样,我们可以使用这个来构建单元联接。由于UFUF~U(FUF)~UF,我们可以通过定义我们的附加项的同构将身份单倍体同态从[a]~[a]传递给[a],得到[a] → [a]的列表同构是a -> [a]类型的函数,这只是列表的返回。好的。
您可以通过以下方式更直接地组成列表:好的。
1
| newtype List a = List (forall b. Monoid b => (a -> b) -> b) |
免费单子好的。
那么什么是免费单子呢?好的。
好吧,我们以前做过同样的事情,我们从一个遗忘的函子u开始,从箭头是单子同态的单子类,到箭头是自然变换的内函子类,我们寻找一个与之伴随的函子。好的。
那么,这和通常使用的自由单子有什么关系呢?好的。
知道某个东西是一个自由单态,Free f告诉你,从Free f -> m给予一个单态同态和从f -> m给予一个自然变换(函数同态)是一样的。记住,对于f,F a -> b必须与a -> U b同构,才能与u相伴随,在这里映射单子到函子。好的。
f至少与我在我的Free包中使用的hackage上的Free类型是同构的。好的。
我们还可以通过定义好的。
1 2 3 4
| class Algebra f x where
phi :: f x -> x
newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x) |
号
科摩纳达咖啡树好的。
我们可以构造类似的东西,通过观察一个遗忘函数的右伴随,假设它存在。余元函子是简单/右伴随/到一个健忘函子,通过对称性,知道某物是一个余元余元与知道从w -> Cofree f给予一个余元同态与从w -> f给予一个自然变换是一样的。好的。好啊。
- 也许我不够聪明,没法挖哈斯克尔,但像这样的东西似乎很难消化。我一直很喜欢这种语言,直到I/O章和纯粹/不纯洁的东西。
- @保罗斯卡丁,这不是你要担心的。我的问题来自于对一些先进的数据结构的理解,也许现在能了解到Haskell开发的最新进展——目前为止,它根本就没有必要,也不能代表Haskell的实际写作内容。(抬头一看,一旦你再次通过IO学习阶段,情况就会好转)
- @保罗斯卡丁,你不需要以上的答案,以富有成效的计划在哈斯克尔,即使与免费单子。事实上,我不建议那些没有范畴理论背景的人用这种方式攻击自由单子。有很多方法可以从操作的角度来讨论它,并且探索如何在不深入类别理论的情况下使用它。然而,如果不深入研究理论,我就不可能回答他们从哪里来的问题。自由结构是范畴理论中的一个强大工具,但您不需要这种背景来使用它们。
- 似乎没有微积分的背景,很难掌握哈斯克尔。也许这就是为什么它不那么受欢迎的原因,尽管它有所有的优点——大多数人在大学里都会带着咀嚼玻璃的乐趣(即使是在计算机科学中)。
- @保罗斯卡丁:你完全不需要微积分就能有效地利用哈斯克尔,甚至能理解你在做什么。抱怨"这种语言是数学"有点奇怪,因为数学只是额外的优点,你可以用它来娱乐和赚钱。在大多数命令式语言中,您不会得到这些东西。你为什么要抱怨额外的费用?你可以选择不进行数学推理,像对待任何其他新语言一样对待它。
- @莎拉:我还没有看到关于哈斯克尔的文档或IRC对话,它对计算机理论和lambda微积分理论并不太重要。
- haskell类型中存在哪些限制/结肠炎?
- @Pauloscardine这是有点偏离了OT,但在Haskell的辩护中:类似的技术东西适用于所有其他编程语言,只是Haskell有一个非常好的编译,人们可以真正享受谈论它们。为什么x是monad对很多人来说很有趣,关于ieee浮点标准的讨论并不是;这两种情况对大多数人来说都不重要,因为他们可以使用结果。
- @mnish-作为一个常规问题而不是回答,你可能会得到更好的结果。这就是说,Hask是Set的一个建设性的类似物,如果你忽略了底部的问题,我们有(弱)产品和(弱)副产品。这应该给你有限的极限,但是注意我们没有hask中的词汇来证明关于交换平方的任何东西,所以任何工作都必须在元层次上完成。在大肠杆菌方面的局限性更为明显,因为在构建推出/大肠杆菌时,我们通常需要商类型,这是Haskell所缺少的。
- @mnish hask有所有的小范围和结肠炎,它是完整和共完成的。如果我们把我们的分类当作一种,我们就可以制造newtype Const a (b::i) = Const a,那么我们就可以制造newtype Lim f = Lim { runLim :: forall a. f a }和data Colim f where Colim :: f a -> Colim f。现在我们有了Colim -| Const -| Lim :: (i -> *) -> *,您可以很容易地显示出来。
- @爱德华:我有点困惑。如果hask有所有的小限制,你如何获得均衡器?
- @托梅利斯:我就知道我会被叫出去的!必须使用的自然转换的类别是自然转换的"真实"类别的一个子集,这种自然转换滥用参数性作为自然性的代理。不幸的是,参数性的强度使得(对于所有a.f a->g a)与fmap和contramap都可以进行交换,而且你能构造的所有函数都在它们前面加上有趣的标记f,这迫使你的函数对类型的忠实映射撒了谎,破坏了许多标准结构。=(
自由monad(数据结构)是对monad(类)的,就像列表(数据结构)对monoid(类)的:它是一个简单的实现,您可以在其中决定如何组合内容。
你可能知道Monad是什么,每个Monad都需要一个特定的(遵守Monad法律的)实施,要么是fmap+join+return+bind+return。
我们假设您有一个函数(fmap的一个实现),但其余的都取决于运行时所做的值和选择,这意味着您希望能够使用monad属性,但希望以后选择monad函数。
这可以使用自由Monad(数据结构)来完成,它以这样的方式包装函数(类型),使得join是这些函数的叠加,而不是减少。
您要使用的实际return和join现在可以作为reduction函数foldFree的参数:
1 2
| foldFree :: Functor f => (a -> b ) -> (f b -> b ) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a |
为了解释类型,我们可以用Monad m替换Functor f,用(m a)替换b:
1
| foldFree :: Monad m => (a -> (m a )) -> (m (m a ) -> (m a )) -> Free m a -> (m a ) |
号
- 这个答案给了我一个印象,那就是我理解它们可能对什么有用。
- 你是一个势在必行的程序员。很好。
haskell-free monad是一个函数列表。比较:
1 2 3
| data List a = Nil | Cons a (List a )
data Free f r = Pure r | Free (f (Free f r)) |
。
Pure与Nil相似,Free与Cons相似。自由monad存储函数列表,而不是值列表。从技术上讲,您可以使用不同的数据类型实现自由monad,但是任何实现都应该与上面的实现是同构的。
只要需要抽象语法树,就可以使用自由单子。自由monad的基函数是语法树的每个步骤的形状。
我的文章已经有人链接了,它给出了几个关于如何用自由单子构建抽象语法树的例子。
- 我知道你只是在画一个类比,而不是做一个定义,但是一个自由的monad在任何意义上都不类似于一个函数列表。它更接近于一个函数树。
- 我坚持我的术语。例如,使用我的索引核心包,您可以定义"自由Monad理解",其行为与列表Monad类似,只是您绑定了函数而不是值。一个自由单子是一个函子的列表,从这个意义上说,如果你把所有的haskell概念转化为函子的类别,那么列表就变成了自由单子。一个真正的函数树就会变得完全不同。
- 你说得对,单子是单子概念的分类,在某种意义上,因此自由单子类似于自由单子,即列表。在这种程度上,你是正确的。但是,自由monad值的结构不是列表。这是一棵树,我在下面详述。
- @从技术上讲,如果基函数是产品函数,那么它只是一棵树。当你有一个和函数作为基函数时,它更像一个堆栈机器。
我认为一个简单具体的例子会有所帮助。假设我们有一个函数
1
| data F a = One a | Two a a | Two' a a | Three Int a a a |
有明显的fmap。那么,Free F a是叶子为a型,节点为One型、Two型、Two'型和Three型的树种。One节点有一个子节点,Two和Two'节点有两个子节点,Three节点有三个子节点,还带有Int标记。
Free f是单胞菌。return将x映射到树上,该树只是一片叶子,值为x。t >>= f看着每一片叶子,用树代替它们。当叶子的值为y时,它会用树f y代替叶子。
一张图表可以更清楚地说明这一点,但我没有方便地绘制的工具!
- 你们要说的是,自由单子的形状是函数本身。所以如果函数是树型的(积),自由单子是树型的;如果是列表型的(和),自由单子是列表型的;如果是函数型的,自由单子是函数型的;等等,这对我来说是有意义的。所以,就像在一个自由的monoid中一样,您一直将mapend的每个应用程序视为创建一个全新的元素;在自由monad中,您将functor的每个应用程序视为一个全新的元素。
- 即使该函子是"和函子",生成的自由单子仍然是树型的。最后,树中有多个节点类型:一个节点对应于总和的每个部分。如果你的"和函数"是x->1+x,那么你确实得到了一个列表,它只是一个退化的树。