关于haskell:什么是免费monad?

What are free monads?

我偶尔会看到"免费单子"这个词突然出现,但每个人似乎都在使用/讨论它们,而没有解释它们是什么。那么:什么是免费单子?(我想说我熟悉Monads和Haskell的基础知识,但对范畴理论只有很粗略的了解。)


这里有一个更简单的答案:monad是当一元上下文被join :: m (m a) -> m a崩溃时"计算"的东西(回顾>>=可以定义为x >>= y = join (fmap y x))。这就是monads如何通过连续的计算链传递上下文:因为在序列中的每个点上,前一个调用的上下文都与下一个调用折叠在一起。

自由Monad满足所有Monad定律,但不会发生任何崩溃(即计算)。它只是构建一系列嵌套的上下文。创建这样一个自由的一元值的用户负责处理这些嵌套上下文,这样一个组合的含义可以延迟到创建一元值之后。


爱德华·凯密特的回答显然很好。但是,这有点技术性。下面是一个更容易理解的解释。

自由单子只是把函子变成单子的一般方法。也就是说,任何函数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

现在,我们都知道名单

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

好吧,考虑到任何类型的t,我们知道[t]是单体的。

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

所以列表是集合(或者haskell类型)上的"自由单倍体"。

好吧,所以免费单子也是一样的想法。我们取了一个函子,还给了一个单子。事实上,由于单子可以看作是内函数类中的单子,因此列表的定义

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

看起来很像自由单子的定义

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)


一个自由的foo恰好是满足所有"foo"定律的最简单的东西。也就是说,它完全满足了成为一个foo所必需的法律,没有什么额外的。好的。

一个健忘函数是当结构从一个类别转到另一个类别时"健忘"结构的一部分。好的。

在给定函子F : D -> CG : C -> D的情况下,我们称F -| GF左与G相邻,或者G右与F相邻,每当a、b:F a -> ba -> G b同构时,箭头都来自适当的类别。好的。

形式上,一个自由函子与一个健忘函子相伴随。好的。

自由单倍体好的。

让我们从一个简单的例子开始,自由单倍体。好的。

以一个单倍体为例,它由一些载流子集合T定义,一个二进制函数将一对元素混合在一起,f :: T → T → Tunit :: 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 → ba → 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给予一个自然变换是一样的。好的。好啊。


自由monad(数据结构)是对monad(类)的,就像列表(数据结构)对monoid(类)的:它是一个简单的实现,您可以在其中决定如何组合内容。

你可能知道Monad是什么,每个Monad都需要一个特定的(遵守Monad法律的)实施,要么是fmapjoinreturnbindreturn

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

这可以使用自由Monad(数据结构)来完成,它以这样的方式包装函数(类型),使得join是这些函数的叠加,而不是减少。

您要使用的实际returnjoin现在可以作为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))

PureNil相似,FreeCons相似。自由monad存储函数列表,而不是值列表。从技术上讲,您可以使用不同的数据类型实现自由monad,但是任何实现都应该与上面的实现是同构的。

只要需要抽象语法树,就可以使用自由单子。自由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节点有一个子节点,TwoTwo'节点有两个子节点,Three节点有三个子节点,还带有Int标记。

Free f是单胞菌。returnx映射到树上,该树只是一片叶子,值为xt >>= f看着每一片叶子,用树代替它们。当叶子的值为y时,它会用树f y代替叶子。

一张图表可以更清楚地说明这一点,但我没有方便地绘制的工具!