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

谁首先说了以下几点?   monad只是一个幺半群   endofunctors的类别,是什么   问题? 在一个不太重要的注意事项上,这是真的,如果是这样,你能给出一个解释(希望有一个可以被没有Haskell经验的人理解的解释)吗?     
已邀请:
詹姆斯·伊里(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 ......满足这些法律: (a•b)•c = a•(b•c),对于S中的所有a,b和c e•a = a•e = a,对于S中的所有a monad是...... 一个endofunctor,T:X→X(在Haskell中,类型构造函数为
* -> *
,带有
Functor
实例) 自然变换,μ:T×T→T,其中×表示仿函数组成(μ在Haskell中称为
join
) 一个自然变换,η:I→T,其中我是X上的身份endofunctor(η在Haskell中被称为
return
) ......满足这些法律: μ∘Tμ=μ∘μT μ∘Tη=μ∘ηT= 1(身份自然变换) 稍微眯着眼睛,你可能会发现这两个定义都是同一个抽象概念的实例。     
直觉上,我认为花哨的数学词汇所说的是: 幺 monoid是一组对象,以及组合它们的方法。众所周知的幺半群是: 您可以添加的数字 您可以连接的列表 你可以结合 还有更复杂的例子。 此外,每个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))。 单子 现在,让我们考虑一种特殊的集合和一种组合对象的特殊方法。 对象 假设您的集合包含特殊类型的对象:函数。这些函数有一个有趣的签名:它们不会将数字带到数字或字符串中。相反,每个函数在两个步骤中将数字带到一个数字列表中。 计算0或更多结果 不知怎的,将这些结果合并到一个答案中。 例子: 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时,我们遵循这个过程(称为绑定): 计算F中的“结果”,但不要将它们组合起来。 计算将G分别应用于F的每个结果的结果,得到一组结果集合。 展平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 - > m b类型的函数上工作,但是泛化是一种技术细节,而不是主要的洞察力。     
首先,我们将使用的扩展和库:
{-# LANGUAGE RankNTypes, TypeOperators #-}

import Control.Monad (join)
其中,
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 }
形式
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]
不能成为自然变换,因为它“挑剔”我们可以为ѭ17实例化哪些类型。我经常使用的一种直观方式是: 仿函数是一种在不触及结构的情况下对某些内容进行操作的方法。 自然变换是一种操作某事物结构的方式,而不会触及或查看内容。 现在,让我们解决这个定义的条款。 第一个子句是“endofunctor,T:X - > X”。好吧,Haskell中的每个
Functor
都是人们称之为“Hask类别”的endofunctor,其对象是Haskell类型(种类
*
),其态射是Haskell函数。这听起来像一个复杂的陈述,但它实际上是一个非常微不足道的陈述。所有这一切意味着,
Functor f :: * -> *
为你提供了为任何
a :: *
构造类型
f a :: *
和任何
f :: a -> b
中的函数
fmap f :: f a -> f b
的方法,并且这些符合算子法则。 第二个子句:Haskell中的
Identity
仿函数(随平台一起提供,所以你只需要导入它)就是这样定义的:
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)
因此Tom Crockett定义的自然变换μ:T×T - > T可以这样写:
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.
    
注意:不,这不是真的。在某些时候,Dan Piponi自己对这个答案发表了评论说,这里的因果恰恰相反,他在回应James Iry的讽刺时写了他的文章。但它似乎已被删除,可能是通过一些强迫性的整洁。 以下是我原来的答案。 Iry很可能已经读过Monoids到Monads,其中Dan Piponi(sigfpe)从Haskell的monoids中衍生monad,并对类别理论进行了大量讨论并明确提到了“Hask上的endofunctors类别”。无论如何,任何想知道monad在endofunctor类别中是monoid意味着什么的人都可能从阅读这个推导中受益。     

要回复问题请先登录注册