monad只是endofunctors类别中的幺半群,问题是什么?

Rom*_*her 708 monads haskell category-theory monoids

谁首先说了以下几点?

monad只是endofunctors类别中的幺半群,问题是什么?

在一个不太重要的注意事项上,这是真的,如果是这样,你能给出一个解释(希望有一个可以被没有Haskell经验的人理解的那个)吗?

Tom*_*ett 773

詹姆斯·伊里(James Iry)从他非常有趣的简短,不完整和错误的编程语言历史中得到了这个特殊的措辞,他将其虚构地归功于菲利普·瓦德勒(Philip Wadler).

最初的引用来自Saunders Mac Lane 的工作数学家类别,这是类别理论的基础文本之一.在这里,它可能是了解其含义的最佳位置.

但是,我会采取刺.原句是这样的:

总而言之,X中的monad只是X的endofunctor类别中的monoid,产品×由endofunctors的组合和身份endofunctor设置的单位替换.

这里的X是一个类别.Endofunctors是从类别本身仿函数(通常是所有 Functor S作为远功能的程序员来说,因为他们大多是处理的只是一个类别,类型的类别-但我离题).但你可以想象另一个类别是" X上的 endofunctors " 类别.这是一个类别,其中对象是endofunctors,而态射是自然变换.

在那些终结者中,其中一些可能是单子.哪些是monad?正是那些在特定意义上是半群的.而不是拼写出从monad到monoids的确切映射(因为Mac Lane确实比我希望的要好得多),我只是将它们各自的定义并排放在一起,让你比较:

幺半群是......

  • 一套,S
  • 操作,•:S×S→S
  • S的元素,e:1→S

......满足这些法律:

  • (一•B)•C = A•(B•c)中,对于所有的一个,bÇ小号
  • Ë•A = A•E =一,所有一个小号

monad是......

  • 一个endofunctor,T:X→X(在Haskell中,一个* -> *带有Functor实例的类型构造函数)
  • 自然变换,μ:T×T→T,其中×表示仿函数合成(μjoin在Haskell中已知)
  • 一个自然变换,η:I→T,其中X上的身份endofunctor (ηreturn在Haskell中已知)

......满足这些法律:

  • μ∘Tμ=μ∘μT
  • μ∘Tη=μ∘ηT= 1(身份自然变换)

稍微眯着眼睛,你可能会发现这两个定义都是同一个抽象概念的实例.

  • 感谢解释和感谢编程语言的简短,不完整和错误的历史文章.我以为它可能来自那里.真正的编程幽默之一. (21认同)
  • @TahirHassan - 在范畴理论的一般性中,我们处理不透明的"对象"而不是集合,因此没有先验的"元素"概念.但是如果你考虑类别**Set**,其中对象是集合,箭头是函数,任何集合S的元素与从任何单元素集合到S的函数一一对应.是的,对于**S**的任何元素**e**,只有一个函数**f:1 - > S**,其中**1**是任何单元素集...(续) "d) (11认同)
  • @TahirHassan 1元素集本身就是"终端对象"更一般的类别理论概念的特化:终端对象是一个类别的任何对象,其中只有一个箭头从任何其他对象到它(你可以检查对于**Set**)中的1元素集合也是如此.在类别理论中,终端对象简称为**1**; 它们在同构性方面是独一无二的,所以没有必要区分它们.所以现在我们对任何**S**都有一个纯粹的类别理论描述"**S**的元素":它们只是从**1**到**S**的箭头! (11认同)
  • @TahirHassan - 用Haskell的术语来说,考虑一下如果`S`是一个类型的事实,你在写一个函数`f ::() - > S`时所能做的就是选择一个特定的类型的术语` S`(它的"元素",如果你愿意的话)并返回它......你没有得到关于参数的真实信息,所以没有办法改变函数的行为.所以```必须是一个常量函数,每次都返回同样的东西.`()`("Unit")是类别**Hask**的终端对象,并且确实有1(非发散)值存在于它中并非巧合. (6认同)
  • @Jonathan:在幺半群的经典公式中,*×*表示集合的笛卡尔积.你可以在这里阅读更多相关内容:http://en.wikipedia.org/wiki/Cartesian_product,但基本思路是*S×T*的元素是一对*(s,t)*,其中*s ∈S*和*t∈T*.因此,在此上下文中幺半体乘积*•:S×S - > S*的签名仅仅意味着将*S*的2个元素作为输入并且产生*S*的另一个元素作为输出的函数. (5认同)
  • @TahirHassan因此,当从终端对象到对象S的箭头与S的元素不是"相同的东西"时,它们与其元素是*同构*,就类别理论而言,它同样好. (4认同)
  • @IvanGozali 集合理论是一种可以定义幺半群的设置;范畴论是另一个更抽象的背景。幺半群的集合论定义只是范畴论定义的一个特例(当所讨论的范畴是 **Set**),而 monads 是*另一种*特例(当范畴是另一个类别的内函子)。有关幺半群的更多实例,请参见 [此处](http://en.wikipedia.org/wiki/Monoid_(category_theory)#Examples) 给出的示例。所以幺半群确实是更抽象的概念。 (3认同)
  • 我对你对monoid的定义感到困惑,特别是:"*S*的元素,**e:1 - > S**".所以**e**是**S**的元素,但是你将其定义为"**e:1 - > S**",这意味着**e**是域**的函数**和codomain**S**.这是什么意思? (2认同)
  • @LifuHuang 您可以在此处阅读 *ηT* 和 *Tη* 符号的含义:https://en.wikipedia.org/wiki/Natural_transformation#Operations_with_natural_transformations ...您的问题让我意识到我对单子定律的描述是语无伦次,所以重新写了一遍。不幸的是,与幺半群定律的句法相似性不再那么明显;我得想办法把它说清楚。 (2认同)
  • @LifuHuang 是的,你已经指出了确切的难度!我怀疑没有捷径,一个令人满意的解释需要引入幺半群的分类概念并展示它如何分别专门用于 **Set** 和 **End**。 (2认同)
  • @LifuHuang 是的,没错。符号`F(f)` 背后的直觉是函子*F:* **C → D** 是*类别* 之间的映射,因此映射**C** (*F( A)*),以及 **C** (*F(f)*) 中的态射。 (2认同)
  • @LifuHuang 在 Haskell 中,`Functor` 的意思是“**Hask** 上的 endofunctor”,`Functor` 将 **Hask** 的对象映射到 **Hask** 中的其他对象。由于 **Hask** 的对象是类型,这意味着`Functor` 实例`t` 可以应用于类型`a` 以产生另一个类型`ta`... 即,它是一个类型构造函数!而`Functor` 实例`t` 将函数`f: a -> b` 映射到函数`ta -> tb` 的方式当然是通过`fmap`。 (2认同)
  • 我知道了。谢谢!我真的欠你一百个赞:) (2认同)
  • 令人惊奇的解释。我只是在最后一部分感到困惑。不应该是:“μ ∘ Tη = μ ∘ ηT = μ(恒等自然变换)”为什么说是 1? (2认同)

mis*_*bee 522

直觉上,我认为花哨的数学词汇所说的是:

半群是一组对象,以及组合它们的方法.众所周知的幺半群是:

  • 您可以添加的数字
  • 您可以连接的列表
  • 你可以结合

还有更复杂的例子.

此外,每个 monoid都有一个标识,这是"no-op"元素,当你将它与其他东西结合起来时没有效果:

  • 0 + 7 == 7 + 0 == 7
  • [] ++ [1,2,3] == [1,2,3] ++ [] == [1,2,3]
  • {} union {apple} == {apple} union {} == {apple}

最后,一个幺半群必须是联想的.(你可以随意减少一长串组合,只要你不改变对象从左到右的顺序)加法就可以了((5 + 3)+1 == 5+(3+) 1)),但减法不是((5-3)-1!= 5-(3-1)).

单子

现在,让我们考虑一种特殊的集合和一种组合对象的特殊方法.

对象

假设您的集合包含特殊类型的对象:函数.这些函数有一个有趣的签名:它们不会将数字带到数字或字符串中.相反,每个函数在两个步骤中将数字带到一个数字列表中.

  1. 计算0或更多结果
  2. 不知怎的,将这些结果合并到一个答案中.

例子:

  • 1 - > [1](只包装输入)
  • 1 - > [](丢弃输入,将虚无包装在列表中)
  • 1 - > [2](在输入中加1,并包装结果)
  • 3 - > [4,6](在输入中加1,并将输入乘以2,并包装多个结果)

组合对象

另外,我们的功能组合方式很特别.组合函数的一种简单方法是组合:让我们看一下上面的例子,并用自己编写每个函数:

  • 1 - > [1] - > [[1]](包装输入,两次)
  • 1 - > [] - > [](丢弃输入,将列表中的虚无包裹两次)
  • 1 - > [2] - > [UH-OH!](我们不能在列表中"添加1"!")
  • 3 - > [4,6] - > [UH-OH!](我们不能添加1个列表!)

如果没有太多的类型理论,关键是你可以组合两个整数来得到一个整数,但你不能总是组成两个函数并获得相同类型的函数.(类型为a - > a的函数 将组成,但a-> [a]不会.)

所以,让我们定义一种组合函数的不同方式.当我们结合其中两个函数时,我们不希望"双重包装"结果.

这就是我们的工作.当我们想要组合两个函数F和G时,我们遵循这个过程(称为绑定):

  1. 计算F中的"结果",但不要将它们组合起来.
  2. 计算将G分别应用于F的每个结果的结果,得到一组结果集合.
  3. 展平2级集合并结合所有结果.

回到我们的例子,让我们使用这种"绑定"函数的新方法将一个函数与自身结合(绑定):

  • 1 - > [1] - > [1](包装输入,两次)
  • 1 - > [] - > [](丢弃输入,将列表中的虚无包裹两次)
  • 1 - > [2] - > [3](加1,然后再加1,并包装结果.)
  • 3 - > [4,6] - > [5,8,7,12](在输入中加1,并将输入乘以2,同时保留两个结果,然后对两个结果再次执行,然后包装最终结果列表.)

这种更复杂的组合函数的方法关联的(当你没有做出花哨的包装东西时,跟随函数组合是如何关联的).

捆绑在一起,

  • monad是一种定义组合(结果)函数的方法的结构,
  • 类似于monoid是一种定义组合对象的方式的结构,
  • 组合方法是关联的,
  • 并且有一个特殊的"无操作",可以与任何东西结合,导致一些不变的东西.

笔记

有很多方法可以"包装"结果.你可以制作一个列表,一个集合,或丢弃除第一个结果之外的所有结果,同时注意是否没有结果,附加状态的边车,打印日志消息等等.

我对这些定义有点松散,希望能够直观地了解基本概念.

我通过坚持我们的monad操作类型为a - > [a]的函数来简化了一些事情.事实上,monad在a - > mb类型函数上工作,但泛化是一种技术细节,而不是主要的洞察力.

  • 这是一个很好的解释,每个monad如何构成_category_([Kleisli类别](http://en.wikipedia.org/wiki/Kleisli_category)是你所展示的 - 还有Eilenberg-Moore类别) .但由于你无法组成_any_两个Kleisli箭头`a - > [b]`和`c - > [d]`(你只能这样做,如果`b` =`c`),这不会不太可能描述一个幺半群.它实际上是你所描述的扁平化操作,而不是功能组合,即"幺半群运算符". (22认同)
  • 这是对monad的最好和最可理解的解释,以及他们在几周内遇到的幺半群的数学背景.这是每个Haskell书中应该印刷的内容,当涉及monad,放下手.给予好评!也许进一步得到这条信息,monad被实现为参数化的类型类实例,将包含在haskell中的内容包装到帖子中.(至少这就是我现在理解他们的方法.如果我错了,请纠正我.请参阅http://www.haskell.org/haskellwiki/What_a_Monad_is_not) (8认同)
  • 当然,如果你将monad限制为只有一种类型,即如果你只允许使用形式为'a - > [a]`的Kleisli箭头,那么这将是一个monoid(因为你将Kleisli类别简化为单个对象) ,根据定义,只有一个对象的任何类别都是幺半群!),但它不能捕捉到monad的完整通用性. (6认同)
  • 在最后一点,有助于记住,a - > [a]只是 - > [] a.([]也只是类型构造函数.)因此它不仅可以被视为 - > mb,而且[]确实是Monad类的一个实例. (5认同)

Lui*_*las 82

首先,我们将使用的扩展和库:

{-# LANGUAGE RankNTypes, TypeOperators #-}

import Control.Monad (join)
Run Code Online (Sandbox Code Playgroud)

其中,RankNTypes唯一一个绝对必要的是下面的.我曾经写过RankNTypes一些解释,有些人似乎觉得有用,所以我会参考.

引用Tom Crockett的优秀答案,我们有:

monad是......

  • 一个endofunctor,T:X - > X.
  • 自然变换,μ:T×T - > T,其中×表示仿函数组成
  • 一个自然变换,η:I - > T,其中X上的标识endofunctor

......满足这些法律:

  • μ(μ(T×T)×T))=μ(T×μ(T×T))
  • μ(η(T))= T =μ(T(η))

我们如何将其转换为Haskell代码?那么,让我们从自然转型的概念开始:

-- | A natural transformations between two 'Functor' instances.  Law:
--
-- > fmap f . eta g == eta g . fmap f
--
-- Neat fact: the type system actually guarantees this law.
--
newtype f :-> g =
    Natural { eta :: forall x. f x -> g x }
Run Code Online (Sandbox Code Playgroud)

A型的形式的f :-> g类似于一个功能类型,但它思想为代替功能两者类型(种*),认为它作为一个态射两者之间函子(各种* -> *).例子:

listToMaybe :: [] :-> Maybe
listToMaybe = Natural go
    where go [] = Nothing
          go (x:_) = Just x

maybeToList :: Maybe :-> []
maybeToList = Natural go
    where go Nothing = []
          go (Just x) = [x]

reverse' :: [] :-> []
reverse' = Natural reverse
Run Code Online (Sandbox Code Playgroud)

基本上,在Haskell中,自然转换是从某种类型f x到另一种类型的函数g x,使得x类型变量对于调用者是"不可访问的".因此,例如,sort :: Ord a => [a] -> [a]不能进入自然变换,因为它"挑剔"我们可能实例化的类型a.我经常使用的一种直观方式是:

  • 仿函数是一种在不触及结构的情况下对某些内容进行操作的方法.
  • 自然变换是一种操作某事物结构的方式,而不会触及或查看内容.

现在,让我们解决这个定义的条款.

第一个子句是"endofunctor,T:X - > X".好吧,FunctorHaskell中的每一个都是人们称之为"Hask类别"的endofunctor,其对象是Haskell类型(类型*),其状态是Haskell函数.这听起来像一个复杂的陈述,但它实际上是一个非常微不足道的陈述.它的意思是说,一个Functor f :: * -> *给你构造类型的手段f a :: *对任何a :: *和功能fmap f :: f a -> f b进行的任何f :: a -> b,而这些服从法律函子.

第二个子句:IdentityHaskell中的仿函数(随平台一起提供,所以你只需要导入它)就是这样定义的:

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    fmap f (Identity a) = Identity (f a)
Run Code Online (Sandbox Code Playgroud)

所以Tom Crockett定义的自然变换η:I - > T可以用这种方式写成任何Monad实例t:

return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)
Run Code Online (Sandbox Code Playgroud)

第三个条款:Haskell中两个仿函数的组合可以通过这种方式定义(它也随平台一起提供):

newtype Compose f g a = Compose { getCompose :: f (g a) }

-- | The composition of two 'Functor's is also a 'Functor'.
instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose fga) = Compose (fmap (fmap f) fga)
Run Code Online (Sandbox Code Playgroud)

因此Tom Crockett定义的自然变换μ:T×T - > T可以这样写:

join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)
Run Code Online (Sandbox Code Playgroud)

这是endofunctors类别中的monoid的说法意味着Compose(部分应用于其前两个参数)是关联的,这Identity是它的标识元素.即,以下同构持有:

  • Compose f (Compose g h) ~= Compose (Compose f g) h
  • Compose f Identity ~= f
  • Compose Identity g ~= g

这些都是很容易证明,因为ComposeIdentity都是定义为newtype,和Haskell的报告定义的语义newtype为被定义的类型之间的同构和参数的所涉及的类型newtype的数据构造.例如,让我们证明Compose f Identity ~= f:

Compose f Identity a
    ~= f (Identity a)                 -- newtype Compose f g a = Compose (f (g a))
    ~= f a                            -- newtype Identity a = Identity a
Q.E.D.
Run Code Online (Sandbox Code Playgroud)


Dmi*_*sev 12

这里的答案在定义幺半群和单子方面做得很好,但是,它们似乎仍然没有回答这个问题:

在不太重要的一点上,这是真的吗?如果是,您能否给出解释(希望没有太多 Haskell 经验的人可以理解)?

这里缺少的问题的关键是“幺半群”的不同概念,更准确地说是所谓的分类——幺半群中的幺半群。可悲的是,麦克莱恩的书本身就让人非常困惑

总而言之,单子 inX只是 的内函子范畴中的幺半群X,乘积×被内函子的组合和由身份内函子设置的单元所取代。

主要困惑

为什么这令人困惑?因为它没有定义什么是 的“内函子范畴中的幺半群” X。相反,这句话建议将所有内函子集合中的一个幺半群连同函子组合一起作为二元运算,将恒等函子作为一个幺半群。它工作得非常好,并变成一个幺半群,包含恒等函子并在函子组合下封闭的任何内函子子集。

然而,这不是正确的解释,这本书在那个阶段没有明确说明。Monadf是一个固定的内函子,而不是在组合下封闭的内函子的子集。一种常见的结构是使用f产生通过取该组所有的幺半k倍的组合物f^k = f(f(...))f与本身,包括k=0对应于身份f^0 = id。现在S,所有这些幂的集合k>=0确实是一个幺半群,“乘积 × 被内函子的组合和由恒等函子设置的单元替换”。

但是:

  • 这个幺半群S可以为任何函子定义,f甚至可以为任何自映射定义X。它是由 生成的幺半群f
  • S由函子组合和恒等函子给出的幺半群结构与f是否为 monad无关。

更令人困惑的是,“幺半群中的幺半群”的定义出现在本书的后面,您可以从目录中看到。然而,理解这个概念对于理解与 monad 的联系绝对至关重要。

(严格)幺半群

转到关于 Monoids 的第 VII 章(在关于 Monads 的第 VI 章之后),我们发现所谓的严格幺半群范畴的定义为三元组(B, *, e),其中B是一个范畴,*: B x B-> B一个函子(函子相对于每个分量,其他分量固定) 并且e是 中的单位对象B,满足结合律和单位定律:

(a * b) * c = a * (b * c)
a * e = e * a = a
Run Code Online (Sandbox Code Playgroud)

对于任何物体a,b,cB,而对于任何态射相同身份a,b,ce替换id_e的身份,同态e。现在观察到,在我们感兴趣的情况下,具有自然变换为态射、函子组合和恒等函子的自函子B的范畴在哪里是有益的,所有这些定律都得到满足,因为可以直接验证。X*e

本书后面是“松弛”半群范畴的定义,其中的定律仅对满足所谓相干关系的某些固定自然变换取模,但这对于我们的内函子范畴来说并不重要。

幺半群中的幺半群

最后,在第七章第三节“幺半群”中,给出了实际的定义:

幺半群c中的幺半群(B, *, e)是一个B有两个箭头的对象(态射)

mu: c * c -> c
nu: e -> c
Run Code Online (Sandbox Code Playgroud)

使 3 个图可交换。回想一下,在我们这里,这些都是态射在endofunctors的范畴,它是对应于精确自然变换joinreturn一个单子。当我们使组合*更加明确时,连接变得更加清晰,替换c * cc^2c我们的 monad在哪里。

最后,请注意这 3 个交换图(在幺半群中的幺半群的定义中)是为一般(非严格)幺半群写的,而在我们的例子中,作为幺半群范畴的一部分出现的所有自然变换实际上都是恒等式。这将使图表与 monad 定义中的图表完全相同,从而使对应关系完整。

结论

总之,根据定义,任何 monad 都是一个内函子,因此是内函子范畴中的一个对象,其中 monadicjoinreturn运算符满足该特定(严格)幺半群中的幺半群的定义。反之亦然,根据定义,内函子的幺半群中的任何幺半群都是由(c, mu, nu)一个对象和两个箭头组成的三元组,例如在我们的例子中的自然变换,满足与单子相同的定律。

最后,请注意(经典)幺半群与幺半群范畴中更一般的幺半群之间的主要区别。的两个箭头munu以上是不再在一组二进制运算和一单元。相反,你有一个固定的内函子c*尽管书中有令人困惑的评论,但函子组合和恒等函子本身并不能提供 monad 所需的完整结构。

另一种方法是与C集合 的所有自映射的标准幺半群进行比较A,其中二元运算是组合,可以看出将标准笛卡尔积映射C x CC。传递到分类幺半群,我们用x函子组合替换笛卡尔积,*二元运算被替换为muc * cto的自然变换c,即join运算符的集合

join: c(c(T))->c(T)
Run Code Online (Sandbox Code Playgroud)

对于每个对象T(编程类型)。经典幺半群中的恒等元素,可以用固定的单点集的地图图像来识别,被return操作符的集合替换

return: T->c(T) 
Run Code Online (Sandbox Code Playgroud)

但是现在没有更多的笛卡尔积,所以没有元素对,因此没有二元运算。

  • @AlexanderBelopolsky,从技术上讲,单子是单群中的幺半群,配备了函子组合作为其产品。相反,经典的“代数幺半群”是配备有笛卡尔积作为其乘积的幺半群集合类别中的幺半群。因此,两者都是幺半群的相同一般分类定义的特定情况。 (2认同)
  • 这应该是公认的答案,因为它准确地描述了桑德拉斯和麦克莱恩的意思。 (2认同)

hob*_*bbs 6

注意:不,这不是真的.在某些时候,Dan Piponi本人对这个答案发表了评论,他说这里的因果恰恰相反,他在回应James Iry的讽刺时写了他的文章.但它似乎已被删除,可能是通过一些强迫性的整洁.

以下是我原来的答案.


Iry很可能已经读过Monoids到Monads,其中Dan Piponi(sigfpe)从Haskell的monoids中衍生monad,并对类别理论进行了大量讨论并明确提到了"Hask上的endofunctors类别".无论如何,任何想知道monad在endofunctor类别中是monoid意味着什么的人都可能从阅读这个推导中受益.


Edm*_*cho 6

我是通过更好地理解Mac Lane的《为数学家分类理论》中臭名昭著的引言而来这篇帖子的。

在描述什么是事物时,描述它不是事物通常同样有用。

Mac Lane使用描述来描述Monad的事实,可能暗示它描述了Monad独有的东西。忍受我。为了使人们对这一说法有更广泛的了解,我认为需要明确指出的是,他并不是在描述单子论所独有的东西。该声明除其他外同样描述了Applicative和Arrows。出于同样的原因,我们可以在Int上拥有两个单半体(Sum和Product),在endofunctors类别中,我们可以在X上具有多个单半体。但是更多的相似之处。

Monad和Applicative均符合以下条件:

  • endo =>在同一位置开始和结束的任何箭头或形态
  • functor =>两个类别之间的任何箭头或态射

    (例如,每天Tree a -> List b,但在类别中Tree -> List

  • monoid =>单个对象;即单一类型,但在这种情况下,仅关于外部层;所以,我们不能Tree -> List只有List -> List

该语句使用“ ...的类别”。它定义了语句的范围。作为一个例子,该函子类别描述的范围f * -> g *,即Any functor -> Any functor,例如,Tree * -> List *Tree * -> Tree *

分类语句未指定的内容描述了在哪里允许一切

在这种情况下,在函子内部未指定* -> *aka a -> b,即Anything -> Anything including Anything else。当我的想象力跳到Int-> String时,它也包含Integer -> Maybe Int,甚至是Maybe Double -> Either String Intwhere a :: Maybe Double; b :: Either String Int

因此,该语句如下:

  • 函子作用域 :: f a -> g b(即,任何参数化类型到任何参数化类型)
  • 内在+函子:: f a -> f b(即,任何一种参数化类型都属于相同的参数化类型)...的说法不同,
  • endofunctor类别中的一个monoid

那么,这种构造的力量在哪里?为了欣赏完整的动态效果,我需要看到一个monoid的典型图形(带有类似标识箭头的单个对象:: single object -> single object)无法说明我被允许使用参数化为任意数量的monoid 的箭头,从Monoid中允许的一种类型对象。等价的内向〜身份箭头定义忽略了函子的类型值以及最内部的“有效负载”层的类型和值。因此,等价true在函数类型匹配的任何情况下返回(例如,Nothing -> Just * -> Nothing等同于Just * -> Just * -> Just *因为它们都是Maybe -> Maybe -> Maybe)。

补充工具栏:〜外面是概念上的,但是中最左边的符号f a。它还描述了“ Haskell”首先读入的内容(大图);所以相对于类型值,类型是“外部”。编程中各层之间的关系(参考链)在类别中不容易关联。集的类别用于描述类型(整数,字符串,也许是整数等),其中包括函子的类别(参数化类型)。参考链:函子类型,函子值(该函子集合的元素,例如Nothing,Just),以及每个函子值所指向的所有其他内容。在类别中,对关系的描述不同,例如,return :: a -> m a被认为是从一个函子到另一个函子的自然变换,与迄今为止提到的任何事物都不同。

回到主线程,总而言之,对于任何定义的张量积和中性值,该语句最终描述了一个由其自相矛盾的结构产生的惊人强大的计算结构:

  • 在外部,它表现为单个对象(例如:: List);静态的
  • 但在内部,允许很多动态
    • 与任何Arity函数的饲料相同类型的任意数量的值(例如,Empty |〜NonEmpty)。张量积将把任何数量的输入减少为一个单一值...对于外部层(〜fold关于有效负载什么也没说)
    • 的无限范围为最内层的类型和值

在Haskell中,阐明声明的适用性很重要。这种结构的强大功能和多功能性与monad 本身完全无关。换句话说,该构造不依赖于使monad唯一的原因。

当试图确定是否要使用共享上下文构建代码以支持相互依赖的计算以及可以并行运行的计算时,此臭名昭著的陈述(尽管描述得如此之多)与选择以下内容没有区别。适用性,“箭头”和“单声道”,而是描述它们多少相同。对于即将做出的决定,该声明尚无定论。

这经常被误解。该语句继续被描述join :: m (m a) -> m a为单项内泛函的张量积。但是,它没有阐明在本声明的背景下如何(<*>)也可以选择。这确实是六分之二的例子。组合值的逻辑是完全相同的。相同的输入会从每个输入产生相同的输出(与Int的Sum和Product单面体不同,因为在组合Ints时它们会产生不同的结果)。

因此,回顾一下:endofunctors类别中的一个monoid描述:

   ~t :: m * -> m * -> m *
   and a neutral value for m *
Run Code Online (Sandbox Code Playgroud)

(<*>)并且(>>=)既提供对两个同时访问m,以便计算所述单个返回值的值。用于计算返回值的逻辑是完全相同的。如果不是因为他们参数(该功能的不同形状f :: a -> bk :: a -> m b)和参数与计算相同的返回类型的位置(即a -> b -> bb -> a -> b每一个分别的),我想我们可以在参数化monoidal的逻辑,张量积,可在两个定义中重用。作为一个练习,使点,尝试和实施~t,以及你结束了(<*>)(>>=)取决于你决定如何定义它forall a b

如果我的最后一点在概念上至少是对的,那么它将解释Applicative和Monad之间的精确且仅有计算上的差异:它们对函数进行参数化。换句话说,不同的是外部对这些类型的类的实现。

总而言之,以我自己的经验,Mac Lane的臭名昭著的报价提供了一个很棒的“ goto”模因,这是我在浏览类别以更好地理解Haskell中使用的成语时可以参考的指南。它成功地捕获了可在Haskell中完美访问的强大计算能力的范围。

但是,具有讽刺意味的是,我是如何首先误解了该声明在monad之外的适用性,以及我希望在此传达的内容。事实证明,它描述的所有内容在Applicative和Monad(以及Arrows等)之间是相似的。它没有说的只是它们之间很小但有用的区别。

-E