我已经看到 Free Monad 一词弹出every now and then一段时间了,但每个人似乎都在使用/讨论它们而不解释它们是什么。所以:什么是免费的monads? (我会说我熟悉monad和Haskell的基础知识,但对类别理论只有非常粗略的了解。)
答案 0 :(得分:373)
这是一个更简单的答案:当Monadic上下文被join :: m (m a) -> m a
折叠时,Monad会“计算”(回想>>=
可以定义为x >>= y = join (fmap y x)
)。这就是Monads如何通过顺序计算链传递上下文:因为在序列中的每个点,前一个调用的上下文都会与下一个调用一起折叠。
免费monad 满足所有Monad定律,但不会进行任何折叠(即计算)。它只是构建了一系列嵌套的上下文。创建这样一个自由monadic值的用户负责使用这些嵌套上下文执行某些操作,以便可以推迟此类组合的含义,直到创建monadic值为止。
答案 1 :(得分:270)
免费monad只是将仿函数转换为monad的一般方法。也就是说,假设任何仿函数f
Free f
都是monad。除了你得到一对函数
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。事实上,由于monads可以被视为endofunctors类别中的monoids,因此列表的定义
data [a] = [] | a : [a]
看起来很像免费monads的定义
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)
答案 2 :(得分:132)
免费foo恰好是满足所有'foo'法则的最简单的事情。也就是说它完全满足成为foo所必需的法则,而不是额外的。
一个健忘的仿函数是一个“忘记”结构的一部分,因为它从一个类别转移到另一个类别。
考虑到仿函数F : D -> C
和G : C -> D
,我们说F -| G
,F
与G
保持联系,或者G
是正确的伴随每当forall a,b:F
与F a -> b
同构时,到a -> G b
,其中箭头来自相应的类别。
正式地,一个自由的仿函数与一个健忘的仿函数相伴。
免费幺半群
让我们从一个更简单的例子开始,即免费的幺半群。
取一个由一些载体集T
定义的monoid,一个二元函数将一对元素混合在一起f :: T → T → T
和一个unit :: T
,这样你就有了一个关联法律和身份法:f(unit,x) = x = f(x,unit)
。
你可以从幺半群类别中创建一个仿函数U
(其中箭头是幺半群同态,也就是说,它们确保它们将unit
映射到另一个幺半群上的unit
,并且你可以在映射到另一个monoid之前或之后编写而不改变含义)到集合的类别(其中箭头只是函数箭头)“忘记”操作和unit
,并且只给你载体集。
然后,您可以将一个仿函数F
从集合类别定义回与该仿函数相伴的monoids类别。该仿函数是将一组a
映射到幺半群[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
的目标位于幺半群的Mon
类别中,其中箭头是幺半群同态,因此我们需要一个来证明来自[a] → b
的幺半群同态可以是由a → b
的函数精确描述。
在Haskell中,我们称之为Set
(呃,Hask
,我们假装设置的Haskell类型的类别),只有foldMap
,当时专门从Data.Foldable
到列表的类型为Monoid m => (a → m) → [a] → m
。
这是一个后果,因为这是一个附属物。值得注意的是,如果你忘记然后自由积累,那么再次忘记,它就像你忘了一次,我们可以用它来建立monadic连接。自UFUF
〜U(FUF)
〜UF
以来,我们可以通过定义我们的adjunction的同构传递[a]
到[a]
的同一性monoid同态,得到它来自[a] → [a]
的列表同构是a -> [a]
类型的函数,这只是列表的返回。
您可以通过以下方式描述这些术语中的列表来直接撰写所有内容:
newtype List a = List (forall b. Monoid b => (a -> b) -> b)
免费Monad
那么什么是免费Monad ?
好吧,我们做了之前我们做过的事情,我们从一个健忘的仿函数U开始,从monad类别中箭头是monad homomorphisms到一类endofunctors,其中箭头是自然变换,我们寻找一个仿函数与...相伴。
那么,这与通常使用的免费monad的概念有什么关系?
知道某个东西是一个自由的monad Free f
,告诉你从Free f -> m
给出一个monad同态,与给出一个自然变换(一个仿函数同态)是同一的(同构的) f -> m
。请记住,F a -> b
必须与a -> U b
同构,才能使F与U相伴。这里将monad映射到仿函数。
F至少与我在Free
hackage包中使用的free
类型同构。
通过定义
,我们还可以通过更加类似于上面代码的自由列表来构建它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仿函数简单地/正确地伴随着一个健忘的仿函数,并且通过对称性,知道某个东西是一个共同的comonad就像知道从w -> Cofree f
给出一个comonad同态一样的东西就像给出一个自然变换一样w -> f
。
答案 3 :(得分:60)
Free Monad(数据结构)是Monad(类),类似于Monoid(类)的List(数据结构):这是一个简单的实现,您可以在其中决定如何组合内容。< / p>
你可能知道Monad是什么,并且每个Monad都需要fmap
+ join
+ return
或bind
+的特定(Monad-law持久)实现return
。
我们假设您有一个Functor(fmap
的实现),但其余的取决于在运行时所做的值和选择,这意味着您希望能够使用Monad属性但希望之后选择Monad函数。
这可以使用Free Monad(数据结构)来完成,它以这样的方式包装Functor(类型),这样join
就是堆叠那些仿函数而不是简化。
您想要使用的真实return
和join
现在可以作为缩减函数foldFree
的参数给出:
foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a
要解释类型,我们可以将Functor f
替换为Monad m
,将b
替换为(m a)
:
foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)
答案 4 :(得分:55)
Haskell免费monad是一个仿函数列表。比较:
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。 free monad的基本仿函数是语法树每一步的形状。
某人已经链接的My post提供了几个如何使用免费monad构建抽象语法树的示例
答案 5 :(得分:21)
我认为一个简单的具体例子会有所帮助。假设我们有一个仿函数
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
替换该叶子。
图表使这更清楚,但我没有轻松绘制一个的设施!
答案 6 :(得分:1)
尝试在此处的简单答案和完整答案之间提供“桥梁”答案。
因此,“免费的monads”会在任何“ functor”中构建一个“ monad”,所以我们按顺序进行整理。
有些东西是类型级别的形容词,这意味着它们采用了“整数”之类的类型名词,并带给您诸如“整数列表”或“成对的字符串”之类的不同类型名词。整数”,甚至“将字符串做成整数的函数”。为了表示任意形容词,让我使用替代词“ blue”。
但是随后我们注意到一种模式,其中一些形容词在其修饰的名词中是 input-ish 或 output-ish 。例如,“将字符串做成__的函数”输入为ish,“将字符串变成__的函数”输出为ish。这里的规则涉及到我具有函数 X → Y ,输出一些形容词“ blue”,或者使用 functor (如果可以使用的话)一个将蓝色 X 转换为蓝色 Y 的函数。想一想“喷洒 X s的消防水带”,然后拧紧该 X → Y 功能,现在您的喷洒水 Y < / em> s。或相反,它是输入或 contravariant ,吸尘器吸起 Y s,当我拧上吸尘器时,吸尘器吸起 X s。有些东西既不输出也不输入。事实都是 都是 phantom :它们与它们所描述的名词完全无关,因为您可以定义函数“ coerce”,带有蓝色的 X 并产生蓝色的 Y ,但是*不知道类型 X 或 Y 的详细信息”,甚至不需要它们之间的功能。
因此会显示“ __的列表”,您可以在 X 的列表上映射 X → Y 以获得列表是 Y s。类似地,输出“一对字符串和一个__”。同时,“从__到其自身的函数”既不是输出,也不是输入,而“一个字符串和一个完全为零的__s列表”可能是“幻像”。
但是,是的,编程中的函子就是全部,它们只是可映射的东西。如果某物是函子,则它是唯一函子,最多只有一种方法可以在数据结构上通用地映射函数。
monad 是除两者之外的函子
因此,这意味着存在一个规范函数,将任何 blue -blue __折叠为一个蓝色__。 (当然,我们还添加了一些法律来使一切变得理智:如果蓝色的一层来自通用应用程序,那么我们只想删除该通用应用程序;此外,如果我们将蓝-蓝-蓝X展平为蓝色X,我们折叠前两个 blue 的第一个还是第二个第二个的折叠不应该有任何区别。)
第一个示例是“可为空的__”。因此,如果我给您提供一个可为null的可为null的int,从某种意义上讲,我给您的内容不多于一个可为null的int。或“整数列表的列表”,如果要使它们包含0个或多个,则“整数列表”工作得很好,并且正确的折叠将所有列表连接在一起成为一个超级列表。 / p>
Monads在Haskell变得很重要,因为Haskell需要一种方法来在现实世界中做事,而又不会违反其数学上纯净的世界,在现实世界中什么也没发生。解决方案是添加 metaprogramming 的某种形式,其中我们引入形容词“一个产生__的程序”。那么,如何获取当前日期?好吧,没有unsafePerformIO
,Haskell不能直接做到这一点,但是它可以让您描述并编写产生当前日期的程序。在这种愿景下,您应该描述一个不产生任何东西的程序,称为“ Main.main”,并且编译器应该以您的描述为基础,并将该程序作为二进制可执行文件交给您,以便您可以在任何时候运行。
无论如何,“一个产生一个能产生一个整数的程序的程序”并不会比“一个能产生一个整数的程序”买得多,所以事实证明这是一个单子。
与函子不同,单子不是唯一的单子。给定函子不仅有一个monad实例。因此,例如“一对int和__”,您正在使用该int做什么?您可以将其相加,也可以相乘。如果将其设置为可为null的int,则可以保留最小非空值1或最大非空值1,或者最左边的非空值1或最右边的非空值1。
给定函子的免费monad是“最无聊”的结构,它只是“免费的蓝色 X 是蓝色的 n X 表示任意 n = 0、1、2,...”。
这是通用的,因为蓝色的 X 只是 X 。一个免费的蓝色免费蓝色 X 是一个蓝色 m 蓝色 n X ,它只是一个蓝色的 m + n X 。它实现了“崩溃”,因此根本不实现崩溃,内部是任意嵌套蓝调。
这也意味着您可以将选择的单子确切地推迟到以后,然后再定义将 blue -blue X 减小为蓝色的函数。 X 并将所有折叠为蓝色 0,1 X ,然后将另一个函数从 X 折叠为蓝色 X 始终为您提供蓝色 1 X 。