我偶尔会看到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)

其他回答

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

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替换叶结点。

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

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

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

Free Monad(数据结构)之于Monad(类),就像List(数据结构)之于Monoid(类):这是一个简单的实现,在那里你可以决定如何组合内容。


你可能知道什么是Monad,每个Monad都需要fmap + join + return或bind + return的特定实现(遵循Monad法)。

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

这可以使用Free Monad(数据结构)来完成,它以这样一种方式包装Functor(类型),以便连接是这些Functor的堆叠而不是约简。

你想要使用的实际返回和连接,现在可以作为约约函数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)

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单子存储的是一个函子列表,而不是一个值列表。从技术上讲,你可以使用不同的数据类型来实现免费的单子,但是任何实现都应该与上面的同构。

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

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

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

自由单子只是将函子转换为单子的一种通用方法。也就是说,给定任意函子,自由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)