谁首先说了如下几点? html
monad只是endofunctors类别中的幺半群,问题是什么? 程序员
在一个不过重要的注意事项上,这是真的,若是是这样,你能给出一个解释(但愿有一个能够被没有Haskell经验的人理解的解释)吗? 编程
首先,咱们将使用的扩展和库: 数组
{-# LANGUAGE RankNTypes, TypeOperators #-} import Control.Monad (join)
其中, RankNTypes
是惟一对下面绝对必要的。 我曾经写过一些有些人彷佛认为有用的RankNTypes
的解释 ,因此我会参考。 编程语言
引用Tom Crockett的优秀答案 ,咱们有: ide
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 }
A型的形式的f :-> g
相似于一个功能类型,但代替它思想为两种类型 (的种类之间的函数 *
),把它做为两个函子 (各类之间的态射 * -> *
)。 例子: this
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
基本上,在Haskell中,天然变换是从某种类型fx
到另外一种类型gx
的x
,使得x
类型变量对于调用者是“不可访问的”。 所以,例如, sort :: Ord a => [a] -> [a]
不能成为天然变换,由于它“挑剔”咱们可能为a
实例化哪些类型。 我常用的一种直观方式是: google
如今,让咱们解决这个定义的条款。 spa
第一个子句是“endofunctor, T:X - > X” 。 好吧,Haskell中的每一个Functor
都是人们称之为“Hask类别”的endofunctor,其对象是Haskell类型(类型*
),其状态是Haskell函数。 这听起来像一个复杂的陈述,但它其实是一个很是微不足道的陈述。 它的意思是,一个Functor f :: * -> *
为您提供了构建型的手段fa :: *
对于任何a :: *
和功能fmap f :: fa -> fb
出任何的f :: a -> b
,而且这些遵照了算子法则。
第二个子句:Haskell中的Identity
functor(它随Platform一块儿提供,因此你只须要导入它)就是这样定义的:
newtype Identity a = Identity { runIdentity :: a } instance Functor Identity where fmap f (Identity a) = Identity (f a)
因此Tom Crockett定义的天然变换η:I - > T能够用这种方式写成任何Monad
实例t
:
return' :: Monad t => Identity :-> t return' = Natural (return . runIdentity)
第三个条款: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)
所以Tom Crockett定义的天然变换μ:T×T - > T能够这样写:
join' :: Monad t => Compose t t :-> t join' = Natural (join . getCompose)
这是endofunctors类别中的monoid的声明而后意味着Compose
(仅部分应用于其前两个参数)是关联的,而且Identity
是其标识元素。 即,如下同构持有:
Compose f (Compose gh) ~= Compose (Compose fg) h
Compose f Identity ~= f
Compose Identity g ~= g
这些很容易证实,由于Compose
和Identity
都被定义为newtype
,而Haskell Reports将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.
注意:不,这不是真的。 在某些时候,Dan Piponi本身对这个答案发表了评论说,这里的因果偏偏相反,他在回应James Iry的讽刺时写了他的文章。 但它彷佛已被删除,多是经过一些强迫性的整洁。
如下是我原来的答案。
Iry极可能已经读过Monoids到Monads ,其中Dan Piponi(sigfpe)从Haskell的monoids中衍生monad,并对类别理论进行了大量讨论并明确提到了“Hask上的endofunctors类别”。 不管如何,任何想知道monad在endofunctor类别中是monoid意味着什么的人均可能从阅读这个推导中受益。
詹姆斯·伊里(James Iry)从他极具娱乐性的简短,不完整和错误的编程语言历史中获得了这一特殊的措辞,其中他将其虚构地归功于菲利普·瓦德勒(Philip Wadler)。
最初的引用来自Saunders Mac Lane 的工做数学家类别,这是类别理论的基础文本之一。 在这里 ,它多是了解其含义的最佳位置。
可是,我会采起刺。 原句是这样的:
总而言之,X中的monad只是X的endofunctor类别中的monoid,产品×由endofunctors的组合和身份endofunctor设置的单位替换。
这里的X是一个类别。 Endofunctors是从类别到自身的仿函数(就函数式程序员而言,它一般都是 Functor
,由于它们主要只处理一个类别;类型类别 - 但我离题了)。 但你能够想象另外一个类别是“ X上的 endofunctors”类别。 这是一个类别,其中对象是endofunctors,而态射是天然变换。
在那些终结者中,其中一些多是单子。 哪些是monad? 正是那些在特定意义上是幺半群的。 而不是拼写出从monad到monoids的确切映射(由于Mac Lane确实比我但愿的要好得多),我只是将它们各自的定义并排放在一块儿让你比较:
* -> *
带有Functor
实例) join
在Haskell) return
) 稍微眯着眼睛,你可能会发现这两个定义都是同一个抽象概念的实例。
我经过更好地理解Mac Lane的工做数学家类别理论中臭名昭着的引用来推断这篇文章。
在描述某些东西时,描述它不是什么一般一样有用。
Mac Lane使用描述来描述Monad这一事实可能意味着它描述了monad特有的东西。 忍受我。 为了更普遍地理解这个陈述,我认为须要明确的是他并没有描述monad独有的东西; 该声明一样描述了Applicative和Arrows等。 出于一样的缘由,咱们能够在Int(Sum和Product)上有两个monoid,咱们能够在endofunctors类别中的X上有几个monoid。 可是类似之处还有更多。
Monad和Applicative均符合标准:
(例如,在平常Tree a -> List b
,但在类别Tree -> List
)
Tree -> List
,只有List -> List
。 该语句使用“Category of ...”这定义了语句的范围。 做为示例,Functor类别描述了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 Int
,其中a :: Maybe Double; b :: Either String Int
a :: Maybe Double; b :: Either String Int
。
因此声明以下:
:: fa -> gb
(即任何参数化类型的参数化类型) :: fa -> fb
(即任何一个参数化类型为相同的参数化类型)...换句话说, 那么,这个结构的力量在哪里? 为了欣赏完整的动态,我须要看到一个monoid的典型图形(单个对象看起来像一个身份箭头, :: single object -> single object
),没法说明我被容许使用箭头从Monoid中容许的一个类型对象参数化任意数量的monoid值。 endo,〜identity等价定义忽略了functor的类型值以及最内层的“payload”层的类型和值。 所以,在函数类型匹配的任何状况下,等价返回true
(例如, Nothing -> Just * -> Nothing
等同于Just * -> Just * -> Just *
由于它们都是Maybe -> Maybe -> Maybe
)。
边栏:〜外面是概念性的,可是fa
最左边的符号。 它还描述了“Haskell”首先读入的内容(大图); 所以Type类型与“类型值”相关。 编程中的层(参考链)之间的关系在类别中不易相关。 Set的类别用于描述类型(Int,Strings,Maybe Int等),其中包括Functor类别(参数化类型)。 参考链:Functor Type,Functor值(Functor集合的元素,例如,Nothing,Just),以及每一个functor值指向的其余全部内容。 在类别中,关系的描述不一样,例如, return :: a -> ma
被认为是从一个Functor到另外一个Functor的天然转换,与目前为止提到的任何内容都不一样。
回到主线程,总而言之,对于任何已定义的张量积和中性值,该语句最终描述了一个由其矛盾结构产生的惊人强大的计算结构:
:: List
); 静态的 fold
,没有说明有效载荷) 在Haskell中,澄清声明的适用性很重要。 这种结构的强大功能和多功能性与monad 自己彻底无关。 换句话说,构造不依赖于monad的独特之处。
当试图弄清楚是否构建具备共享上下文的代码以支持相互依赖的计算时,与能够并行运行的计算相比,这个臭名昭着的语句,与其描述的同样,并非选择应用,箭头和Monads,而是描述它们是多少相同。 对于手头的决定,声明没有实际意义。
这常常被误解。 该陈述继续描述join :: m (ma) -> ma
做为monoidal endofunctor的张量积。 可是,它没有明确说明在本声明的背景下, (<*>)
也能够如何选择。 它确实是一个六/半打的例子。 组合价值的逻辑彻底相同; 相同的输入从每一个输出生成相同的输出(与Int的Sum和Product monoids不一样,由于它们在组合Ints时会产生不一样的结果)。
那么,回顾一下:endofunctors类别中的monoid描述:
~t :: m * -> m * -> m * and a neutral value for m *
(<*>)
和(>>=)
都提供对两个m
值的同时访问,以便计算单个返回值。 用于计算返回值的逻辑彻底相同。 若是不是函数的不一样形状它们参数化( f :: a -> b
与k :: a -> mb
)和参数的位置具备相同的计算返回类型(即a -> b -> b
与b -> a -> b
分别为每一个),我怀疑咱们能够对幺半群逻辑(张量积)进行参数化,以便在两种定义中重复使用。 做为一个重点的练习,尝试并实现~t
,最终获得(<*>)
和(>>=)
取决于你决定如何定义它forall ab
。
若是个人最后一点至少在概念上是真的,那么它解释了Applicative和Monad之间的精确且惟一的计算差别:它们参数化的函数。 换句话说,不一样的是外部对这些类型的类的实现。
总而言之,根据我本身的经验,Mac Lane的臭名昭着的引用提供了一个伟大的“goto”模因,这是我在引导个人方式经过类别时引用的指南,以更好地理解Haskell中使用的习语。 它成功地捕获了在Haskell中很是容易访问的强大计算能力的范围。
然而,有点讽刺的是我第一次误解了陈述在monad以外的适用性,以及我但愿在这里传达的内容。 它所描述的一切都证实了Applicative和Monads(以及其余箭头)之间的类似之处。 它没有说的正是它们之间的微小但有用的区别。
- E.
这里的答案在定义monoids和monad方面作得很是出色,可是,他们彷佛仍然没有回答这个问题:
在一个不过重要的注意事项上,这是真的,若是是这样,你能给出一个解释(但愿有一个能够被没有Haskell经验的人理解的解释)吗?
这里缺乏的问题的关键是“幺半群”的不一样概念,更确切地说是所谓的分类 - 幺半群中的幺半群。 可悲的是,麦克莱恩的书自己使人困惑 :
总而言之,在一个单子
X
是正好在endofunctors的类别幺半群X
,具备产品×
由endofunctors和单元由身份endofunctor设定的组合物替代。
为何这使人困惑? 由于它没有定义什么是X
“endofunctors类别中的monoid”。 相反,这句话建议在全部endofunctor集合中使用monoid ,将functor组合做为二元运算,将标识函子做为幺半单元。 哪一个工做彻底正常,并变成一个monoid任何包含身份仿函数的endofunctor的子集,并在仿函数组合下关闭。
然而,这不是正确的解释,这本书在那个阶段没有说清楚。 Monad f
是固定的 endofunctor,而不是在组合下关闭的endofunctors的子集。 一种常见的结构是使用f
经过取该组全部的以产生独异k
倍组合物f^k = f(f(...))
的f
与自己,包括k=0
对应于身份f^0 = id
。 如今,对于全部k>=0
,全部这些幂的集合S
确实是一个“monoid”,其中产品×由endofunctors的组成和由身份endofunctor设置的单位替换。
可是:
S
能够为任何仿函数f
定义,甚至能够为X
任何自映射字面定义。 它是由f
生成的幺半群。 S
的幺半群结构与f
或不是monad没有任何关系。 而且为了使事情更加混乱,正如您从目录中能够看到的那样,“monoid in monoidal category”的定义出如今本书的后面。 然而,理解这个概念对理解与monad的联系相当重要。
关于Monoids的第七章(比Monad第六章更晚),咱们发现所谓的严格幺半群类别的定义为三元组(B, *, e)
,其中B
是一个类别, *: B x B-> B
bifunctor (关于每一个组件的functor与其余组件固定)和e
是B
的单元对象,知足关联性和单位定律:
(a * b) * c = a * (b * c) a * e = e * a = a
对于任何对象a,b,c
的B
任何态射,和相同的标识a,b,c
与e
替换id_e
,身份态射e
。 如今是指导观察,在咱们感兴趣的状况下,若是B
是endofunctors类别X
与天然变换为态射, *
函子组成和e
身份仿函数,全部这些法律都满意,由于能够直接验证。
随之而来后的书是“轻松” monoidal范畴 ,在法律只能容纳模知足所谓的连贯关系 ,但它不是咱们的endofunctor类别的状况下,重要的一些固定的天然变换的定义。
最后,在第七章第3节“幺半群”中,给出了实际的定义:
幺半群类别中的幺半群
c
(B, *, e)
是B
的对象,带有两个箭头(态射)
mu: c * c -> c nu: e -> c
制做3个图表可交换。 回想一下,在咱们的例子中,这些是endofunctor类别中的态射,它们是对应于精确join
和return
monad的天然变换。 当咱们使组合*
更明确时,链接变得更清晰,用c^2
替换c * c
,其中c
是咱们的monad。
最后,请注意3个交换图(在幺半群类别中的幺半群的定义中)是针对通常(非严格)幺半群类别编写的,而在咱们的例子中,做为幺半群类别的一部分而出现的全部天然变换实际上都是身份。 这将使图表与monad定义中的图表彻底相同,从而使对应完成。
总之,根据定义,任何monad都是endofunctor,所以是endofunctor类别中的对象,其中monadic join
和return
运算符知足特定(严格)monoidal类别中monoid的定义。 反之亦然,在幺半群类型的endofunctor中的任何幺半群定义为由对象和两个箭头组成的三元组(c, mu, nu)
,例如在咱们的状况下的天然变换,知足与monad相同的定律。
最后,请注意(经典)幺半群与幺半群类别中更通常的幺半群之间的关键区别。 上面的两个箭头mu
和nu
再也不是二元运算和集合中的单位。 相反,你有一个固定的endofunctor c
。 尽管书中有使人困惑的评论,可是单独的仿函数组合*
和身份仿函数并不能提供monad所需的完整结构。
另外一种方法是与集合A
的全部自映射的标准幺半群C
进行比较,其中二元运算是组合,能够看出将标准笛卡尔积C x C
映射到C
。 传递到分类的monoid,咱们用functor组合*
替换笛卡尔积x
,而且二进制操做被替换为从c * c
到c
的天然变换mu
,这是join
运算符的集合
join: c(c(T))->c(T)
对于每一个对象T
(编程中的类型)。 经典幺半群中的身份元素,能够用固定的一点集的地图图像识别,被return
算子的集合所取代
return: T->c(T)
但如今没有更多的笛卡尔积,因此没有元素对,于是没有二元运算。