可视化免费Monad

时间:2015-12-02 20:28:48

标签: haskell monads free-monad

我想我已经大致了解了免费monad是什么,但我希望有一个更好的方法可视化它。

有意义的是,自由岩浆只是二元树,因为它是"一般"因为你可以不丢失任何信息。

同样,有意义的是,免费幺半群只是列表,因为操作的顺序并不重要。现在在"二叉树"中存在冗余,所以如果有意义的话,你可以将它展平。

由于类似的原因,自由群体看起来像分形是有道理的:https://en.wikipedia.org/wiki/Cayley_graph#/media/File:Cayley_graph_of_F2.svg 为了获得其他群体,我们只是将群体中的不同元素识别为"相同"我们得到其他团体。

我应该如何看待免费的monad?现在,我只是把它想象成你能想象的最通用的抽象语法树。这本质上是吗?还是我过分简化了?

此外,类似地,从免费monad到列表或其他monad我们会失去什么?我们认为什么是"相同"?

我感谢所有能够阐明这一点的评论。谢谢!

2 个答案:

答案 0 :(得分:12)

  

现在,我只想到[免费monad]作为你能想象到的最通用的抽象语法树。这本质上是吗?还是我过分简化了?

你过分简单了:

  1. “免费monad”是“免费monad 优于特定仿函数”或Free f a数据类型的缩写,实际上每个f数据类型都是Free。 1}}。
  2. 所有免费的monad都没有一个通用的结构。他们的结构分解为:
    • 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`
    • 的不同选择所贡献的内容
  3. 但我们采取不同的方法。我通过首先研究密切相关的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的“指令”(现在正在分解的类比)具有单个“尾部”(如OutputBell),零尾(如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中,而是分别包含每个元素。这可能有助于理解ApplicativeMonad之间的差异。

请注意,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特例。

我们可以将FreeOperational合并为{{1}}免费monad。正如其他答案所提到的那样,因为有涉及的功能,所以人们可以想象出一棵树。 OTOH你可以把这些延续想象成你图片中不同的神奇箭头:)