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


当前回答

爱德华·克米特的回答显然很棒。但是,这有点技术性。这里有一个可能更容易理解的解释。

自由单子只是将函子转换为单子的一种通用方法。也就是说,给定任意函子,自由f是一个单子。这不是很有用,除非你得到一对函数

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

第一个选项让你“进入”你的单子,第二个选项让你“退出”单子。

更一般地说,如果X是一个Y,加上一些额外的P,那么“自由X”是一种从Y到X而不获得任何额外东西的方法。

例如:一个单类(X)是一个带有额外结构(P)的集合(Y),它基本上说它有一个操作(你可以想到加法)和一些恒等式(比如零)。

So

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

现在,我们都知道列表

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

对于任意类型的t,我们知道[t]是一个一元曲面

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

因此列表是集合(或Haskell类型)之上的“自由单类”。

免费单子也是一样的。我们取一个函子,然后返回一个单子。事实上,由于单子可以被视为内函子范畴内的单类,一个列表的定义

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)

其他回答

试图在这里的超简单答案和完整答案之间提供一个“桥梁”答案。

所以“free monads”从任何“functor”中构建一个“monad”,让我们按顺序来看。

函子的细节

有些东西是类型级形容词,这意味着它们接受一个类型名词,比如“整数”,然后返回一个不同的类型名词,比如“整数列表”或“带整数的字符串对”,甚至是“用整数组成字符串的函数”。为了表示任意一个形容词,让我用“blue”来代替。

But then we notice a pattern that some of these adjectives are input-ish or output-ish in the noun they modify. For example “functions making strings out of __” is input-ish, “functions turning strings into __” is output-ish. The rule here involves me having a function X → Y, some adjective “blue” is outputtish, or a functor, if I can use such a function to transform a blue X into a blue Y. Think of “a firehose spraying Xs” and then you screw on this X → Y function and now your firehose sprays Ys. Or it is inputtish or a contravariant if it is the opposite, a vacuum cleaner sucking up Ys and when I screw this on I get a vacuum sucking up Xs. Some things are neither outputtish nor inputtish. Things that are both it turns out are phantom: they have absolutely nothing to do with the nouns that they describe, in the sense that you can define a function “coerce” which takes a blue X and makes a blue Y, but *without knowing the details of the types X or Y,” not even requiring a function between them.

所以" lists of __ "是输出的,你可以将X→Y映射到X的列表上得到Y的列表。类似地," a pair of a string and a __ "是输出的。同时,“从__到自身的函数”既不是输出的也不是输入的”,而“一个字符串和一个恰好0个__s的列表”可能是“幻影”情况。

这就是编程中函数子的全部内容,它们只是可映射的东西。如果某物是函子,那么它是唯一的函子,最多只有一种方法将一个函数映射到一个数据结构上。

单体

单子是一个函子,它同时是两者

普遍适用,给定任意X,我可以构造一个蓝色的X, 可以在不改变意思的情况下重复。所以蓝色的X在某种程度上和蓝色的X是一样的。

这意味着有一个规范函数将任何蓝色的__折叠为蓝色的__。(当然,我们会添加一些定律让一切变得理智:如果其中一层蓝色来自通用应用,那么我们就想擦掉这个通用应用;此外,如果我们将一个蓝色-蓝色-蓝色X压扁为一个蓝色X,那么我们是先折叠前两个蓝色还是先折叠后两个蓝色应该没有区别。)

第一个例子是“可空__”。所以如果我给你一个可为空的int,在某种意义上,我给你的只是一个可为空的int。或者“整型数组列表的列表”,如果目的是要有0个或多个整型数组,那么“整型数组列表”就可以了,正确的折叠是将所有列表连接到一个超级列表中。

Monads became big in Haskell because Haskell needed an approach to do things in the real world without violating its mathematically pure world where nothing really happens. The solution was to add is sort of watered down form of metaprogramming where we introduce an adjective of “a program which produces a __.” So how do I fetch the current date? Well, Haskell can't do it directly without unsafePerformIO but it will let you describe and compose the program which produces the current date. In this vision, you are supposed to describe a program which produces nothing called “Main.main,” and the compiler is supposed to take your description and hand you this program as a binary executable for you to run whenever you want.

不管怎样,"一个程序生成一个产生int型的程序"并不比"一个程序生成int型"更有价值所以这是一个单子。

的自由单体

与函子不同,单子不是唯一的单子。对于一个给定的函子,不只有一个单子实例。例如“一对int和__”,你用这个int做什么?你可以相加,也可以相乘。如果你让它成为一个可空的int,你可以保留最小的非空值或者最大的非空值,或者最左边的非空值或者最右边的非空值。

给定函子的自由单子是“最无聊”的结构,它只是“一个自由的蓝色X对于任何n = 0,1,2,…都是一个蓝色X”。

它是通用的,因为一个蓝色⁰X只是一个X,而一个自由的蓝色X是一个蓝色X,它只是一个蓝色+n X。它实现了“崩溃”,因此根本不实现崩溃,在内部蓝色是任意嵌套的。

这也意味着你可以将你选择的单子推迟到以后的日期,以后你可以定义一个函数,它将蓝-蓝X缩减为蓝X,并将所有这些缩减为blue0,1 X,然后从X到蓝X的另一个函数将始终提供blue1 X。

Haskell free monad是一个函子列表。比较:

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

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

Pure类似于Nil, Free类似于Cons。Free单子存储的是一个函子列表,而不是一个值列表。从技术上讲,你可以使用不同的数据类型来实现免费的单子,但是任何实现都应该与上面的同构。

只要需要抽象语法树,就可以使用免费的单子。自由单子的基函子是语法树中每一步的形状。

我的文章(已经有人链接了)给出了几个如何用免费单子构建抽象语法树的例子

这里有一个更简单的答案:一个Monad是一些“计算”时,Monad上下文被join:: m (m A) -> m A(回忆>>=可以定义为x >>= y = join (fmap y x))。这就是Monads如何通过一个连续的计算链携带上下文:因为在系列中的每个点上,来自前一个调用的上下文与下一个调用一起折叠。

一个自由的单子满足所有的单子定律,但不做任何折叠(即计算)。它只是建立了一系列嵌套的上下文。创建这样一个自由单体值的用户负责对这些嵌套上下文做一些事情,这样组合的含义可以延迟到单体值创建之后。

爱德华·克米特的回答显然很棒。但是,这有点技术性。这里有一个可能更容易理解的解释。

自由单子只是将函子转换为单子的一种通用方法。也就是说,给定任意函子,自由f是一个单子。这不是很有用,除非你得到一对函数

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

第一个选项让你“进入”你的单子,第二个选项让你“退出”单子。

更一般地说,如果X是一个Y,加上一些额外的P,那么“自由X”是一种从Y到X而不获得任何额外东西的方法。

例如:一个单类(X)是一个带有额外结构(P)的集合(Y),它基本上说它有一个操作(你可以想到加法)和一些恒等式(比如零)。

So

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

现在,我们都知道列表

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

对于任意类型的t,我们知道[t]是一个一元曲面

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

因此列表是集合(或Haskell类型)之上的“自由单类”。

免费单子也是一样的。我们取一个函子,然后返回一个单子。事实上,由于单子可以被视为内函子范畴内的单类,一个列表的定义

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)

我想举个简单具体的例子会有帮助。假设我们有一个函子

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 -nodes有一个子节点,Two-和Two'-nodes有两个子节点,three -nodes有三个子节点,也被标记为Int。

Free F是一个单子。Return将x映射到树,它是一个值为x的叶子。t >>= f查看每个叶子并将它们替换为树。当叶结点的值为y时,它就会用树f y替换叶结点。

一个图表使这更清楚,但我没有工具来轻松地画一个!