谁先说以下?
一个monad只是endofunctors类别中的一个monoid,这是什么问题?
在一个不太重要的注解上,这是真的吗?如果可以的话,您能否给出一个解释(希望是一个没有太多Haskell经验的人可以理解的解释)?
谁先说以下?
一个monad只是endofunctors类别中的一个monoid,这是什么问题?
在一个不太重要的注解上,这是真的吗?如果可以的话,您能否给出一个解释(希望是一个没有太多Haskell经验的人可以理解的解释)?
Answers:
詹姆斯·艾里(James Iry)的特别措辞来自他极富娱乐性的《简明,不完整和大部分错误的编程语言史》,他在小说中将其归因于菲利普·沃德勒。
原始报价摘自Saunders Mac Lane 的《工作数学家的类别》,这是类别理论的基础文章之一。在上下文中,这可能是确切了解其含义的最佳位置。
但是,我会刺。原来的句子是这样的:
总而言之,X中的单子仅是X的终结符类别中的一个单义词,乘积×由终结符的组成替换,单位由身份终结符设置。
X这里是一个类别。Endofunctors是从一个类别到其自身的函子(就函数式程序员而言,通常是所有 函子Functor
,因为它们主要只处理一个类别;类型的类别,但我离题了)。但是您可以想象另一个类别,即“ X上的 endofunctors ” 类别。这是一个类别,其中的对象是内函子,同态射是自然变换。
在这些终结者中,有些可能是单子。哪一个是单子?在特定意义上恰好是单调的。与其说出从单子到Monoid的确切映射(因为Mac Lane的性能远远超出我的期望),不如将它们各自的定义放在一起,让您进行比较:
* -> *
带有Functor
实例的类型的构造函数)join
在Haskell中是已知的)return
在Haskell中是已知的)稍作斜视,您也许就能看到这两个定义都是同一抽象概念的实例。
S
是类型,则编写函数时所能做的f :: () -> S
就是挑选某个特定类型的术语S
(如果可以的话,选择它的“元素”)并返回它...您未获得有关该参数的实际信息,因此无法更改该函数的行为。因此f
必须是一个常数函数,每次都只返回相同的内容。()
(“ Unit”)是Hask类别的终端对象,并且恰好有1个(非散度)值存在于其中,这并非巧合。
凭直觉,我认为幻想的数学词汇是这样说的:
一个monoid是一组对象,以及一种将它们组合的方法。著名的monoid为:
还有更复杂的例子。
此外,每个 monoid都有一个identity,即将“ no-op”元素与其他元素组合在一起时无效的元素:
最后,一个monoid必须是关联的。(只要不改变对象从左到右的顺序,就可以随意减少一长串组合)加法是确定的((5 + 3)+1 == 5+(3+ 1)),但减法不是((5-3)-1!= 5-(3-1))。
现在,让我们考虑一种特殊的集合和一种组合对象的特殊方式。
假设您的集合包含特殊类型的对象:functions。这些函数具有一个有趣的签名:它们不将数字带数字或字符串带字符串。而是,每个函数在两步过程中将数字携带到数字列表中。
例子:
同样,我们组合功能的方式也很特殊。组合函数的一种简单方法是composition:让我们以上面的示例为例,并使用自身组合每个函数:
不必过多研究类型理论,关键是可以组合两个整数来获得一个整数,但是不能总是组成两个函数并获得相同类型的函数。(类型为a-> a的函数 将组成,但类型a-> [a]则不会。)
因此,让我们定义一种不同的功能组合方式。当我们将其中两个功能结合在一起时,我们不想对结果进行“双重包装”。
这是我们的工作。当我们想要组合两个函数F和G时,我们遵循以下过程(称为binding):
回到我们的示例,让我们使用这种“绑定”函数的新方式将一个函数与自身结合(绑定):
这种更复杂的功能组合方式是关联的(根据您在不进行精美包装时的功能组合是如何关联的)。
绑在一起
有很多方法可以“包装”结果。您可以列出或设置列表,也可以舍弃除第一个结果以外的所有结果,同时注意是否没有结果,附加状态提示,打印日志消息等。
我在定义上有些松懈,希望可以直观地理解基本思想。
我坚持要求我们的monad对a- > [a]类型的函数进行操作,从而简化了某些事情。实际上,monad可以处理a- > mb类型的函数,但是泛化只是一种技术细节,并不是主要的见识。
a -> [b]
并且c -> [d]
(只能在b
=时执行此操作c
),因此并不能很好地描述一个类人动物。实际上,它是您描述的展平操作,而不是函数组合,即“ monoid运算符”。
a -> [a]
这将是一个monoid(因为您将Kleisli类别简化为单个对象,而任何类别都只有一个对象从定义上讲是一个monoid!),但它不能捕获monad的全部一般性。
首先,我们将要使用的扩展和库:
{-# LANGUAGE RankNTypes, TypeOperators #-}
import Control.Monad (join)
其中,RankNTypes
是唯一对以下内容绝对必要的内容。我曾经写过一篇关于RankNTypes
有些人似乎发现有用的解释,所以我将参考它。
引用汤姆·克罗基特的出色答案,我们有:
一个单子是...
- 一个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 }
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
基本上,在Haskell中,自然变换是某种类型的函数 f x
到另一种类型g x
,使得x
类型变量为“不可访问”给调用者。因此,例如,sort :: Ord a => [a] -> [a]
不能将其转换为自然变换,因为它对于我们可以实例化的类型是“挑剔的” a
。我经常想到的一种直观方法是:
现在,让我们解决这个定义的子句。
第一个子句是“ endofunctor,T:X-> X”。嗯,Functor
Haskell中的每个对象都是人们所说的“ Hask类别”的终结者,其对象是Haskell类型(种类*
),其态射是Haskell函数。这听起来像是一个复杂的陈述,但实际上是一个非常琐碎的陈述。它的全部意思是a Functor f :: * -> *
提供了构造类型的方法f a :: *
为任何对象a :: *
和fmap f :: f a -> f b
从任何对象函数的方法f :: a -> b
,并且这些方法均服从函子定律。
第二条: Identity
Haskell中仿函数(随平台提供,因此您只需导入即可)是按以下方式定义的:
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
因此,可以将汤姆·克罗基特定义中的自然变换η: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)
因此自然变换μ:T×T-> T,Tom Crockett定义中可以这样写:
join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)
声明这是endofunctors类别中的一个monoid,则意味着Compose
(部分地仅应用于它的前两个参数)是关联的,这Identity
就是它的标识元素。即,以下同构成立:
Compose f (Compose g h) ~= Compose (Compose f g) h
Compose f Identity ~= f
Compose Identity g ~= g
这些都很容易证明,因为Compose
和Identity
都定义为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.
Natural
,我无法弄清楚(Functor f, Functor g)
约束在做什么。你能解释一下吗?
Functor
约束,因为它们似乎没有必要。如果您不同意,请随时将其添加回去。
join
定义时才定义。那join
就是投影态射。但是我不确定。
注意:不,这是不正确的。在某个时候,丹·皮波尼本人对此回答发表了评论,说这里的因果正好相反,他写这篇文章是为了回应詹姆斯·伊里的怪癖。但是它似乎已经被删除了,也许是由于一些强迫性的手段。
以下是我的原始答案。
Iry很有可能读过《从Monoids到Monads》一书,其中Dan Piponi(sigfpe)在Haskell中从monoids派生了monads,他对类别理论进行了很多讨论,并明确提到了“ Hask上的终结者的类别”。无论如何,任何想知道单子组成为内爆者类中的类群的意义的人都可以从阅读此推导中受益。
:-)
。
我通过更好地理解Mac Lane的《为数学家的分类理论》中的臭名昭著的推论而来这篇文章。。
在描述什么是事物时,描述它不是事物通常同样有用。
Mac Lane使用描述来描述Monad的事实,可能暗示它描述了Monad独有的东西。忍受我。为了使人们对这一说法有更广泛的了解,我认为需要明确指出他不是描述单子论所独有的东西。该声明除其他外同样描述了Applicative和Arrows。出于相同的原因,我们可以在Int上拥有两个单半体(Sum和Product),在endofunctors类别中,我们可以在X上具有多个单半体。但是更多的相似之处。
Monad和Applicative均符合以下条件:
(例如,每天Tree a -> List b
,但在类别中Tree -> List
)
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 Int
where a :: Maybe Double; b :: Either String Int
。
因此,该语句如下:
:: f a -> g b
(即,任何参数化类型到任何参数化类型):: f a -> f b
(即,任何一种参数化类型都属于相同的参数化类型)...表示不同,那么,这种构造的力量在哪里?为了欣赏完整的动态效果,我需要看到一个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
);静态的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 *
(<*>)
并且(>>=)
既提供对两个同时访问m
,以便计算所述单个返回值的值。用于计算返回值的逻辑是完全相同的。如果不是因为他们参数(该功能的不同形状f :: a -> b
与k :: a -> m b
)和参数与计算相同的返回类型的位置(即a -> b -> b
对b -> a -> b
每一个分别的),我想我们可以在参数化monoidal的逻辑,张量积,可在两个定义中重用。作为一个练习,使点,尝试和实施~t
,以及你结束了(<*>)
和(>>=)
取决于你决定如何定义它forall a b
。
如果我的最后一点在概念上至少是对的,那么它将解释Applicative和Monad之间的精确且仅有计算上的区别:它们对函数进行参数化。换句话说,不同的是外部对这些类型的类的实现。
总而言之,以我自己的经验,Mac Lane的臭名昭著的报价提供了一个很棒的“ goto”模因,这是我在浏览类别以更好地理解Haskell中使用的成语时可以参考的指南。它成功地捕获了可在Haskell中完美访问的强大计算能力的范围。
但是,具有讽刺意味的是,我是如何首先误解了该声明在monad之外的适用性,以及我希望在此传达的内容。它描述的所有内容在Applicative和Monad(以及Arrows等)之间是相似的。它没有说的只是它们之间很小但有用的区别。
-E
这里的答案在定义monoid和monad方面做得非常出色,但是,它们似乎仍然无法回答这个问题:
在一个不太重要的注解上,这是真的吗?如果可以的话,您能否给出一个解释(希望是一个没有太多Haskell经验的人可以理解的解释)?
这里遗漏的问题的症结在于“ monoid”的不同概念,更准确地说是所谓的分类 -在monoidal类别中的monoid。可悲的是,Mac Lane的书本身使人非常困惑:
总而言之,in中的monad
X
只是endofunctors类别中的一个monoidX
,乘积×
由endofunctors的组成代替,单位由endofunctor标识设置。
为什么这令人困惑?因为它没有定义的“ endofunctors类别中的monoid” X
。取而代之的是,这句话建议将所有endofunctors集合内的一个类半群与函子组成一起作为二元运算,将身份函子作为一个单半群单元。它工作得非常好,并且可以将包含身份函子且在函子组成下封闭的endofunctors的任何子集变成一个monoid。
但这并不是正确的解释,这本书在那个阶段还没有弄清楚。一个Monad f
是固定的 endofunctors,而不是根据组成封闭的endofunctors的子集。一种常见的结构是使用f
以产生通过取该组所有的幺半k
倍的组合物f^k = f(f(...))
的f
与本身,包括k=0
对应于身份f^0 = id
。现在S
,所有这些所有权力的集合k>=0
确实是一个mono半同形的“乘积×被终结者组成,单位由身份终结者所设定”。
但是:
S
可以为任何函子定义此monoid ,f
甚至可以为的任何自映射定义该monoid X
。这是由生成的类集f
。S
由函子组成和恒等函子给定的单曲面结构与f
是否为单子无关。为了使事情更加混乱,您可以从目录中看到“单曲面类别中的Monoid”的定义。但是,理解这一概念对于理解与monad的联系绝对至关重要。
要第七章幺(后来自带比第六章上单子),我们发现了所谓的定义严格monoidal范畴作为三重(B, *, e)
,其中B
是一个类别,*: B x B-> B
一个bifunctor(函子相对于与其他组件的每个部件固定),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
具有启发性的观察结果是,在我们感兴趣的情况下,X
以自然变换为态素*
的函子类,函子组成和e
身份函子的类别满足所有这些定律,可以直接验证。
这本书中的定义是“松弛” 单项类的定义,其中法律仅对满足所谓相干关系的某些固定自然变换进行模运算,但是这对于我们的终结论者类而言并不重要。
最后,在第七章的第3节“ Monoids”中,给出了实际的定义:
阿半群
c
在monoidal范畴(B, *, e)
是的目的B
有两个箭头(态射)
mu: c * c -> c
nu: e -> c
使3个图可交换。回想一下,在我们的案例中,这些是endofunctors类别中的态射,这是join
与return
monad 精确对应的自然变换。当我们使组合物的连接变得更加清晰*
更加明确,取代c * c
通过c^2
,这里c
是我们的单子。
最后,请注意,为一般(非严格)单曲面类别编写了3个交换图(按单曲面类别中的单曲面定义),而在我们的情况下,作为单曲面类别的一部分出现的所有自然变换实际上都是恒等式。这将使这些图与monad定义中的图完全相同,从而使对应关系完整。
总之,根据定义,任何monad都是endofunctor,因此是endofunctors类别中的一个对象,其中monadic join
和return
运算符满足该特定(严格)monoidal类别中的monoid的定义。反之亦然,根据定义,endofunctors的monoidal类别中的任何monooid都是由(c, mu, nu)
一个对象和两个箭头组成的三元组,例如,在我们的情况下为自然变换,满足与monad相同的定律。
最后,请注意(经典)monoid和更简单的monoidal类中的monoid之间的主要区别。两个箭头mu
和nu
以上是不再在一组二进制运算和一单元。相反,您有一个固定的endofunctor c
。*
尽管书中的说法令人困惑,但仅函子组成和身份函子无法提供monad所需的完整结构。
另一种方法是与C
集合中所有自映射的标准monoid比较A
,其中二进制运算是组合,可以看到将标准笛卡尔积映射C x C
到C
。传递给归类的monoid,我们用x
函子组成替换笛卡尔乘积*
,并且二元运算被替换为自然转换mu
从
c * c
到c
,即join
运算符的集合
join: c(c(T))->c(T)
对于每个对象T
(编程中的类型)。可以用固定的单点集的地图图像标识的经典类四面体中的标识元素,由return
运算符集合代替
return: T->c(T)
但是现在不再有笛卡尔积,因此没有元素对,因此也没有二进制运算。