我想我已经大致了解了免费monad是什么,但我希望有一个更好的方法可视化它。
有意义的是,自由岩浆只是二元树,因为它是"一般"因为你可以不丢失任何信息。
同样,有意义的是,免费幺半群只是列表,因为操作的顺序并不重要。现在在"二叉树"中存在冗余,所以如果有意义的话,你可以将它展平。
由于类似的原因,自由群体看起来像分形是有道理的:https://en.wikipedia.org/wiki/Cayley_graph#/media/File:Cayley_graph_of_F2.svg 为了获得其他群体,我们只是将群体中的不同元素识别为"相同"我们得到其他团体。
我应该如何看待免费的monad?现在,我只是把它想象成你能想象的最通用的抽象语法树。这本质上是吗?还是我过分简化了?
此外,类似地,从免费monad到列表或其他monad我们会失去什么?我们认为什么是"相同"?
我感谢所有能够阐明这一点的评论。谢谢!
答案 0 :(得分:12)
现在,我只想到[免费monad]作为你能想象到的最通用的抽象语法树。这本质上是吗?还是我过分简化了?
你过分简单了:
Free f a
数据类型的缩写,实际上每个f
数据类型都是Free
。 1}}。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`
但我们采取不同的方法。我通过首先研究密切相关的operational monad来学习免费的monad,它具有更均匀,更易于可视化的结构。我强烈建议你从链接本身学习。
定义操作monad的最简单方法是:
instr
...其中data StackInstruction a where
Pop :: StackInstruction Int
Push :: Int -> StackInstruction ()
类型参数表示monad的“指令”类型,通常是GADT。例如(取自链接):
Program
操作monad中的Bind
,非正式地,我将其可视化为指令的“动态列表”,其中由任何指令的执行产生的结果用作决定的函数的输入什么是“指令清单”的“尾巴”。 Functor
构造函数将指令与“尾选择器”函数配对。
许多免费的monad也可以用类似的术语表示 - 你可以说为给定的免费monad选择的仿函数作为它的“指令集”。但是对于免费的monad,“指令”的“尾巴”或“孩子”由data Free f r = Free (f (Free f r)) | Pure r
-- The `next` parameter represents the "tails" of the computation.
data Toy b next =
Output b next
| Bell next
| Done
instance Functor (Toy b) where
fmap f (Output b next) = Output b (f next)
fmap f (Bell next) = Bell (f next)
fmap _ Done = Done
本身管理。这是一个简单的例子(取自Gabriel González's popular blog entry on the topic):
Program
在操作monad中,用于生成“tail”的函数属于Bind
类型(在Functor
构造函数中),在free monads中,tails属于“instruction”/ { {1}}类型。这允许免费monad的“指令”(现在正在分解的类比)具有单个“尾部”(如Output
或Bell
),零尾(如Done
)或如果您愿意,可以选择多个尾巴。或者,在另一种常见模式中,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)
顺便提一下,对于将自由或操作monad称为“语法树”的人来说,这是一个异议。对它们的实际使用要求节点的“子节点”不透明地隐藏在函数内。你通常不能完全检查这个“树”!
所以,实际上,当你了解它时,如何可视化一个免费的monad完全归结为你用来参数化它的Functor
的结构。有些看起来像列表,有些看起来像树,有些看起来像“不透明的树”,功能作为节点。 (有人曾经用一句话来回应我的反对意见,例如“一个函数是一个有很多孩子的树节点,就像有可能的论点一样。”)
答案 1 :(得分:1)
你可能听说过
Monad是endofunctors类别中的monoid
你已经提到过monoid只是列表。所以你有。
扩展一点:
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是endofunctors类别中的monoid
......类别不同。 Roman Cheplyaka's blog post中有不同的免费应用编码可视化。
所以免费Applicative
也是一个列表。我将它想象为f a
值的异构列表,以及单个函数:
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
并非需要Functor
才能Applicative (FreeA f a)
成为Free
monad上面的Functor
。
还有免费data Coyoneda f a = Coyoneda :: (b -> a) -> f b -> Coyoneda f a
* -> *
可生成任何Functor
类型Applicative
。将其与上面的免费f a
进行比较。
在应用案例中,我们有Coyoneda
值的长度 n 的异构列表和组合它们的n元函数。
Coyoneda是上述1-ary特例。
我们可以将Free
和Operational
合并为{{1}}免费monad。正如其他答案所提到的那样,因为有涉及的功能,所以人们可以想象出一棵树。 OTOH你可以把这些延续想象成你图片中不同的神奇箭头:)