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确实比我希望的要好得多),我只是将它们各自的定义并排放在一起,让你比较:
* -> *
带有Functor
实例的类型构造函数)join
在Haskell中已知)return
在Haskell中已知)稍微眯着眼睛,你可能会发现这两个定义都是同一个抽象概念的实例.
mis*_*bee 522
直觉上,我认为花哨的数学词汇所说的是:
阿半群是一组对象,以及组合它们的方法.众所周知的幺半群是:
还有更复杂的例子.
此外,每个 monoid都有一个标识,这是"no-op"元素,当你将它与其他东西结合起来时没有效果:
最后,一个幺半群必须是联想的.(你可以随意减少一长串组合,只要你不改变对象从左到右的顺序)加法就可以了((5 + 3)+1 == 5+(3+) 1)),但减法不是((5-3)-1!= 5-(3-1)).
现在,让我们考虑一种特殊的集合和一种组合对象的特殊方法.
假设您的集合包含特殊类型的对象:函数.这些函数有一个有趣的签名:它们不会将数字带到数字或字符串中.相反,每个函数在两个步骤中将数字带到一个数字列表中.
例子:
另外,我们的功能组合方式很特别.组合函数的一种简单方法是组合:让我们看一下上面的例子,并用自己编写每个函数:
如果没有太多的类型理论,关键是你可以组合两个整数来得到一个整数,但你不能总是组成两个函数并获得相同类型的函数.(类型为a - > a的函数 将组成,但a-> [a]不会.)
所以,让我们定义一种组合函数的不同方式.当我们结合其中两个函数时,我们不希望"双重包装"结果.
这就是我们的工作.当我们想要组合两个函数F和G时,我们遵循这个过程(称为绑定):
回到我们的例子,让我们使用这种"绑定"函数的新方法将一个函数与自身结合(绑定):
这种更复杂的组合函数的方法是关联的(当你没有做出花哨的包装东西时,跟随函数组合是如何关联的).
捆绑在一起,
有很多方法可以"包装"结果.你可以制作一个列表,一个集合,或丢弃除第一个结果之外的所有结果,同时注意是否没有结果,附加状态的边车,打印日志消息等等.
我对这些定义有点松散,希望能够直观地了解基本概念.
我通过坚持我们的monad操作类型为a - > [a]的函数来简化了一些事情.事实上,monad在a - > mb类型的函数上工作,但泛化是一种技术细节,而不是主要的洞察力.
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".好吧,Functor
Haskell中的每一个都是人们称之为"Hask类别"的endofunctor,其对象是Haskell类型(类型*
),其状态是Haskell函数.这听起来像一个复杂的陈述,但它实际上是一个非常微不足道的陈述.它的意思是说,一个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)
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
这些都是很容易证明,因为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.
Run Code Online (Sandbox Code Playgroud)
Dmi*_*sev 12
这里的答案在定义幺半群和单子方面做得很好,但是,它们似乎仍然没有回答这个问题:
在不太重要的一点上,这是真的吗?如果是,您能否给出解释(希望没有太多 Haskell 经验的人可以理解)?
这里缺少的问题的关键是“幺半群”的不同概念,更准确地说是所谓的分类——幺半群中的幺半群。可悲的是,麦克莱恩的书本身就让人非常困惑:
总而言之,单子 in
X
只是 的内函子范畴中的幺半群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,c
的B
,而对于任何态射相同身份a,b,c
与e
替换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的范畴,它是对应于精确自然变换join
和return
一个单子。当我们使组合*
更加明确时,连接变得更加清晰,替换c * c
为c^2
,c
我们的 monad在哪里。
最后,请注意这 3 个交换图(在幺半群中的幺半群的定义中)是为一般(非严格)幺半群写的,而在我们的例子中,作为幺半群范畴的一部分出现的所有自然变换实际上都是恒等式。这将使图表与 monad 定义中的图表完全相同,从而使对应关系完整。
总之,根据定义,任何 monad 都是一个内函子,因此是内函子范畴中的一个对象,其中 monadicjoin
和return
运算符满足该特定(严格)幺半群中的幺半群的定义。反之亦然,根据定义,内函子的幺半群中的任何幺半群都是由(c, mu, nu)
一个对象和两个箭头组成的三元组,例如在我们的例子中的自然变换,满足与单子相同的定律。
最后,请注意(经典)幺半群与幺半群范畴中更一般的幺半群之间的主要区别。的两个箭头mu
和nu
以上是不再在一组二进制运算和一单元。相反,你有一个固定的内函子c
。*
尽管书中有令人困惑的评论,但函子组合和恒等函子本身并不能提供 monad 所需的完整结构。
另一种方法是与C
集合 的所有自映射的标准幺半群进行比较A
,其中二元运算是组合,可以看出将标准笛卡尔积映射C x C
到C
。传递到分类幺半群,我们用x
函子组合替换笛卡尔积,*
二元运算被替换为mu
从
c * c
to的自然变换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)
但是现在没有更多的笛卡尔积,所以没有元素对,因此没有二元运算。
注意:不,这不是真的.在某些时候,Dan Piponi本人对这个答案发表了评论,他说这里的因果恰恰相反,他在回应James Iry的讽刺时写了他的文章.但它似乎已被删除,可能是通过一些强迫性的整洁.
以下是我原来的答案.
Iry很可能已经读过Monoids到Monads,其中Dan Piponi(sigfpe)从Haskell的monoids中衍生monad,并对类别理论进行了大量讨论并明确提到了"Hask上的endofunctors类别".无论如何,任何想知道monad在endofunctor类别中是monoid意味着什么的人都可能从阅读这个推导中受益.
我是通过更好地理解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 *
Run Code Online (Sandbox Code Playgroud)
(<*>)
并且(>>=)
既提供对两个同时访问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
归档时间: |
|
查看次数: |
148369 次 |
最近记录: |