什么是免费monad?

时间:2012-11-12 21:53:52

标签: haskell monads free-monad

我已经看到 Free Monad 一词弹出every now and then一段时间了,但每个人似乎都在使用/讨论它们而不解释它们是什么。所以:什么是免费的monads? (我会说我熟悉monad和Haskell的基础知识,但对类别理论只有非常粗略的了解。)

7 个答案:

答案 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 -> CG : C -> D,我们说F -| GFG保持联系,或者G是正确的伴随每当forall a,b:FF 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 → ba → U b

同构

现在,请记住F的目标位于幺半群的Mon类别中,其中箭头是幺半群同态,因此我们需要一个来证明来自[a] → b的幺半群同态可以是由a → b的函数精确描述。

在Haskell中,我们称之为Set(呃,Hask,我们假装设置的Haskell类型的类别),只有foldMap,当时专门从Data.Foldable到列表的类型为Monoid m => (a → m) → [a] → m

这是一个后果,因为这是一个附属物。值得注意的是,如果你忘记然后自由积累,那么再次忘记,它就像你忘了一次,我们可以用它来建立monadic连接。自UFUFU(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 + returnbind +的特定(Monad-law持久)实现return

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

这可以使用Free Monad(数据结构)来完成,它以这样的方式包装Functor(类型),这样join就是堆叠那些仿函数而不是简化。

您想要使用的真实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 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类似于NilFree类似于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,其节点标有OneTwoTwo'ThreeOne - 节点有一个孩子Two - 和Two' - 节点有两个孩子,Three - 节点有三个,并且还标有Int

Free F是一个单子。 returnx映射到只是值为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列表”可能是“幻像”。

但是,是的,编程中的函子就是全部,它们只是可映射的东西。如果某物是函子,则它是唯一函子,最多只有一种方法可以在数据结构上通用地映射函数。

Monads

monad 是除两者之外的函子

  1. 普遍适用,给定任何 X ,我可以构造蓝色的 X
  2. 可以重复,而无需过多更改含义。因此,蓝色-蓝色 X 在某种意义上与蓝色 X 相同。

因此,这意味着存在一个规范函数,将任何 blue -blue __折叠为一个蓝色__。 (当然,我们还添加了一些法律来使一切变得理智:如果蓝色的一层来自通用应用程序,那么我们只想删除该通用应用程序;此外,如果我们将蓝-蓝-蓝X展平为蓝色X,我们折叠前两个 blue 的第一个还是第二个第二个的折叠不应该有任何区别。)

第一个示例是“可为空的__”。因此,如果我给您提供一个可为null的可为null的int,从某种意义上讲,我给您的内容不多于一个可为null的int。或“整数列表的列表”,如果要使它们包含0个或多个,则“整数列表”工作得很好,并且正确的折叠将所有列表连接在一起成为一个超级列表。 / p>

Monads在Haskell变得很重要,因为Haskell需要一种方法来在现实世界中做事,而又不会违反其数学上纯净的世界,在现实世界中什么也没发生。解决方案是添加 metaprogramming 的某种形式,其中我们引入形容词“一个产生__的程序”。那么,如何获取当前日期?好吧,没有unsafePerformIO,Haskell不能直接做到这一点,但是它可以让您描述并编写产生当前日期的程序。在这种愿景下,您应该描述一个不产生任何东西的程序,称为“ Main.main”,并且编译器应该以您的描述为基础,并将该程序作为二进制可执行文件交给您,以便您可以在任何时候运行。

无论如何,“一个产生一个能产生一个整数的程序的程序”并不会比“一个能产生一个整数的程序”买得多,所以事实证明这是一个单子。

免费Monads

与函子不同,单子不是唯一的单子。给定函子不仅有一个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