的文档Free
说:
许多常见的单子以自由单子的形式出现,
- Given
data Empty a
,与monadFree Empty
同构。Identity
- Free
Maybe
可用于对偏性 monad 建模,其中每一层代表运行计算一段时间。
还有哪些其他 monad 可以用 来表达Free
?
我只能再想到一个:我相信Free (Const e)
与Either e
.
编辑:哪些单子是不可表达的Free
,为什么?
的文档Free
说:
许多常见的单子以自由单子的形式出现,
- Given
data Empty a
,与monadFree Empty
同构。Identity
- Free
Maybe
可用于对偏性 monad 建模,其中每一层代表运行计算一段时间。
还有哪些其他 monad 可以用 来表达Free
?
我只能再想到一个:我相信Free (Const e)
与Either e
.
编辑:哪些单子是不可表达的Free
,为什么?
几乎所有这些(直到涉及循环和 的问题mfix
)但不是Cont
.
考虑State
单子
newtype State s a = State (s -> (a,s))
看起来一点也不像一个免费的单子......但考虑一下State
你如何使用它
get :: m s --or equivalently (s -> m a) -> m a
set :: s -> m () --or (s,m a) -> m a
runState :: m a -> s -> (a,s)
我们可以通过将操作列为构造函数来设计具有此接口的免费 monad
data StateF s a
= Get (s -> a) | Set s a deriving Functor
那么我们有
type State s a = Free (StateF s) a
和
get = Impure (Get Pure)
set x = Impure (Set x (Pure ())
我们只需要一种使用它的方法
runState (Pure a) s = (a,s)
runState (Impure (Get f)) s = runState (f s) s
runState (Impure (Set s next)) _ = runState next s
你可以用大多数单子来做这个构造。就像可能/偏颇性单子由以下定义
stop :: m a
maybe :: b -> (a -> b) -> m a -> b
m x
规则是,我们将每个以 some结尾的函数x
视为函子中的构造函数,而其他函数是运行生成的自由 monad 的方式。在这种情况下
data StopF a = StopF deriving Functor
maybe _ f (Pure a) = f a
maybe b _ (Impure Stop) = b
为什么这么酷?有几件事
f a -> a
,a
当f
它是一个函子时),组合也有那个代数。 函子组合只是我们可以通过多种方式组合函子,其中大多数都保留了该代数。在这种情况下,我们想要的不是函子的组合, (f (g (x)))
而是函子 coproduct。函子添加
data f :+: g a = Inl (f a) | Inr (g a)
instance (Functor f, Functor g) => Functor (f :+: g) where
fmap f (Inl x) = Inl (fmap f x)
fmap f (Inr x) = Inr (fmap f x)
compAlg :: (f a -> a) -> (g a -> a) -> f :+: g a -> a
compAlg f _ (Inl x) = f x
compAlf _ g (Inr x) = g x
自由单子也保存代数
freeAlg :: (f a -> a) -> Free f a -> a
freeAlg _ (Pure a) = a
freeAlg f (Impure x) = f $ fmap (freeAlg f) x
在 Wouter Swierstra 的著名论文Data Types A La Carte中,这被用来取得很好的效果。该论文中的一个简单示例是计算器。我们将对这篇文章采取一元化的方式。给定代数
class Calculator f where
eval :: f Integer -> Integer
我们可以想到各种实例
data Mult a = Mult a a deriving Functor
instance Calculator Mult where
eval (Mult a b) = a*b
data Add a = Add a a deriving Functor
instance Calculator Add where
eval (Add a b) = a+b
data Neg a = Neg a deriving Functor
instance Calculator Neg where
eval (Neg a) = negate a
instance Calculator (Const Integer) where
eval (Const a) = a
data Signum a = Signum a deriving Functor
instance Calculator Signum where
eval (Signum a) = signum a
data Abs a = Abs a deriving Functor
instance Calculator Abs where
eval (Abs a) = abs a
和最重要的
instance (Calculator f, Calculator g) => Calculator (f :+: g) where
eval = compAlg eval
你可以定义数字单子
newtype Numerical a = Numerical (
Free (Mult
:+: Add
:+: Neg
:+: Const Integer
:+: Signum
:+: Abs) a deriving (Functor, Monad)
然后你可以定义
instance Num (Numerical a)
这可能完全没用,但我觉得很酷。它确实可以让您定义其他内容,例如
class Pretty f where
pretty :: f String -> String
instance Pretty Mult where
pretty (Mult a b) = a ++ "*" ++ b
其余的都类似。
这是一个有用的设计策略:列出你想让你的 monad 做的事情 ==> 为每个操作定义函子 ==> 找出它的一些代数应该是什么 ==> 为每个操作定义那些函子 ==> 让它快速地。
让它变快很难,但我们有一些技巧。技巧 1 是把你的自由单子包裹起来Codensity
(“更快的按钮”),但是当这不起作用时,你想摆脱自由表示。记得当我们有
runState (Pure a) s = (a,s)
runState (Impure (Get f)) s = runState (f s) s
runState (Impure (Set s next)) _ = runState next s
好吧,这是一个从结果类型Free StateF a
到s -> (a,s)
只使用结果类型的函数,因为我们对状态的定义似乎是合理的……但是我们如何定义操作呢?在这种情况下,您知道答案,但推导它的一种方法是根据 Conal Elliott 所谓的类型类态射进行思考。你要
runState (return a) = return a
runState (x >>= f) = (runState x) >>= (runState f)
runState (set x) = set x
runState get = get
这很容易
runState (return a) = (Pure a) = \s -> (a,s)
runState (set x)
= runState (Impure (Set x (Pure ())))
= \_ -> runState (Pure ()) x
= \_ -> (\s -> (a,s)) x
= \_ -> (a,x)
runState get
= runState (Impure (Get Pure))
= \s -> runState (Pure s) s
= \s -> (s,s)
这非常有帮助。以这种方式推导>>=
可能很困难,我不会在此处包含它,但其中的其他定义正是您所期望的。
要回答所说的问题,您在问题中没有提到的大多数熟悉的单子本身并不是自由单子。Philip JF 的回答暗示了如何将给定的 monad 与一个新的、自由的 monad 联系起来,但新的 monad 会“更大”:它比原来的 monad 具有更多不同的值。例如 real State s
monad 满足get >>= put = return ()
,但 free monadStateF
不满足。作为一个自由单子,它不满足定义上的额外方程;这就是自由的概念。
我将证明Reader r
,Writer w
和State s
monad 不是免费的,除非在r
/ w
/的特殊条件下s
。
让我介绍一些术语。如果m
是 monad,那么我们称任何类型m a
的值为 ( m
-) 动作。m
如果一个 -action 等于return x
for some ,我们称它为平凡x
;否则我们称它为非平凡的。
如果m = Free f
是函子上的任何自由单子f
,则m
承认单子同态Maybe
。这是因为Free
它的参数是函子的,f
而函子类别中的终端对象在Maybe = Free (Const ())
哪里。Const ()
具体来说,这个同态可以写成
toMaybe :: Free f a -> Maybe a
toMaybe (Pure a) = Just a
toMaybe (Impure _) = Nothing
因为toMaybe
是 monad 同态,所以它特别满足toMaybe (v >> w) = toMaybe v >> toMaybe w
任何m
-actionv
和w
. 现在观察toMaybe
将琐碎m
的 -actions 发送到琐碎的Maybe
-actions 和m
nontrivial -actions 到 nontrivial Maybe
-action Nothing
。现在Maybe
有这样的性质,即>>
以任何顺序对任何动作进行非平凡动作,都会产生非平凡动作(Nothing >> w = v >> Nothing = Nothing
);所以对于 也是如此m
,因为toMaybe
是一个保持(非)平凡的单子同态。
(如果您愿意,也可以直接从>>
免费 monad 的公式中验证这一点。)
为了证明一个特定的 monadm
不与任何自由 monad 同构,只需找到m
-actionsv
和w
至少一个v
和w
不是平凡的而是v >> w
平凡的。
Reader r
满足v >> w = w
,所以我们只需要选择w = return ()
和 任何非平凡的行动v
,只要它r
至少有两个值就存在(那么ask
是非常量,即非平凡)。
对于Writer w
,假设除了恒等元之外还有一个可逆元素k :: w
。让它kinv :: w
成为它的倒数。那么tell k >> tell kinv = return ()
, 但是tell k
和tell kinv
是不平凡的。任何非平凡群(例如加法下的整数)都有这样的元素k
。我假设形式的自由单子Writer w
只是那些幺半群w
本身是自由的,即w = [a]
, Writer w ~ Free ((,) a)
。
因为State s
,类似地,如果允许任何具有逆的s
非平凡自同构,则. 任何具有至少两个元素和可判定相等的类型都具有这样的自同构。f :: s -> s
finv :: s -> s
modify f >> modify finv = return ()
s
根据 Reid 回答中的见解,我写了一个证明,证明 list monad 在发布到 haskell-cafe mailing list时不是免费的:
回想一下,对于任何函子 f,我们可以构造 f 上的自由单子:
data Free f a = Pure a | Roll (f (Free f a))
直观地说,Free fa 是具有 a 型叶子的 f 形树的类型。连接操作只是将树嫁接在一起,不执行任何进一步的计算。在这篇文章中,表单(Roll _)的值应称为“非平凡”。
一些常见的单子实际上是一些函子上的自由单子:
Tree monad 是 functor 上的自由 monad Pair
,其中
data Pair a = MkPair a a
. 在这个例子中,对形状树的直觉是最强烈的。每个父节点都有一对子节点。
Maybe monad 是函子上的自由 monad Unit
,其中
data Unit a = MkUnit
. 在图形图片父节点有一个
Unit
孩子,所以根本没有孩子。所以正好有两棵
Unit
- 形的树:仅由叶子组成的树(对应于Just _
)和由无子父节点组成的三棵树(对应于Nothing
)。
Identity
monad 是 functor 上的自由 monad ,Void
其中Void a
是没有构造函数的类型。
Partiality
monad 是 free monad Maybe
over 。
Writer [r]
monad 是 functor 上的自由monad (r,)
。在(r,)
- 形树中,父节点用 type 值装饰,r
并且只有一个子节点。遍历这样的路径会产生一个r
's 列表和一个 type 的叶值a
,因此总共有一个 type 的值([r],a) = Writer r a
。
(特别Free (r,) ()
是 同构于[r]
。这个观察导致了列表单子是免费的。但这个声明是错误的,并且确实不是从观察中得出的。为了证明一个单子m
是免费的,一个人必须展示一个单子forall a. m a -> Free f a
某些函子的同构f
。相比之下,展示 with 的同构m a
既不
Free (f a) ()
充分也不必需。)
更多的单子是自由单子的商。例如,State s
是 的商Free (StateF s)
,其中
data StateF s a = Put s a | Get (s -> a).
-- NB: This functor is itself a free functor over a type constructor
-- which is sometimes called StateI [1], rendering Free (StateF s) what
-- Oleg and Hiromi Ishii call a "freer monad" [2].
商由以下单子态射展示,它为自由单子的纯句法值提供语义。
run :: Free (StateF s) a -> State s a
run (Pure x) = return x
run (Roll (Put s m)) = put s >> run m
run (Roll (Get k)) = get >>= run . k
这种观点是 apfelmus [1] 的操作包的基础,也在 StackOverflow 线程 [3] 中讨论过。这是自由单子在编程环境中有用的主要原因。
那么,列表单子是免费单子吗?直观地说,它不是,因为 list monad(连接)的连接操作不仅将表达式嫁接在一起,而且将它们展平 [4]。
这是一个证明单子列表不是免费的。我正在记录它,因为我对证明感兴趣已经有一段时间了,但是搜索它并没有产生任何结果。不过,线程 [3] 和 [5] 很接近。
在任何函子上的自由单子中,将非平凡动作与任何函数绑定的结果总是非平凡的,即
(Roll _ >>= _) = Roll _
这可以直接从(>>=)
自由单子的定义中检查,或者使用 Reid Barton 将单子态射利用到 Maybe 的技巧,请参阅 [3]。
如果列表 monad 在某个函子上与自由 monad 同构,则同构只会将单例列表映射[x]
到表单的值,(Pure _)
而将所有其他列表映射到非平凡值。这是因为 monad 同构必须与“return”通勤,并且return x
在[x]
list monad 和Pure x
free monad 中。
这两个事实相互矛盾,如以下示例所示:
do
b <- [False,True] -- not of the form (return _)
if b
then return 47
else []
-- The result is the singleton list [47], so of the form (return _).
在通过某个函子对自由单子应用假设的同构之后,我们会得到一个非平凡值([False,True]
同构下的图像)与某个函数的绑定会产生一个平凡值(的图像[47]
,即return 47
)。
干杯,英戈
[1] http://projects.haskell.org/operational/Documentation.html
[2] http://okmij.org/ftp/Haskell/extensible/more.pdf
[4] https://hackage.haskell.org/package/free-4.12.4/docs/Control-Monad-Free.html
[5] https://www.reddit.com/r/haskell/comments/50zvyb/why_is_liststate_not_a_free_monad/
所有 monad 都可以表示为 Free Monad。它们的附加属性(如 MonadFix 和 Cont)可以这样被剥离,因为 Free Monad 不是 Free MonadFix 或 Free Cont。
一般的方法是用 liftM 定义 Functor 的 fmap,然后将 Free 包裹在该 Functor 周围。通过指定函数return
和join
(纯和不纯)必须如何映射到实际的 Monad,可以将生成的 Monad 简化为先前/实际的 Monad。