谁首先说了以下内容?
monad只是一个幺半群 endofunctors的类别,是什么 问题
并且在一个不那么重要的注意事项上,这是真的,如果是这样的话,你能给出一个解释(希望有一个可以被没有Haskell经验的人理解的那个)吗?
答案 0 :(得分:735)
詹姆斯·伊里(James Iry)从他极具娱乐性的 Brief, Incomplete and Mostly Wrong History of Programming Languages 中得到了特别的措辞,他将其虚构地归功于菲利普·瓦德勒(Philip Wadler)。
原始引用来自Saunders Mac Lane的工作数学家类别,这是类别理论的基础文本之一。 Here it is in context,这可能是了解其含义的最佳地点。
但是,我会捅一下。原句是这样的:
总而言之,X中的monad只是X的endofunctor类别中的monoid,产品×由endofunctors的组合和身份endofunctor设置的单位替换。
X 这是一个类别。 Endofunctors是从类别到自身的仿函数(就函数式程序员而言,它通常是所有 Functor
,因为它们主要只处理一个类别;类型类别 - 但是我离题了。但你可以想象另一个类别是“ X 上的endofunctors”类别。这是一个类别,其中对象是endofunctors,而态射是自然变换。
在那些endofunctors中,其中一些可能是monad。哪些是monad?正是特定意义上的 monoidal 的那些。而不是拼写出从monad到monoids的确切映射(因为Mac Lane确实比我希望的要好得多),我只是将它们各自的定义并排放在一起让你进行比较:
* -> *
,其中Functor
个实例)join
)return
)稍微眯着眼睛,你可能会发现这两个定义都是同一个abstract concept的实例。
答案 1 :(得分:502)
答案 2 :(得分:78)
首先,我们将要使用的扩展和库:
{-# LANGUAGE RankNTypes, TypeOperators #-}
import Control.Monad (join)
其中,RankNTypes
是唯一对下面绝对必要的人。 I once wrote an explanation of RankNTypes
that some people seem to have found useful,所以我会参考。
引用Tom Crockett's excellent answer,我们有:
monad是......
- endofunctor, T:X - > X 强>
- 自然变换, μ:T×T - > T ,其中×表示仿函数合成
- 自然变换, η:I - > T ,其中 I 是 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 }
f :-> g
形式的类型类似于函数类型,而不是将其视为两个类型之间的函数(类型{ {1}}),将其视为两个仿函数之间的态射(各种*
)。例子:
* -> *
基本上,在Haskell中,自然转换是从某种类型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
到另一种类型f x
的函数,因此g x
类型变量对于调用者来说是“不可访问的”。因此,例如,x
不能成为自然变换,因为它“挑剔”我们可以为sort :: Ord a => [a] -> [a]
实例化哪些类型。我经常使用的一种直观方式是:
现在,尽管如此,让我们解决定义的条款。
第一个句子是“endofunctor, T:X - > X 。”好吧,Haskell中的每个a
都是人们称之为“Hask类别”的endofunctor,其对象是Haskell类型(种类Functor
)并且其态射是Haskell函数。这听起来像一个复杂的陈述,但它实际上是一个非常微不足道的陈述。这意味着,*
为您提供了为任何Functor f :: * -> *
构建f a :: *
类型的方法,以及为任何a :: *
构建函数fmap f :: f a -> f b
的方法,以及这些都遵守了算子法则。
第二个子句:Haskell中的f :: a -> b
仿函数(随平台一起提供,所以你只需要导入它)就是这样定义的:
Identity
所以自然转变 η:I - >来自Tom Crockett定义的T 可以通过这种方式为任何newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
实例Monad
编写:
t
第三个条款:Haskell中两个仿函数的组合可以通过这种方式定义(也随平台一起提供):
return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)
因此自然变换 μ:T×T - >来自Tom Crockett定义的T 可以这样写:
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)
这是endofunctors类别中的monoid的陈述,然后意味着join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)
(仅部分应用于其前两个参数)是关联的,Compose
是其标识元素。即,以下同构持有:
Identity
Compose f (Compose g h) ~= Compose (Compose f g) h
Compose f Identity ~= f
这些很容易证明,因为Compose Identity g ~= g
和Compose
都定义为Identity
,而Haskell Reports将newtype
的语义定义为类型之间的同构正在定义以及newtype
的数据构造函数的参数类型。例如,让我们证明newtype
:
Compose f Identity ~= f
答案 3 :(得分:5)
注意:不,这不是真的。在某些时候,Dan Piponi自己对这个答案发表了评论,说这里的因果恰恰相反,他写了一篇文章来回应James Iry的讽刺。但它似乎已被删除,可能是一些强迫性的整洁。
以下是我的原始答案。
Iry很有可能阅读了From Monoids to Monads,其中Dan Piponi(sigfpe)从Haskell的monoids中获取monad,并对类别理论进行了大量讨论并明确提到了#34; Hask"上的endofunctors类别。无论如何,任何想知道monad在endofunctor类别中是monoid意味着什么的人都可能从阅读这个推导中受益。
答案 4 :(得分:3)
我是通过更好地理解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
。 该声明使用"类别..."这定义了语句的范围。例如,Functor类别描述f * -> g *
的范围,即Any functor -> Any functor
,例如Tree * -> List *
或Tree * -> Tree *
。
分类声明未指定的内容描述在何处以及允许的所有内容。
在这种情况下,在仿函数中,未指定* -> *
又名a -> b
,这意味着Anything -> Anything including Anything else
。当我的想象力跳到Int - >字符串,它还包括Integer -> Maybe Int
,甚至包括Maybe Double -> Either String Int
a :: Maybe Double; b :: Either String Int
。
所以声明如下:
:: f a -> g b
(即任何参数化类型的参数化类型):: f a -> f b
(即任何一个参数化类型为相同的参数化类型)......换句话说,那么,这个结构的力量在哪里?为了欣赏完整的动力学,我需要看到一个幺半群的典型图画(单个物体看起来像一个身份箭头,:: single object -> single object
),无法说明我允许使用箭头参数化使用任意数量的monoid值,来自Monoid中允许的 one 类型对象。等价的endo,〜identity箭头定义忽略了仿函数的类型值以及最内部的"有效负载&#34的类型和值;层。因此,在函数类型匹配的任何情况下,等价都会返回true
(例如,Nothing -> Just * -> Nothing
等同于Just * -> Just * -> Just *
,因为它们都是Maybe -> Maybe -> Maybe
)。
侧边栏:〜外部是概念性的,但是f a
中最左边的符号。它还描述了什么" Haskell"首先读入(大图);所以类型是"外面"与类型值有关。编程中的层(参考链)之间的关系在类别中不易相关。 Set的类别用于描述类型(Int,Strings,Maybe Int等),其中包括Functor类别(参数化类型)。参考链:Functor Type,Functor值(Functor的集合元素,例如Nothing,Just),以及每个functor值指向的其他所有内容。在类别中,关系的描述不同,例如,return :: a -> m a
被认为是从一个Functor到另一个Functor的自然转换,与迄今为止提到的任何内容都不同。
回到主线程,总而言之,对于任何已定义的张量积和中性值,该语句最终描述了一个从其矛盾结构中诞生的惊人强大的计算结构:
:: List
);静态fold
没有说明有效载荷)在Haskell中,澄清声明的适用性非常重要。这种结构的强大功能和多功能性与monad 本身无关。换句话说,构造不依赖于monad的独特之处。
当试图弄清楚是否构建具有共享上下文的代码以支持相互依赖的计算时,与可以并行运行的计算相比,这个臭名昭着的语句与其描述的不同之处并不是Applicative,Arrows和Monads的选择,而是描述它们是多少相同。对于手头的决定,声明没有实际意义。
这经常被误解。该声明继续将join :: m (m a) -> m a
描述为幺半群内向量的张量积。但是,它没有明确表示在本声明的背景下,(<*>)
也可以如何被选中。它确实是一个六/半打的例子。组合价值的逻辑完全相同;相同的输入从每个输出生成相同的输出(与Int的Sum和Product monoids不同,因为它们在组合Ints时会产生不同的结果)。
因此,回顾一下:endofunctors类别中的monoid描述:
~t :: m * -> m * -> m *
and a neutral value for m *
(<*>)
和(>>=)
都提供对两个m
值的同时访问,以便计算单个返回值。用于计算返回值的逻辑完全相同。如果不是函数的不同形状,则它们参数化(f :: a -> b
与k :: a -> m b
)以及具有相同计算返回类型的参数的位置(即a -> b -> b
与{分别为{1}},我怀疑我们可以参数化幺半群逻辑,张量积,以便在两个定义中重复使用。作为一个重点练习,请尝试并实施b -> a -> b
,最后得到~t
和(<*>)
,具体取决于您决定如何定义(>>=)
。
如果我的最后一点至少在概念上是正确的,那么它解释了Applicative和Monad之间的精确且唯一的计算差异:它们参数化的函数。换句话说,差异是外部到这些类型类的实现。
总而言之,根据我自己的经验,Mac Lane臭名昭着的引用提供了一个伟大的&#34; goto&#34; meme,我在引导我浏览类别时引用的指南,以更好地理解Haskell中使用的习语。它成功地捕获了在Haskell中非常容易访问的强大计算能力的范围。
但是,我最初误解了monad之外的声明的适用性,以及我希望在这里传达的内容,具有讽刺意味。它所描述的一切都证明了Applicative和Monads(以及其他箭头)之间的相似之处。它没有说的正是它们之间的微小但有用的区别。
- E
答案 5 :(得分:2)
这里的答案在定义monoid和monad方面做得很好,但是,它们似乎仍然无法回答问题:
还有一个不太重要的注解,这是真的吗?如果可以的话,您能否给出一个解释(希望是一个没有太多Haskell经验的人可以理解的解释)?
这里遗漏的问题的症结在于“ monoid”的不同概念,更准确地说是所谓的“ em归类” -一个单面体类别中的类半体中的一个。可悲的是Mac Lane的书本身makes it very confusing:
总而言之,
X
中的一个monad只是X
的endofunctors类别中的一个monoid,乘积×
被endofunctors的组成和由单位endofunctor设置的单位代替。
这为什么令人困惑?因为它没有定义X
的“ endofunctors类别中的monoid”。取而代之的是,这句话建议在所有endofunctors的集合内使用一个类半群,并将该函子组成作为二元运算,将身份函子作为一个类群单元。可以很好地工作,并且可以将包含身份函子并且在函子组成下封闭的endofunctors的任何子集变成一个半圆形。
然而,这并不是正确的解释,这本书当时还没有明确说明。 Monad f
是 fixed 终结符,不是组成时关闭的终结符的子集。一种常见的构造是使用f
本身k
的所有f^k = f(f(...))
倍组合f
的组合{{1}包括与身份k=0
相对应的f^0 = id
。现在,所有S
的所有这些幂的集合k>=0
确实是一个mono半同形的词,“乘积×被endofunctors的组成所取代,单位由身份endofunctor所设置”。
但是:
S
甚至甚至从字面上为f
的任何自映射定义此等分体X
。它是f
生成的monoid。S
的单曲面结构与f
是单子还是非单子无关。为了使事情更加混乱,您可以在table of contents中看到“单曲面类别中的Monoid”的定义。然而,理解这一概念对于理解与monad的联系绝对至关重要。
转到有关Monoids的第七章(比Monads的VI章晚),我们发现所谓的严格Monoidal类别的定义为三元组(B, *, e)
,其中{{ 1}}是一个类别,B
是一个 bifunctor (相对于固定了其他组件的每个组件而言的functor),而*: B x B-> B
是e
中的一个单元对象,满足关联性和单位律:
B
对于(a * b) * c = a * (b * c)
a * e = e * a = a
的任何对象a,b,c
,以及对于B
被a,b,c
代替的e
的任何形态id_e
的相同身份,{{ 1}}。现在,在我们感兴趣的情况下观察是有启发性的,其中e
是B
的终结符的类别,其中X
是函子组成,*
是自然变换为态射。身份函子,所有这些定律都可以满足,可以直接验证。
本书中紧随其后的是“放松”的 monoidal类别的定义,其中法律仅对某些固定的自然变换进行模运算,这些自然变换满足所谓的相干关系 ,但是对于我们的endofunctor类别而言,这并不重要。
最后,在第七章的第3节“ Monoids”中给出了实际定义:
在单面体类别
e
中的单面体c
是(B, *, e)
的带有两个箭头(同构)的对象
B
使3个图互换。回想一下,在我们的情况下,这些是endofunctors类别中的态射,这是与monad正好对应于mu: c * c -> c
nu: e -> c
和join
的自然变换。当我们使构图return
更明确,用*
代替c * c
时,联系变得更加清晰,其中c^2
是我们的单子。
最后,请注意,为一般(非严格)单曲面类别编写了3个交换图(按单曲面类别中的单曲面定义),而在我们的案例中,作为单曲面类别一部分的所有自然变换实际上都是身份。这将使这些图与monad定义中的图完全相同,从而使对应关系完整。
总而言之,根据定义,任何monad都是endofunctor,因此是endofunctors类别中的一个对象,其中monadic c
和join
运算符满足一个单义类的定义(严格)等分类别。反之亦然,根据定义,endofunctors的单面体类别中的任何单面体都是由对象和两个箭头组成的三元组return
。在我们的案例中是自然变换,满足与monad相同的定律。
最后,请注意(经典)id半群词和更普遍的mono半群词之间的主要区别。上方的两个箭头(c, mu, nu)
和mu
不再是二进制运算和集合中的一个单元。相反,您有一个固定的endofunctor nu
。尽管书中有令人困惑的注释,但函子组成c
和身份函子本身并不能提供monad所需的完整结构。
另一种方法是与集合*
的所有自映射的标准monoid C
进行比较,其中二元运算是组成,可以看到映射了笛卡尔积A
到C x C
中。传递到归类的monoid,我们用函子组成C
替换笛卡尔乘积x
,而二元运算被替换为自然变换*
mu
至c * c
,即c
个运算符的集合
join
对于每个对象join: c(c(T))->c(T)
(在编程中键入)。可以用T
运算符
return
但是现在不再有笛卡尔积,所以没有成对的元素,因此也没有二进制运算。