什么是免费monad?

Dav*_*vid 357 monads haskell free-monad

我见过的长期免费单子弹出每一个 现在 随后一段时间,但每个人似乎只是使用/讨论这些问题没有给予它们是什么解释.所以:什么是免费的monads?(我会说我熟悉monad和Haskell的基础知识,但对类别理论只有非常粗略的了解.)

Joh*_*ley 401

这里有一个更简单的答案:Monad是在monadic context崩溃时"计算"的东西join :: m (m a) -> m a(回想起>>=可以定义为x >>= y = join (fmap y x)).这就是Monads如何通过顺序计算链来传递上下文:因为在序列中的每个点,前一个调用的上下文都会与下一个调用一起折叠.

一个免费的monad满足所有Monad定律,但不做任何崩溃(即计算).它只是构建了一系列嵌套的上下文.创建这样一个自由monadic值的用户负责使用那些嵌套的上下文做某事,这样这个组合的含义可以推迟到创建monadic值之后.

  • 我真的很喜欢这个答案. (18认同)
  • 由于这个解释,我终于'得到'免费monad. (10认同)
  • 你的段落对菲利普的帖子起了很大的作用. (8认同)
  • 免费monad可以取代Monad类型吗?也就是说,我是否可以仅使用免费monad的返回和绑定编写程序,然后使用我喜欢的Mwybe或List或其他任何内容来连接结果,甚至生成一个绑定/连接函数调用序列的多个monadic视图.忽略底部和不确定,即. (5认同)
  • 这个答案有所帮助,但是如果我没有在NICTA课程上遇到"加入"并且阅读http://www.haskellforall.com/2012/06/you-could-have-invented-free-monads,我认为这会让我很困惑html的.所以对我而言,理解的诀窍是阅读大量答案,直到它陷入其中.(NICTA参考:https://github.com/NICTA/course/blob/master/src/Course/Bind.hs) (2认同)
  • @misterbee:它可以替换 Monad 类型类,因为您可以使用单个方法“Free m ~> m”从类中获得等效的公式,就像使用“Monad”一样。https://www.tweag.io/blog/2018-02-05-free-monads/ (2认同)

Phi*_* JF 279

Edward Kmett的回答显然很棒.但是,它有点技术性.这是一个可能更容易理解的解释.

免费monad只是将仿函数转换为monad的一般方法.也就是说,鉴于任何仿函数f Free f都是monad.除非你得到一对函数,否则这不会很有用

liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r) -> Free f r -> r
Run Code Online (Sandbox Code Playgroud)

第一个让你"进入"你的monad,第二个给你一个"走出去"它的方法.

更一般地说,如果X是带有一些额外东西P的Y,那么"自由X"是从Y到X而不获得额外任何东西的一种方式.

示例:monoid(X)是具有额外结构(P)的集合(Y),基本上表示它具有操作(您可以想到添加)和一些身份(如零).

所以

class Monoid m where
   mempty  :: m
   mappend :: m -> m -> m
Run Code Online (Sandbox Code Playgroud)

现在,我们都知道列表

data [a] = [] | a : [a]
Run Code Online (Sandbox Code Playgroud)

好吧,鉴于任何类型,t我们知道这[t]是一个幺半群

instance Monoid [t] where
  mempty   = []
  mappend = (++)
Run Code Online (Sandbox Code Playgroud)

所以列表是集合(或Haskell类型)中的"免费幺半群".

好吧,所以免费的monad是一样的想法.我们带一个仿函数,然后给一个monad.事实上,由于monads可以被视为endofunctors类别中的monoids,因此定义了一个列表

data [a] = [] | a : [a]
Run Code Online (Sandbox Code Playgroud)

看起来很像免费monads的定义

data Free f a = Pure a | Roll (f (Free f a))
Run Code Online (Sandbox Code Playgroud)

并且实例与列表的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
Run Code Online (Sandbox Code Playgroud)

现在,我们得到了两个操作

-- 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)
Run Code Online (Sandbox Code Playgroud)

  • 我认为看看"Free fa = Pure a |"很有意思 Roll(f(Free fa))`as`Free fa = a + fa + ffa + ...`,即"f应用于任意次".然后`concatFree`(即`join`)将"f应用任意次数(f应用任意次数到a)"并将两个嵌套的应用程序折叠成一个.并且`>> =`将"f应用任意次数"和"如何从a到(b用f应用任意次数)",并且基本上将后者应用于前者内部并折叠筑巢.现在我自己得到它! (14认同)
  • 这可能是我所见过的"免费"最好的平易近人的解释.特别是从"更普遍"开始的段落. (11认同)
  • "这可能是一个更容易理解的解释.[...]事实上,由于monad可以被视为endo functor类别中的monoid,..."不过,我认为这是一个非常好的答案. (9认同)
  • @Hjulle Haskell 类型不是集合,Haskell“列表”也不是列表。所以,这确实不应该令人惊讶。`[a]` 具有自由 monad 没有的额外值,因为 Haskell 列表可能是无限的。 (2认同)
  • “monads 可以被看作是内函子类别中的幺半群”<3(你应该链接到 http://stackoverflow.com/a/3870310/1306877 因为每个 Haskeller 都应该知道那个参考!) (2认同)
  • 很棒的答案。初学者可能会对“f”的不同含义感到困惑——在某些情况下它是一种类型,在其他情况下它是一个函数。也许将函数重命名为“g”? (2认同)
  • @jkff:这个公式几乎是正确的!但如果你仔细展开 Free 的递归定义,你不会得到 `Free fa = a + fa + ffa + ...`;你得到`Free fa = a + f (a + f (a + ...) )`。它们是不同的,因为“f (a + b)”(不一定)与“fa + fb”相同。例如,采用“[]”。你的公式会说“Free [] Int”由整数、整数列表、整数列表列表等组成。但是“Free [] Int”更通用 - 它有诸如 [1, [2, 3] 之类的东西,4],这不是其中任何一个。这是因为 `[a + b]` 比 `[a] + [b]` 大。 (2认同)

Edw*_*ETT 136

一个免费的foo恰好是满足所有'foo'定律的最简单的东西.也就是说它完全满足成为foo所必需的法则,而不是额外的.

一个健忘的仿函数是一个"忘记"结构的一部分,因为它从一个类别到另一个类别.

给定仿函数F : D -> C,并且G : C -> D,我们说F -| G,只要forall a,b:是同构的,其中箭头来自适当的类别,它们F是伴随的G,或者G是伴随的.FF a -> ba -> G b

在形式上,一个自由的仿函数与一个健忘的仿函数相伴.

自由幺半群

让我们从一个更简单的例子开始,即免费的幺半群.

取一个由一些载体集定义的monoid,T一个将一对元素混合在一起的二元函数f :: T ? T ? T,以及a unit :: T,使得你有一个关联定律和一个身份定律:f(unit,x) = x = f(x,unit).

你可以U从幺半群的类别(其中箭头是monoid homomorphisms,也就是说,它们确保它们映射unitunit另一个monoid上,并且你可以在映射到另一个monoid之前或之后编写而不改变含义)构成一个仿函数到类别套件(箭头只是功能箭头)"忘记"操作unit,并且只给你载体集.

然后,您可以将一个仿函数F从集合类别定义回与该仿函数相邻的一元组类别.该仿函数是将一组映射a到monoid的仿函数[a],其中unit = []mappend = (++).

那么到目前为止,回顾我们的例子,在伪Haskell中:

U : Mon ? Set -- is our forgetful functor
U (a,mappend,mempty) = a

F : Set ? Mon -- is our free functor
F a = ([a],(++),[])
Run Code Online (Sandbox Code Playgroud)

然后展示F是免费的,需要证明它是伴随的U,一个健忘的仿函数,也就是说,正如我们上面提到的,我们需要证明

F a ? b 是同构的 a ? U b

现在,记住目标FMon幺半群的类别,其中箭头是幺半群同态,所以我们需要一个来证明一个幺半群同态[a] ? b可以用一个函数来精确描述a ? b.

在Haskell中,我们称之为存在于Set(呃,Hask我们假装设置的Haskell类型的类别)的一方,只是foldMap,当专门从Data.FoldableLists类型Monoid m => (a ? m) ? [a] ? m.

这是一个附带的后果.值得注意的是,如果你忘记然后自由积累,那么再次忘记,它就像你忘了一次,我们可以用它来建立monadic连接.由于UFUFU(FUF)UF,我们可以在身份幺同态,从传递[a][a]通过定义我们的红利同构,获取从列表同构[a] ? [a]是类型的函数a -> [a],而这仅仅是返回列表.

您可以通过使用以下术语描述列表来直接撰写所有这些内容:

newtype List a = List (forall b. Monoid b => (a -> b) -> b)
Run Code Online (Sandbox Code Playgroud)

免费Monad

什么是免费Monad

好吧,我们做了我们之前做过的同样的事情,我们从一个健忘的仿函数U开始,从monad类别中箭头是monad同态到一类endofunctors,其中箭头是自然变换,我们寻找一个左边伴随的仿函数那个.

那么,这与通常使用的免费monad的概念有什么关系呢?

知道某个东西是一个自由的monad,Free f告诉你给出一个monad同态Free f -> m,与给出一个自然变换(一个仿函数同态)是一样的(同构的)f -> m.记住F a -> b必须是同构的,a -> U b因为F要与U同伴.这里将monad映射到仿函数.

F至少与Freefree在hackage包中使用的类型同构.

我们还可以通过定义来构建它,以类似于上面的代码为自由列表

class Algebra f x where
  phi :: f x -> x

newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x)
Run Code Online (Sandbox Code Playgroud)

Cofree Comonads

我们可以构造类似的东西,通过查看正确的伴随假定它存在的健忘函子.一个cofree仿函数简单地/正确地伴随着一个健忘的仿函数,并且通过对称性,知道一些cofree comonad就像知道给出一个comonad homomorphism与w -> Cofree f给出一个自然变换一样w -> f.

  • @PauloScardine:你需要完全没有微积分来有效地利用Haskell,甚至理解你在做什么.抱怨"这种语言很糟糕"是有点奇怪的,因为这是一种额外的好处,你可以用它来获得乐趣和利润.你不会在大多数命令式语言中得到这些东西.你为什么要抱怨额外的东西?你可以选择不以数学方式推理,并像任何其他新语言一样接近它. (17认同)
  • @PauloScardine,这不是你要关心的事情.我的问题来自对理解一些高级数据结构的兴趣,并且可能现在可以看到Haskell开发中最前沿的东西 - 它绝不是必要的或代表到目前为止Haskell的实际编写内容.(并且一旦你再次超过IO学习阶段就会变得更好) (12认同)
  • @PauloScardine这有点漂移,但在Haskell的辩护中:类似的技术事物适用于所有其他编程语言,只有Haskell有一个很好的编译,人们可以真正喜欢谈论它们.为什么/如何X是monad对很多人来说很有趣,关于IEEE浮点标准的讨论不是; 这两种情况对大多数人来说都无关紧要,因为他们只能使用结果. (11认同)
  • @PauloScardine你不需要上面的答案就可以在Haskell中高效编程,即使是使用免费的monad.事实上,我不建议以这种方式攻击免费的monad给那些没有类别理论背景的人.从操作的角度来看,有很多方法可以讨论它,并且如何在不深入分类理论的情况下使用它.但是,如果不深入理论,我不可能回答关于它们来自哪里的问题.自由构造是类别理论中的强大工具,但您不需要此背景来使用它们. (8认同)
  • @Sarah:我还没有看到有关Haskell的文档或IRC对话,但是对计算机理论和lambda演算热学并不十分重视。 (3认同)
  • Haskell类型中存在哪种限制/冒号? (2认同)
  • @EdwardKmett:我有点困惑.如果Hask具有所有小的限制,你如何获得均衡器? (2认同)

com*_*nad 65

Free Monad(数据结构)是Monad(类),类似于List(数据结构)到Monoid(类):这是一个简单的实现,您可以在其中决定如何组合内容.


你可能知道Monad是什么,每个Monad需要一个特定的(Monad-law持久)实现fmap+ join+ returnbind+ return.

我们假设您有一个Functor(一个实现fmap),但其余的依赖于在运行时所做的值和选择,这意味着您希望能够使用Monad属性但是之后想要选择Monad函数.

这可以使用Free Monad(数据结构)来完成,它以这样的方式包装Functor(类型),这样join那些函数的堆叠比减少更多.

真实returnjoin你想要使用,现在可以作为减少函数的参数给出foldFree:

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a
Run Code Online (Sandbox Code Playgroud)

为了解释的类型,我们可以更换Functor f使用Monad m,并b(m a):

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)
Run Code Online (Sandbox Code Playgroud)

  • 这个答案让我觉得我明白他们甚至可能会有用. (8认同)
  • 你ELI是一个势在必行的程序员.很好. (3认同)

Gab*_*lez 56

Haskell免费monad是一个仿函数列表.相比:

data List a   = Nil    | Cons  a (List a  )

data Free f r = Pure r | Free (f (Free f r))
Run Code Online (Sandbox Code Playgroud)

Pure类似于Nil并且Free类似于Cons.免费monad存储一个函子列表而不是值列表.从技术上讲,您可以使用不同的数据类型实现免费monad,但任何实现都应该与上面的实现同构.

只要需要抽象语法树,就可以使用免费的monad.free monad的基础仿函数是语法树每一步的形状.

我的帖子,有人已经链接,提供了几个如何使用免费monad构建抽象语法树的示例

  • 我知道你只是在画一个类比而不是制定一个定义,但是一个免费的monad肯定不是类似于任何意义上的仿函数列表*.它更接近一个仿函数树. (6认同)
  • 我支持我的术语.例如,使用我的index-core包你可以定义"free monad comprehensions",它的行为就像list monad一样,除了你绑定了functor而不是value.免费monad是一个函子列表,如果你将所有Haskell概念翻译成仿函数的类别,那么列表就变成了免费的monad.真正的仿函树然后变得完全不同. (6认同)
  • 你是对的,monad是从某种意义上说是monoid概念的分类,因此自由monad类似于自由monoid,即列表.在某种程度上,你肯定是正确的.但是,自由monad的值的结构不是列表.它是一棵树,如[我在下面详述](http://stackoverflow.com/a/13432770/997606). (4认同)
  • @TomEllis从技术上讲,如果你的基础仿函数是一个产品函子,它只是一棵树.当你有一个sum functor作为基础仿函数时,它更像一个堆栈机器. (2认同)

Tom*_*lis 21

我认为一个简单的具体例子会有所帮助.假设我们有一个仿函数

data F a = One a | Two a a | Two' a a | Three Int a a a
Run Code Online (Sandbox Code Playgroud)

显而易见的fmap.然后Free F a是树木,叶子有型的类型a,其节点标记One,Two,Two'Three. One-nodes有一个子节点,TwoTwo'-nodes有两个子Three节点,-nodes有三个子节点,并且还标有一个子节点Int.

Free F是一个单子. return映射x到只是具有值的叶子的树x. t >>= f查看每个叶子并用树替换它们.当叶子有值时,y它用树替换该叶子f y.

图表使这更清楚,但我没有轻松绘制一个的设施!

  • 你们所说的是,免费的monad采用了仿函数本身的形状.因此,如果仿函数是树状的(产品),则自由monad是树状的; 如果它是列表(总和),免费monad是列表式的; 如果它像函数一样,免费monad就像函数一样; 这对我来说很有意义.所以就像在一个免费的monoid中一样,你将mappend的每个应用都视为创造一个全新的元素; 在free monad中,你将functor的每个应用程序视为一个全新的元素. (13认同)
  • 即使算子是一个"总算函子",所得到的免费monad仍然是树状的.您最终会在树中使用多种类型的节点:一个节点的每个组件都有一个节点.如果你的"总算函数"是X - > 1 + X,那么你确实会得到一个列表,这只是一种退化的树. (4认同)

CR *_*ost 9

尝试在此处的超级简单答案和完整答案之间提供 \xe2\x80\x9cbridge\xe2\x80\x9d 答案。

\n

所以 \xe2\x80\x9cfree monads\xe2\x80\x9d 从任何 \xe2\x80\x9cfunctor\xe2\x80\x9d 构建一个 \xe2\x80\x9cmonad\xe2\x80\x9d ,所以让 \xe2\x80 \x99s 按顺序排列这些。

\n

函子详细信息

\n

有些东西是类型级形容词,这意味着它们采用像 \xe2\x80\x9cintegers\xe2\x80\x9d 这样的类型名词,并给你一个不同的类型名词,如 \xe2\x80\x9clists of integers\xe2\x80 \x9d 或 \xe2\x80\x9c 带有整数的字符串对\xe2\x80\x9d 甚至 \xe2\x80\x9c 用整数生成字符串的函数。\xe2\x80\x9d 为了表示任意形容词,让我使用标准 -在单词\xe2\x80\x9cblue\xe2\x80\x9d中。

\n

但随后我们注意到一种模式,其中一些形容词在它们修饰的名词中是输入型输出型。例如, \xe2\x80\x9c 函数从 __\xe2\x80\x9d 生成字符串是类似输入的, \xe2\x80\x9c 函数将字符串转换为 __\xe2\x80\x9d 是类似输出的。这里的规则涉及我有一个函数X \xe2\x86\x92 Y,一些形容词 \xe2\x80\x9cblue\xe2\x80\x9d is outputtish ,或者 functor 如果我可以使用这样的函数来转换蓝色X变成蓝色Y。想想 \xe2\x80\x9ca 消防水带喷射X s\xe2\x80\x9d,然后你拧上这个X \xe2\x86\x92 Y函数,现在你的消防水带喷射Y s。或者,如果相反,则它是输入性的或逆变的,真空吸尘器吸起Y s,当我拧上它时,我得到一个吸尘器吸起X s。有些东西既不是输出也不是输入。事实证明,两者都是幻影它们与它们描述的名词完全无关,从某种意义上说,您可以定义一个函数 \xe2\x80\x9ccoerce\xe2\x80\x9d ,它接受一个蓝色的X和制作一个蓝色的Y ,但是*在不知道类型XY的详细信息的情况下,\xe2\x80\x9d 甚至不需要它们之间的函数。

\n

因此 __\xe2\x80\x9d 的 \xe2\x80\x9clists 是输出性的,您可以将X \xe2\x86\x92 Y映射到X列表上以获得Y列表。类似地, \xe2\x80\x9ca 一对字符串和 __\xe2\x80\x9d 是输出性的。同时 \xe2\x80\x9ca 从 __ 到自身的函数 \xe2\x80\x9d 既不是输出也不是输入,\xe2\x80\x9d 而 \xe2\x80\x9ca 字符串和正好为零的 __s\xe2\x80\ 列表x9d 可能是 \xe2\x80\x9cphantom\xe2\x80\x9d 情况。

\n

但是,是的,这就是编程中函子的全部内容,它们只是可映射的东西。如果某个东西是一个函子,那么它就是一个独特的函子,那么最多只有一种方法可以将函数一般地映射到数据结构上。

\n

单子

\n

一个monad是一个函子,此外它还是

\n
    \n
  1. 普遍适用,给定任何X,我可以构造一个蓝色的X
  2. \n
  3. 可以重复而不改变太多意义。因此,-蓝X在某种意义上与蓝色X相同。
  4. \n
\n

所以这意味着有一个规范函数将任何 blue -blue __ 折叠为 blue __。(当然,我们添加法则来使一切变得理智:如果其中一层蓝色来自通用应用程序,那么我们只想删除该通用应用程序;此外,如果我们将蓝-蓝-蓝 X 扁平化为蓝色 X,无论我们先折叠前两个蓝色还是先折叠后两个,应该没有什么区别。)

\n

第一个示例是 \xe2\x80\x9ca 可空 __\xe2\x80\x9d。因此,如果我给你一个可空的整数,从某种意义上说,我给你的只是一个可空的整数。或者 \xe2\x80\x9ca 整数列表列表,\xe2\x80\x9d 如果点只是有 0 个或多个整数,那么 \xe2\x80\x9ca 整数列表 \xe2\x80\x9d 就可以工作很好,正确的折叠是将所有列表连接在一起形成一个超级列表。

\n

Monads 在 Haskell 中变得很重要,因为 Haskell 需要一种方法来在现实世界中做事,而不违反其数学上纯粹的世界,在这个世界上什么也没有发生。解决方案是添加一种淡化形式的元编程,其中我们引入 \xe2\x80\x9ca 程序的形容词,该程序生成 __.\xe2\x80\x9d 那么我如何获取当前日期?好吧,Haskell 不能直接做到这一点unsafePerformIO,但它可以让你描述和编写生成当前日期的程序。在这个愿景中,您应该描述一个不生成任何名为 \xe2\x80\x9cMain.main,\xe2\x80\x9d 的程序,并且编译器应该接受您的描述并将该程序作为二进制可执行文件交给您随时随地跑步。

\n

无论如何,生成一个 int\xe2\x80\x9d 的程序的 \xe2\x80\x9ca 程序并不会比生成一个 int\xe2\x80\x9d 的 \xe2\x80\x9ca 程序买得更多,所以事实证明成为一个单子。

\n

自由单子

\n

与函子不同,单子并不是唯一的单子。对于给定的函子来说,不只有一个 monad 实例。例如 \xe2\x80\x9ca 一对 int 和 __\xe2\x80\x9d,你用这个 int 做什么?你可以加它,你可以乘它。如果将其设为可空 int,则可以保留最小非空值或最大非空值,或者最左边的非空值或最右边的非空值。

\n

给定函子的自由单子是“最无聊的 \xe2\x80\x9d 构造,它只是 \xe2\x80\x9cA 自由蓝色X任何n = 0, 1, 2, ...\ xe2\x80\x9d。

\n

它是通用的,因为 blue\xe2\x81\xb0 X只是一个X。自由蓝自由蓝X是蓝色mn X,它只是蓝色m + n X。它实现了 \xe2\x80\x9ccollapse\xe2\x80\x9d 因此根本不实现折叠,内部蓝色是任意嵌套的。

\n

这也意味着您可以准确地将您选择的 monad 推迟到稍后的日期,稍后您可以定义一个函数,将blue -blue X减少为 blue X并将所有这些折叠为 blue 0,1 X,然后从另一个函数X到蓝色X给你蓝色1 X。

\n

  • 非常感谢您的这个接地气的解释!我不需要了解 Haskell 就能得到这个非常简单的想法(我是一个 scala FP 爱好者)。所以现在我更清楚了,这听起来像是 Coyoneda 技巧(惰性函子)的延续,但我们也推迟了 flatMap 部分。使用 Coyoneda,我们只需封装所有函数序列,直到我们知道如何应用它们,在 Free Monads 中,我们通过封装纯值和数据结构中的挂起来创建一种“惰性 monad”,因此我们可以稍后提供实际的 monad 。 (3认同)
  • 这是一个很棒的答案! (2认同)