Haskell 自由单子的可视化
我想我大致知道什么是免费单子,但我想有一个更好的方式来形象化它 自由岩浆只是二叉树,这是有道理的,因为它尽可能地“通用”,而不会丢失任何信息 类似地,自由幺半群只是列表也是有道理的,因为操作顺序并不重要。现在“二叉树”中有一个冗余,所以如果有意义的话,你可以把它展平 出于类似的原因,自由群体看起来有点像分形,这是有道理的: 为了得到其他组,我们只需将组中的不同元素识别为“相同”,就可以得到其他组 我应该如何想象自由单子?现在,我认为它是你能想象到的最通用的抽象语法树。本质上是这样吗?还是我过于简单化了 同样,从一个自由单子到一个列表或其他单子,我们会损失什么?我们认为什么是“相同的” 我感谢所有能说明这一点的评论。谢谢 现在,我认为[自由单子]是你能想象到的最通用的抽象语法树。本质上是这样吗?还是我过于简单化了 你太简单化了:Haskell 自由单子的可视化,haskell,monads,free-monad,Haskell,Monads,Free Monad,我想我大致知道什么是免费单子,但我想有一个更好的方式来形象化它 自由岩浆只是二叉树,这是有道理的,因为它尽可能地“通用”,而不会丢失任何信息 类似地,自由幺半群只是列表也是有道理的,因为操作顺序并不重要。现在“二叉树”中有一个冗余,所以如果有意义的话,你可以把它展平 出于类似的原因,自由群体看起来有点像分形,这是有道理的: 为了得到其他组,我们只需将组中的不同元素识别为“相同”,就可以得到其他组 我应该如何想象自由单子?现在,我认为它是你能想象到的最通用的抽象语法树。本质上是这样吗?还是我过于简
Free fa
数据类型的缩写,实际上,对于f
的每个选择,它都是不同的Free monad
本身贡献了什么Free
f
{-# LANGUAGE GADTs #-}
data Program instr a where
Return :: a -> Program instr a
Bind :: instr x -- an "instruction" with result type `x`
-> (x -> Program instr a) -- function that computes rest of program
-> Program instr a -- a program with result type `a`
…其中,instr
type参数表示monad的“指令”类型,通常是GADT。例如(摘自链接):
因此,一个程序
在操作单子中,非正式地说,我将其视为一个指令的“动态列表”,其中执行任何指令产生的结果被用作函数的输入,该函数决定“指令列表”的“尾部”是什么。Bind
构造函数将指令与“tail chooser”函数配对
许多自由单子也可以用类似的术语可视化。你可以说,为给定的自由单子选择的函子充当它的“指令集”。但对于自由单子,“指令”的“尾部”或“子项”由函子本身管理。下面是一个简单的例子(摘自):
在操作单体中,用于生成“尾部”的函数属于程序
类型(在绑定
构造函数中),而在自由单体中,尾部属于“指令”/函子
类型。这允许自由单子的“指令”(一个正在分解的类比)有一个“尾巴”(如输出
或贝尔
)、零尾巴(如完成
)或多个尾巴(如果您选择的话)。或者,在另一种常见模式中,next
参数可以是嵌入式函数的结果类型:
data Terminal a next =
PutStrLn String next
| GetLine (String -> next) -- can't access the next "instruction" unless
-- you supply a `String`.
instance Functor Terminal where
fmap f (PutStrLn str next) = PutStrLn str (f next)
fmap f (GetLine g) = GetLine (fmap f g)
x :: FreeA f a
x = FreeA g [ s, t, u, v]
where g :: b -> c -> d -> e -> a
s :: f b
t :: f c
u :: f d
v :: f e
顺便说一句,我长期以来一直反对将自由单子或可操作单子称为“语法树”的人——实际使用它们需要将节点的“子节点”不透明地隐藏在函数中。你通常不能完全检查这棵“树”
实际上,当你开始讨论它时,如何可视化一个自由单子完全取决于你用来参数化它的函子的结构。有些看起来像列表,有些看起来像树,还有一些看起来像“不透明的树”,函数作为节点。(有人曾经用一句话回应我的反对意见,比如“函数是一个树节点,有尽可能多的子节点和可能多的参数。”)你可能听说过
Monad是内函子范畴中的幺半群
你已经提到了幺半群只是列表。原来你在这里
对此进行一点扩展:
data Free f a = Pure a
| Free (f (Free f a))
它不是一个普通的a
列表,而是一个尾部被包裹在f
中的列表。如果编写多个嵌套绑定的值结构,您将看到:
pure x >>= f >>= g >>= h :: Free m a
可能导致
Free $ m1 $ Free $ m2 $ Free $ m3 $ Pure x
where m1, m2, m3 :: a -> m a -- Some underlying functor "constructors"
如果上例中的m
为总和类型:
data Sum a = Inl a | Inr a
deriving Functor
然后列表实际上是一棵树,在每个构造函数中,我们可以向左或向右分支
你可能听说过
Applicative是一类内函子中的幺半群
。。。类别完全不同。在中有不同的免费应用程序编码的很好的可视化
所以freeApplicative
也是一个列表。我把它想象成一个由fa
值和单个函数组成的异构列表:
data Terminal a next =
PutStrLn String next
| GetLine (String -> next) -- can't access the next "instruction" unless
-- you supply a `String`.
instance Functor Terminal where
fmap f (PutStrLn str next) = PutStrLn str (f next)
fmap f (GetLine g) = GetLine (fmap f g)
x :: FreeA f a
x = FreeA g [ s, t, u, v]
where g :: b -> c -> d -> e -> a
s :: f b
t :: f c
u :: f d
v :: f e
在这种情况下,尾部本身并不是用f
包装的,而是分别用每个元素包装的。这可能有助于理解Applicative
和Monad
之间的区别
请注意,f
不需要是函子
就可以使应用(freeafa)
,与上面的Free
单子相反
还有免费的Functor
data Coyoneda f a = Coyoneda :: (b -> a) -> f b -> Coyoneda f a
这使得任何*->*
类型函子
。将其与上面的免费Applicative
进行比较。
在应用案例中,我们有一个长度n为fa
值的异构列表,以及一个组合它们的n元函数。
Coyoneda是上述的一元特例
我们可以将Coyoneda
和Free
结合起来,使可操作
免费单子。正如其他答案所提到的,一棵树是可以想象的,因为它涉及到一些功能。