单子只是内函子类别中的幺半群,有什么问题吗?

发布于 2024-09-26 03:14:11 字数 157 浏览 9 评论 0 原文

下面这句话是谁先说的?

单子只是其中的一个幺半群 内函子的类别,是什么 有问题吗?

另一个不太重要的问题是,这是真的吗?如果是的话,您能否给出一个解释(希望没有太多 Haskell 经验的人能够理解)?

Who first said the following?

A monad is just a monoid in the
category of endofunctors, what's the
problem?

And on a less important note, is this true and if so could you give an explanation (hopefully one that can be understood by someone who doesn't have much Haskell experience)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

梦行七里 2024-10-03 03:14:11

这个特殊的措辞是 James Iry 的,来自他非常有趣的编程语言的简短、不完整且大多错误的历史,其中他虚构地将其归因于 Philip Wadler。

原始引用来自 Saunders Mac Lane 的《工作数学家的范畴》,范畴论的基础文本之一。 这里是上下文,这可能是了解其确切含义的最佳位置。

不过,我还是会尝试一下。原句是这样的:

总而言之,X 中的单子只是 X 的内函子类别中的幺半群,其中乘积 × 被内函子的组合所取代,单位由恒等内函子设定。

X 这里是一个类别。 Endofunctor 是从一个类别到其自身的函子(就函数式程序员而言,通常是所有函子,因为他们大多只处理一个类别;类别类型 - 但我离题了)。但你可以想象另一个类别,即“X 上的endofunctors”类别。在这个范畴中,对象是内函子,态射是自然变换。

在这些内函子中,其中一些可能是单子。哪些是单子?正是那些在特定意义上幺半群。我不会详细说明从 monad 到 monoid 的精确映射(因为 Mac Lane 在这方面做得比我希望的要好得多),我只是将它们各自的定义并排放置并让您进行比较:

幺半群是...

  • 一个集合, S
  • 一个运算,• : S × S → S
  • S 的一个元素>, e : 1 → S

...满足以下定律:

  • (a • b) • c = a • (b • c) ,对于 S 中的所有 abc
  • e • a = a • e = a ,对于 S 中的所有 a

单子是...

  • 一个内函子,T : X → X (在 Haskell 中,带有 Functor 实例的 * -> * 类型构造函数)
  • 自然变换,μ : T × T → T,其中 × 表示函子组合(μ 称为 join (Haskell 中)
  • 自然转换,η : I → T,其中 IX 上的恒等函子(η 是称为 return 在 Haskell 中)

...满足这些定律:

  • μ ∘ Tμ = μ ∘ μT
  • μ ∘ Tη = μ ∘ ηT = 1 (身份自然转换)

稍微眯一下眼睛,您可能会发现这两个定义都是相同的实例 抽象概念

That particular phrasing is by James Iry, from his highly entertaining Brief, Incomplete and Mostly Wrong History of Programming Languages, in which he fictionally attributes it to Philip Wadler.

The original quote is from Saunders Mac Lane in Categories for the Working Mathematician, one of the foundational texts of Category Theory. Here it is in context, which is probably the best place to learn exactly what it means.

But, I'll take a stab. The original sentence is this:

All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor.

X here is a category. Endofunctors are functors from a category to itself (which is usually all Functors as far as functional programmers are concerned, since they're mostly dealing with just one category; the category of types - but I digress). But you could imagine another category which is the category of "endofunctors on X". This is a category in which the objects are endofunctors and the morphisms are natural transformations.

And of those endofunctors, some of them might be monads. Which ones are monads? Exactly the ones which are monoidal in a particular sense. Instead of spelling out the exact mapping from monads to monoids (since Mac Lane does that far better than I could hope to), I'll just put their respective definitions side by side and let you compare:

A monoid is...

  • A set, S
  • An operation, • : S × S → S
  • An element of S, e : 1 → S

...satisfying these laws:

  • (a • b) • c = a • (b • c), for all a, b and c in S
  • e • a = a • e = a, for all a in S

A monad is...

  • An endofunctor, T : X → X (in Haskell, a type constructor of kind * -> * with a Functor instance)
  • A natural transformation, μ : T × T → T, where × means functor composition (μ is known as join in Haskell)
  • A natural transformation, η : I → T, where I is the identity endofunctor on X (η is known as return in Haskell)

...satisfying these laws:

  • μ ∘ Tμ = μ ∘ μT
  • μ ∘ Tη = μ ∘ ηT = 1 (the identity natural transformation)

With a bit of squinting you might be able to see that both of these definitions are instances of the same abstract concept.

放我走吧 2024-10-03 03:14:11

首先,我们将要使用的扩展和库:

{-# LANGUAGE RankNTypes, TypeOperators #-}

import Control.Monad (join)

其中,RankNTypes 是唯一对以下内容绝对重要的扩展和库。 我曾经写过一篇关于 RankNTypes 的解释人们似乎发现有用,所以我会参考一下。

引用 Tom Crockett 的出色回答,我们有:

单子是...

  • 一个内函子,T : X -> X
  • 自然变换,μ : T × T -> T,其中×表示函子组合
  • 自然变换,η:I -> T,其中 IX
  • 上的恒等函子

...满足这些法律:

  • μ(μ(T × T) × T)) = μ(T × μ(T × T))
  • μ(η(T)) = T = μ(T(η))

我们如何将其转换为哈斯克尔代码?好吧,让我们从自然变换的概念开始:

-- | 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 实例化的类型“挑剔”。我经常用来思考这个问题的一种直观方式如下:

  • 函子是一种在不触及结构的情况下对某事物的内容进行操作的方式。
  • 自然转换是一种在不接触或查看内容的情况下对事物的结构进行操作的方法。

现在,解决这个问题,让我们来处理定义的子句。

第一个子句是“一个endofunctor,T : X -> X”。好吧,Haskell 中的每个函子都是人们所谓的“Hask 类别”中的一个内函子,其对象是 Haskell 类型(类型为 *),其态射是 Haskell 函数。这听起来像是一个复杂的说法,但实际上是一个非常简单的说法。它的意思是函子 f :: * -> * 为您提供了为任何 a :: * 构造类型 fa :: * 和函数 fmap f :: fa -> 的方法; f b 出自任何 f :: a ->; b,并且它们遵守函子定律。

第二个子句:Haskell 中的 Identity 函子(平台附带,因此您可以导入它)是这样定义的:

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    fmap f (Identity a) = Identity (f a)

所以自然变换η : I ->对于任何 Monad 实例 t,Tom Crockett 定义中的 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 -> Tom Crockett 的定义中的 T 可以这样写:

join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)

这是 endofunctors 类别中的幺半群的陈述意味着 Compose (部分应用于其前两个参数)是关联的,Identity 是它的标识元素。即,以下同构成立:

  • Compose f (Compose gh) ~= Compose (Compose fg) h
  • Compose f Identity ~= f
  • Compose Identity g ~= g

这些很容易证明,因为 ComposeIdentity 都定义为 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.

First, the extensions and libraries that we're going to use:

{-# LANGUAGE RankNTypes, TypeOperators #-}

import Control.Monad (join)

Of these, RankNTypes is the only one that's absolutely essential to the below. I once wrote an explanation of RankNTypes that some people seem to have found useful, so I'll refer to that.

Quoting Tom Crockett's excellent answer, we have:

A monad is...

  • An endofunctor, T : X -> X
  • A natural transformation, μ : T × T -> T, where × means functor composition
  • A natural transformation, η : I -> T, where I is the identity endofunctor on X

...satisfying these laws:

  • μ(μ(T × T) × T)) = μ(T × μ(T × T))
  • μ(η(T)) = T = μ(T(η))

How do we translate this to Haskell code? Well, let's start with the notion of a natural transformation:

-- | 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 type of the form f :-> g is analogous to a function type, but instead of thinking of it as a function between two types (of kind *), think of it as a morphism between two functors (each of kind * -> *). Examples:

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

Basically, in Haskell, natural transformations are functions from some type f x to another type g x such that the x type variable is "inaccessible" to the caller. So for example, sort :: Ord a => [a] -> [a] cannot be made into a natural transformation, because it's "picky" about which types we may instantiate for a. One intuitive way I often use to think of this is the following:

  • A functor is a way of operating on the content of something without touching the structure.
  • A natural transformation is a way of operating on the structure of something without touching or looking at the content.

Now, with that out of the way, let's tackle the clauses of the definition.

The first clause is "an endofunctor, T : X -> X." Well, every Functor in Haskell is an endofunctor in what people call "the Hask category," whose objects are Haskell types (of kind *) and whose morphisms are Haskell functions. This sounds like a complicated statement, but it's actually a very trivial one. All it means is that that a Functor f :: * -> * gives you the means of constructing a type f a :: * for any a :: * and a function fmap f :: f a -> f b out of any f :: a -> b, and that these obey the functor laws.

Second clause: the Identity functor in Haskell (which comes with the Platform, so you can just import it) is defined this way:

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    fmap f (Identity a) = Identity (f a)

So the natural transformation η : I -> T from Tom Crockett's definition can be written this way for any Monad instance t:

return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)

Third clause: The composition of two functors in Haskell can be defined this way (which also comes with the Platform):

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)

So the natural transformation μ : T × T -> T from Tom Crockett's definition can be written like this:

join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)

The statement that this is a monoid in the category of endofunctors then means that Compose (partially applied to just its first two parameters) is associative, and that Identity is its identity element. I.e., that the following isomorphisms hold:

  • Compose f (Compose g h) ~= Compose (Compose f g) h
  • Compose f Identity ~= f
  • Compose Identity g ~= g

These are very easy to prove because Compose and Identity are both defined as newtype, and the Haskell Reports define the semantics of newtype as an isomorphism between the type being defined and the type of the argument to the newtype's data constructor. So for example, let's prove 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.
苍景流年 2024-10-03 03:14:11

这里的答案在定义幺半群和单子方面做得很好,但是,它们似乎仍然没有回答这个问题:

还有一个不太重要的问题,这是真的吗?如果是的话,您能否给出解释(希望没有太多 Haskell 经验的人能够理解)?

这里缺少的问题的关键是“幺半群”的不同概念,更准确地说是所谓的分类——幺半群在幺半群范畴中的概念。遗憾的是 Mac Lane 的书本身 让人非常困惑

总而言之,X中的单子只是X的endofunctors类别中的幺半群,其乘积×被替换为由恒等endofunctor 设置的endofunctors 和单位。

主要困惑

为什么这会令人困惑?因为它没有定义什么是X的“endofunctors范畴中的幺半群”。相反,这句话建议将所有内函子集合内的幺半群与函子组合一起作为二元运算,将恒等函子作为幺半群单元。它工作得很好,并且将包含恒等函子并在函子组合下封闭的内函子的任何子集变成幺半群。

但这并不是正确的解释,本书当时未能明确说明这一点。 Monad f 是一个固定的endofunctor,而不是组合下封闭的endofunctor的子集。常见的构造是使用 f 通过获取所有 k 折叠组合的集合来生成一个幺半群 f^k = f f 的 (f(...)) 与其自身,包括对应于身份 f^0 = idk=0代码>.现在,所有 k>=0 的所有这些幂的集合 S 确实是一个幺半群,“乘积 × 替换为 endofunctor 的组合,单位由恒等 endofunctor 设置” 。

然而:

  • 这个幺半群S可以为任何函子f定义,甚至可以为X的任何自映射定义。它是由f生成的幺半群。
  • 由函子组合和恒等函子给出的 S 幺半群结构与 f 是否是单子无关。

更令人困惑的是,“幺半群类别中的幺半群”的定义在本书后面出现,您可以从 目录。然而,理解这个概念对于理解与单子的联系绝对至关重要。

(严格)幺半群范畴

转到关于幺半群的第七章(比关于单子的第六章晚),我们发现所谓的严格幺半群范畴的定义为三重(B, * , e),其中B是类别,*: B x B-> B 是一个 bifunctor(相对于每个组件的函子,其他组件固定),eB 中的一个单位对象,满足结合律和单位定律:

(a * b) * c = a * (b * c)
a * e = e * a = a

对于 B 的任何对象 a,b,c,以及对于任何态射 a,b,c 具有相同的恒等式将e 替换为id_e,即e 的恒等态射。现在观察一下,在我们感兴趣的例子中,其中 BX 的内函子类别,自然变换为态射,*函子复合和e恒等函子,所有这些定律都满足,可以直接验证。

书中接下来是“宽松”幺半群范畴的定义,其中定律仅对某些满足所谓相干关系的固定自然变换进行取模,即然而对于我们的内函子类别的例子来说并不重要。

最后,在第七章第 3 节“幺半群”中,给出了实际的定义

幺半群范畴 (B, *, e) 中的幺半群 c 是带有两个箭头(态射)的 B 对象 < /p>

mu: c * c -> c
nu: e -> c

使 3 个图可交换。回想一下,在我们的例子中,这些是 endofunctors 类别中的态射,它们是与 monad 的 joinreturn 精确对应的自然变换。当我们使组合 * 更加明确时,用 c^2 替换 c * c,其中 c< /code> 是我们的 monad。

最后,请注意,3 个交换图(在幺半群范畴中的幺半群定义中)是为一般(非严格)幺半群范畴编写的,而在我们的例子中,作为幺半群范畴的一部分而出现的所有自然变换实际上都是恒等式。这将使图表与单子定义中的图表完全相同,从而使对应关系完整。

结论

总之,任何 monad 根据定义都是一个 endofunctor,因此是 endofunctor 类别中的对象,其中 monadic joinreturn 运算符满足 a 的定义特定(严格)幺半群类别中的幺半群。反之亦然,根据定义,内函子幺半群类别中的任何幺半群都是由一个对象和两个箭头组成的三元组(c, mu, nu),例如我们例子中的自然变换,满足与一个单子。

最后,请注意(经典)幺半群和幺半群类别中更一般的幺半群之间的主要区别。上面的两个箭头 munu 不再是二元运算和集合中的单位。相反,您有一个固定的 endofunctor c。尽管书中有令人困惑的评论,但函子组合 * 和恒等函子本身并不能提供 monad 所需的完整结构。

另一种方法是与集合A的所有自映射的标准幺半群C进行比较,其中二元运算是组合,可以看出映射标准笛卡尔积 C x C 转换为 C。传递到分类幺半群,我们将笛卡尔积 x 替换为函子组合 *,并将二元运算替换为自然变换 mu > 来自
join 运算符的集合

join: c(c(T))->c(T)

c * cc,这是每个对象 T 的 (在编程中键入)。经典幺半群中的恒等元素可以用固定单点集中的地图图像来识别,它被return运算符的集合所取代。

return: T->c(T) 

但是现在不再有笛卡尔积了,所以没有元素对,因此没有二元运算。

The answers here do an excellent job in defining both monoids and monads, however, they still don't seem to answer the question:

And on a less important note, is this true and if so could you give an explanation (hopefully one that can be understood by someone who doesn't have much Haskell experience)?

The crux of the matter that is missing here, is the different notion of "monoid", the so-called categorification more precisely -- the one of monoid in a monoidal category. Sadly Mac Lane's book itself makes it very confusing:

All told, a monad in X is just a monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor.

Main confusion

Why is this confusing? Because it does not define what is "monoid in the category of endofunctors" of X. Instead, this sentence suggests taking a monoid inside the set of all endofunctors together with the functor composition as binary operation and the identity functor as a monoidal unit. Which works perfectly fine and turns into a monoid any subset of endofunctors that contains the identity functor and is closed under functor composition.

Yet this is not the correct interpretation, which the book fails to make clear at that stage. A Monad f is a fixed endofunctor, not a subset of endofunctors closed under composition. A common construction is to use f to generate a monoid by taking the set of all k-fold compositions f^k = f(f(...)) of f with itself, including k=0 that corresponds to the identity f^0 = id. And now the set S of all these powers for all k>=0 is indeed a monoid "with product × replaced by composition of endofunctors and unit set by the identity endofunctor".

And yet:

  • This monoid S can be defined for any functor f or even literally for any self-map of X. It is the monoid generated by f.
  • The monoidal structure of S given by the functor composition and the identity functor has nothing do with f being or not being a monad.

And to make things more confusing, the definition of "monoid in monoidal category" comes later in the book as you can see from the table of contents. And yet understanding this notion is absolutely critical to understanding the connection with monads.

(Strict) monoidal categories

Going to Chapter VII on Monoids (which comes later than Chapter VI on Monads), we find the definition of the so-called strict monoidal category as triple (B, *, e), where B is a category, *: B x B-> B a bifunctor (functor with respect to each component with other component fixed) and e is a unit object in B, satisfying the associativity and unit laws:

(a * b) * c = a * (b * c)
a * e = e * a = a

for any objects a,b,c of B, and the same identities for any morphisms a,b,c with e replaced by id_e, the identity morphism of e. It is now instructive to observe that in our case of interest, where B is the category of endofunctors of X with natural transformations as morphisms, * the functor composition and e the identity functor, all these laws are satisfied, as can be directly verified.

What comes after in the book is the definition of the "relaxed" monoidal category, where the laws only hold modulo some fixed natural transformations satisfying so-called coherence relations, which is however not important for our cases of the endofunctor categories.

Monoids in monoidal categories

Finally, in section 3 "Monoids" of Chapter VII, the actual definition is given:

A monoid c in a monoidal category (B, *, e) is an object of B with two arrows (morphisms)

mu: c * c -> c
nu: e -> c

making 3 diagrams commutative. Recall that in our case, these are morphisms in the category of endofunctors, which are natural transformations corresponding to precisely join and return for a monad. The connection becomes even clearer when we make the composition * more explicit, replacing c * c by c^2, where c is our monad.

Finally, notice that the 3 commutative diagrams (in the definition of a monoid in monoidal category) are written for general (non-strict) monoidal categories, while in our case all natural transformations arising as part of the monoidal category are actually identities. That will make the diagrams exactly the same as the ones in the definition of a monad, making the correspondence complete.

Conclusion

In summary, any monad is by definition an endofunctor, hence an object in the category of endofunctors, where the monadic join and return operators satisfy the definition of a monoid in that particular (strict) monoidal category. Vice versa, any monoid in the monoidal category of endofunctors is by definition a triple (c, mu, nu) consisting of an object and two arrows, e.g. natural transformations in our case, satisfying the same laws as a monad.

Finally, note the key difference between the (classical) monoids and the more general monoids in monoidal categories. The two arrows mu and nu above are not anymore a binary operation and a unit in a set. Instead, you have one fixed endofunctor c. The functor composition * and the identity functor alone do not provide the complete structure needed for the monad, despite that confusing remark in the book.

Another approach would be to compare with the standard monoid C of all self-maps of a set A, where the binary operation is the composition, that can be seen to map the standard cartesian product C x C into C. Passing to the categorified monoid, we are replacing the cartesian product x with the functor composition *, and the binary operation gets replaced with the natural transformation mu from
c * c to c, that is a collection of the join operators

join: c(c(T))->c(T)

for every object T (type in programming). And the identity elements in classical monoids, which can be identified with images of maps from a fixed one-point-set, get replaced with the collection of the return operators

return: T->c(T) 

But now there are no more cartesian products, so no pairs of elements and thus no binary operations.

半窗疏影 2024-10-03 03:14:11

我写这篇文章是为了更好地理解 Mac Lane 的工作数学家的范畴论中臭名昭著的引文的推论。

在描述某事物是什么时,描述它不是什么通常同样有用。

Mac Lane 使用该描述来描述 Monad 的事实可能意味着它描述了 monad 所独有的东西。耐心听我说。为了对这个陈述有更广泛的理解,我认为需要明确的是,他并不是在描述单子所独有的东西;而是在描述单子所特有的东西。该声明同样描述了 Applicative 和 Arrows 等。出于同样的原因,我们可以在 Int 上有两个幺半群(Sum 和 Product),我们可以在 X 上的内函子类别中拥有多个幺半群。但两者还有更多相似之处。

Monad 和 Applicative 都满足标准:

  • endo =>任何在同一位置开始和结束的箭头或态射
  • functor =>两个类别之间的任何箭头或态射

    (例如,在日常Tree a -> List b中,但在Category Tree -> List中)< /p>

  • monoid =>单个对象;即,单一类型,但在这种情况下,仅涉及外部层;所以,我们不能有Tree ->列表,仅列表->列表

该语句使用“类别...”这定义了该语句的范围。例如,函子类别描述了 f * -> 的范围。 g *,即任何函子 ->任何函子,例如,Tree * ->列表 *树 * ->树*

分类语句未指定的内容描述了允许任何事物

在这种情况下,在函子内部,* -> * 又名 a -> b 未指定,这意味着 Anything ->任何东西,包括任何其他东西。当我的想象力跳到 Int -> String,还包括Integer ->也许是 Int,甚至是 也许是 Double -> String Int where a :: Maybe Double; b :: 要么是 String Int

因此该语句组合如下:

  • functorscope :: fa -> g b (即任何参数化类型到任何参数化类型)
  • endo + functor :: fa -> f b (即,任何一个参数化类型到相同的参数化类型)...换句话说,
  • endofunctor 类别中的幺半群

那么,这个构造的威力在哪里?为了理解完整的动态,我需要看到幺半群的典型绘图(带有单位箭头的单个对象,:: single object -> single object)未能说明这一点我可以使用由 Monoid 中允许的 one 类型对象中任意数量的 monoid 值参数化的箭头。等价的 endo, ~ 恒等箭头定义忽略函子的类型值以及最内部“有效负载”层的类型和值。因此,在函子类型匹配的任何情况下,等价都会返回 true(例如,Nothing -> Just * -> Nothing 相当于 Just * -> ; 只是 * -> 只是 * 因为它们都是也许 -> 也许)。

侧边栏:~outside 是概念性的,但它是 f a 中最左边的符号。它还描述了“Haskell”首先读入的内容(大图);因此,类型相对于类型值而言是“外部”的。编程中各层(引用链)之间的关系不容易在类别中关联起来。 Set 的类别用于描述类型(Int、Strings、Maybe Int 等),其中包括 Functor 的类别(参数化类型)。引用链:函子类型、函子值(函子集合的元素,例如 Nothing、Just),以及每个函子值指向的其他所有内容。在类别中,关系的描述不同,例如,return :: a -> m a 被认为是从一个 Functor 到另一个 Functor 的自然转换,与迄今为止提到的任何内容都不同。

回到主线程,总而言之,对于任何定义的张量积和中性值,该语句最终描述了一个源自其悖论结构的极其强大的计算构造:

  • 在外部,它显示为单个对象(例如,:: 列表);静态
  • 但内部允许大量动态
    • 任意数量的相同类型的值(例如,Empty | ~NonEmpty)作为任意数量函数的素材。张量积会将任意数量的输入减少为单个值...对于外部层(~fold 未提及有效负载)
    • 最内层的类型和值的范围无限

在 Haskell 中,澄清该语句的适用性非常重要。这种结构的力量和多功能性与单子本身完全无关。换句话说,该构造不依赖于单子的独特性。

当试图弄清楚是否使用共享上下文构建代码来支持相互依赖的计算,而不是可以并行运行的计算时,这个臭名昭著的陈述,尽管它描述了很多,并不是以下选择之间的对比应用性、箭头和单子,而是对它们有多少相同的描述。对于手头的决定,该声明没有实际意义。

这常常被误解。该语句继续描述 join :: m (ma) -> m a 作为幺半群内函子的张量积。然而,它没有阐明在该声明的上下文中如何选择(<*>)。这确实是“六合一,六合一”的例子。组合值的逻辑完全相同;相同的输入会生成相同的输出(与 Int 的 Sum 和 Product monoids 不同,因为它们在组合 Int 时生成不同的结果)。

因此,回顾一下:endofunctors 类别中的幺半群描述:

 ~t :: m * -> m * -> m *
 and a neutral value for m *
    

(<*>)(>>=) 都提供对两者的同时访问m 值以计算单个返回值。用于计算返回值的逻辑完全相同。如果不是因为它们参数化的函数形状不同(f :: a -> bk :: a -> m b)以及具有相同计算返回类型的参数(即分别为 a -> b -> bb -> a -> b) ,我怀疑我们可以参数化幺半群逻辑,即张量积,以便在两个定义中重用。作为阐明这一点的练习,尝试并实现 ~t,最终得到 (<*>)(>>= ) 取决于您决定如何为所有 a b 定义它。

如果我的最后一点至少在概念上是正确的,那么它就解释了 Applicative 和 Monad 之间精确且唯一的计算差异:它们参数化的函数。换句话说,差异是这些类型类的实现的外部

总之,根据我自己的经验,Mac Lane 臭名昭著的引言提供了一个很棒的“goto”模因,它是我在浏览类别时可以参考的指南,以更好地理解 Haskell 中使用的惯用语。它成功地捕获了 Haskell 中可以轻松访问的强大计算能力的范围。

然而,具有讽刺意味的是,我第一次误解了该声明在 monad 之外的适用性,以及我希望在这里传达的内容。它所描述的一切都被证明是 Applicative 和 Monad(以及 Arrows 等)之间的相似之处。它没有说的恰恰是它们之间微小但有用的区别。

I came to this post by way of better understanding the inference of the infamous quote from Mac Lane's Category Theory For the Working Mathematician.

In describing what something is, it's often equally useful to describe what it's not.

The fact that Mac Lane uses the description to describe a Monad, one might imply that it describes something unique to monads. Bear with me. To develop a broader understanding of the statement, I believe it needs to be made clear that he is not describing something that is unique to monads; the statement equally describes Applicative and Arrows among others. For the same reason we can have two monoids on Int (Sum and Product), we can have several monoids on X in the category of endofunctors. But there is even more to the similarities.

Both Monad and Applicative meet the criteria:

  • endo => any arrow, or morphism that starts and ends in the same place
  • functor => any arrow, or morphism between two Categories

    (e.g., in day to day Tree a -> List b, but in Category Tree -> List)

  • monoid => single object; i.e., a single type, but in this context, only in regards to the external layer; so, we can't have Tree -> List, only List -> List.

The statement uses "Category of..." This defines the scope of the statement. As an example, the Functor Category describes the scope of f * -> g *, i.e., Any functor -> Any functor, e.g., Tree * -> List * or Tree * -> Tree *.

What a Categorical statement does not specify describes where anything and everything is permitted.

In this case, inside the functors, * -> * aka a -> b is not specified which means Anything -> Anything including Anything else. As my imagination jumps to Int -> String, it also includes Integer -> Maybe Int, or even Maybe Double -> Either String Int where a :: Maybe Double; b :: Either String Int.

So the statement comes together as follows:

  • functor scope :: f a -> g b (i.e., any parameterized type to any parameterized type)
  • endo + functor :: f a -> f b (i.e., any one parameterized type to the same parameterized type) ... said differently,
  • a monoid in the category of endofunctor

So, where is the power of this construct? To appreciate the full dynamics, I needed to see that the typical drawings of a monoid (single object with what looks like an identity arrow, :: single object -> single object), fails to illustrate that I'm permitted to use an arrow parameterized with any number of monoid values, from the one type object permitted in Monoid. The endo, ~ identity arrow definition of equivalence ignores the functor's type value and both the type and value of the most inner, "payload" layer. Thus, equivalence returns true in any situation where the functorial types match (e.g., Nothing -> Just * -> Nothing is equivalent to Just * -> Just * -> Just * because they are both Maybe -> Maybe -> Maybe).

Sidebar: ~ outside is conceptual, but is the left most symbol in f a. It also describes what "Haskell" reads-in first (big picture); so Type is "outside" in relation to a Type Value. The relationship between layers (a chain of references) in programming is not easy to relate in Category. The Category of Set is used to describe Types (Int, Strings, Maybe Int etc.) which includes the Category of Functor (parameterized Types). The reference chain: Functor Type, Functor values (elements of that Functor's set, e.g., Nothing, Just), and in turn, everything else each functor value points to. In Category the relationship is described differently, e.g., return :: a -> m a is considered a natural transformation from one Functor to another Functor, different from anything mentioned thus far.

Back to the main thread, all in all, for any defined tensor product and a neutral value, the statement ends up describing an amazingly powerful computational construct born from its paradoxical structure:

  • on the outside it appears as a single object (e.g., :: List); static
  • but inside, permits a lot of dynamics
    • any number of values of the same type (e.g., Empty | ~NonEmpty) as fodder to functions of any arity. The tensor product will reduce any number of inputs to a single value... for the external layer (~fold that says nothing about the payload)
    • infinite range of both the type and values for the inner most layer

In Haskell, clarifying the applicability of the statement is important. The power and versatility of this construct, has absolutely nothing to do with a monad per se. In other words, the construct does not rely on what makes a monad unique.

When trying to figure out whether to build code with a shared context to support computations that depend on each other, versus computations that can be run in parallel, this infamous statement, with as much as it describes, is not a contrast between the choice of Applicative, Arrows and Monads, but rather is a description of how much they are the same. For the decision at hand, the statement is moot.

This is often misunderstood. The statement goes on to describe join :: m (m a) -> m a as the tensor product for the monoidal endofunctor. However, it does not articulate how, in the context of this statement, (<*>) could also have also been chosen. It truly is an example of 'six in one, half a dozen in the other'. The logic for combining values are exactly alike; same input generates the same output from each (unlike the Sum and Product monoids for Int because they generate different results when combining Ints).

So, to recap: A monoid in the category of endofunctors describes:

 ~t :: m * -> m * -> m *
 and a neutral value for m *
    

(<*>) and (>>=) both provide simultaneous access to the two m values in order to compute the the single return value. The logic used to compute the return value is exactly the same. If it were not for the different shapes of the functions they parameterize (f :: a -> b versus k :: a -> m b) and the position of the parameter with the same return type of the computation (i.e., a -> b -> b versus b -> a -> b for each respectively), I suspect we could have parameterized the monoidal logic, the tensor product, for reuse in both definitions. As an exercise to make the point, try and implement ~t, and you end up with (<*>) and (>>=) depending on how you decide to define it forall a b.

If my last point is at minimum conceptually true, it then explains the precise, and only computational difference between Applicative and Monad: the functions they parameterize. In other words, the difference is external to the implementation of these type classes.

In conclusion, in my own experience, Mac Lane's infamous quote provided a great "goto" meme, a guidepost for me to reference while navigating my way through Category to better understand the idioms used in Haskell. It succeeds at capturing the scope of a powerful computing capacity made wonderfully accessible in Haskell.

However, there is irony in how I first misunderstood the statement's applicability outside of the monad, and what I hope conveyed here. Everything that it describes turns out to be what is similar between Applicative and Monads (and Arrows among others). What it doesn't say is precisely the small but useful distinction between them.

谈下烟灰 2024-10-03 03:14:11

注意:不,这不是真的。在某个时候,丹·皮波尼本人对这个答案发表了评论,说这里的因果关系完全相反,他写这篇文章是为了回应詹姆斯·艾里的俏皮话。但它似乎已经被删除了,也许是被一些强迫性的整理者删除了。

以下是我原来的回答。


Iry 很可能读过 从 Monoid 到 Monads,一篇文章,其中 Dan Piponi (sigfpe) 从 Haskell 中的幺半群派生出 monad,其中对范畴论进行了大量讨论,并明确提到了“Hask" 。无论如何,任何想知道单子作为内函子类别中的幺半群意味着什么的人可能会从阅读这个推导中受益。

Note: No, this isn't true. At some point there was a comment on this answer from Dan Piponi himself saying that the cause and effect here was exactly the opposite, that he wrote his article in response to James Iry's quip. But it seems to have been removed, perhaps by some compulsive tidier.

Below is my original answer.


It's quite possible that Iry had read From Monoids to Monads, a post in which Dan Piponi (sigfpe) derives monads from monoids in Haskell, with much discussion of category theory and explicit mention of "the category of endofunctors on Hask" . In any case, anyone who wonders what it means for a monad to be a monoid in the category of endofunctors might benefit from reading this derivation.

稚然 2024-10-03 03:14:11

这是另一个使用 monad IO 的显式示例。

可能的混淆1:A Monad 不是 Monoid

在 Haskell 文档中 在上面的 Monoid 中,你可以看到源代码是

class Semigroup a => Monoid a where
    ⟨additional conditions⟩

这样写的:“Define Monoid to be a typeclass: a type a is a 幺半群如果它是半群和⟨附加条件⟩”。

您在 Monad。因为 Monad 不是 Haskell 幺半群。

为什么? “monoid”这个词有 2 个含义:

Haskell 中的类型类 Monoid 是抽象代数意义上的幺半群。

说明性图

特别是,

  • 单子由一组对象组成。 (IO 当然不包含像 那样的内容。)
  • monad 的二元运算不会接受两个对象并返回一个。 (+ 接受两个整数并返回一个,但是 join 不接受两个任何东西 --- IO 内部没有任何东西可以拿!)

可能的混淆2:monad(category_theory)不是Haskell Monad

不同概念之间的关系:

  1. 抽象代数中的幺半群是抽象代数中的幺半群(范畴论) >集合类别

    这是一个等价——集合范畴中的幺半群是抽象代数中的幺半群。

  2. 单子是内函子类别中的幺半群(范畴论)。

    这也是等价的。

  3. Haskell 中的 Monad 是一个 monad。例如,IO 是一个 monad。

    不是等价!并非所有 monad 都是 Haskell monad。

范畴论中的幺半群是什么?

需要澄清的是,这里滥用了术语。当我们说

是一个环

是我们实际上指的

(ℤ, +, 0, ×, 1) 是一个环

当然,使用哪些运算符通常是隐式的。

同样,当我们说

是一个幺半群

我们所说的

幺半群

(ℕ, +, 0) 是一个幺半群。

另一方面,(ℕ, ×, 1) 也是一个幺半群。

当我们说

X 是类别 Y

中的幺半群

这里同样存在符号滥用。它实际上意味着

(X, μ, η) 是类别 (Y, *, e)

中的幺半群

,或者

X 是类别 Y 中的对象,且 (X, μ, η) 满足幺半群公理

或, 更短,

X 是类别 Y 中关于 (μ, η, *, e)幺半群对象代码>.

(我将在这里使用 η (eta)。其他一些答案使用 ν (nu)。)

示例

考虑上面的 示例。我们想说

(ℕ, +, ⟨• → 0⟩) 是类别 (Sets, ×, {•}) 中的幺半群。

您可以检查事物的类型:

  • 是集合类别中的对象。
  • + 是从 ℕ × ℕ 的集合的态射。
  • ⟨• → 0⟩ 是从单例集 {•} 的集合态射,发送唯一的元素 0
  • × 是一个 Sets2 → Sets 函子,ℕ × ℕSets,
  • {•} 是类别 Sets 中的单例集。

那么什么是 Haskell monad?

Hask 类别是其中

  • 对象是 Haskell 中的类型(例如 StringInteger 等)的类别,
  • 态射是 Haskell 函数(例如(+ 1) :: Integer -> Integerread :: String -> Integer)。

对于任何两个类别 AB,我们都有函子类别,这里表示为 Fun(A, B),其中

  • 对象是函子在 AB 之间,
  • 态射是两个这样的函子之间的自然变换。

(如果您不知道函子和自然变换是什么,请阅读它们。)

具体来说,如果 A = B,则类别 Fun(A, A) code> 是Aendofunctors 类别。

将这些拼凑在一起,

Haskell Monad 是幺半群范畴 (Fun(Hask, Hask), Compose, Identity) 中的幺半群(范畴论)。

这里,

  • Compose 是一个 Fun(Hask, Hask)2 → Fun(Hask, Hask) 函子,Compose IO IO是将 String 发送到 IO (IO String) 的 endofunctor,
  • IdentityFun(Hask, Hask) 类别中的对象身份字符串等于字符串

例子

IO 是一个 Haskell Monad

这意味着:

  • IOFun(Hask, Hask) 类别中的对象代码>:

    • Hask 类别中的每个对象,IO 将其发送到另一个对象。

      例如,从String,您将获得IO String。 (请注意,String 是一种类型,但在 Hask 类别中,它是一个对象。)

    • 对于 Hask 类别中的每个态射,IO 将其发送到另一个态射。

      例如从 read :: String ->整数,它被映射到fmap read :: IO String -> IO 整数.

  • return 是类别 Fun(Hask, Hask) 中的态射。

    • 也就是说,它是从函子 Identity 到函子 IO 的自然转换。

      这是一些图表。

      “图”

  • join 是类别 Fun(Hask, Hask) 中的态射。

    • 也就是说,它是从函子 Compose IO IO 到函子 IO 的自然转换。

      (函子 Compose IO IO 是什么?它只接受 String 并返回 IO (IO String))。

  • ((IO, fmap), join, return) 满足幺半群公理。

这不是定义吗?

事实上,有两种等效的方法来定义 monad。

  • Haskell 方式:(IO, bind, return) --- 其中 bind 写为 >>=
  • 对应范畴论中幺半群的方式:(IO, fmap, join, return)

它们是可以互换的:

  • bind 可以通过 fmapjoin 实现,如下所示:

    -- a :: IO x 例如 getLine :: IO String
    --f::x-> IO y 例如 putStrLn :: String -> IO()
    -- 绑定 af::IO y 例如 getLine>>= putStrLn::IO ()
    绑定 af = 加入 (fmap fa)
    
  • joinfmap 可以通过 bind 实现,如下所示:

    -- x :: IO (IO y) 例如 fmap putStrLn getLine :: IO (IO String)
    -- 加入 x :: IO y
    连接 x = x >>= (\ioY -> ioY)
    --f::a->乙
    -- fmap f::IO a --> IOb
    fmap f ioA = ioA >>= (返回 .f)
    

    或者在 Haskell 语法糖中(do 符号去糖到 returnbind):

    加入 x = do
       ioY <- x -- ioY :: IO ()
       -- 如果你调用 `join (fmap putStrLn getLine)`
       -- 那么 `ioY` 的值为 `putStrLn "⟨the input⟩"`
       结果<-ioY
       返回结果
    fmap f ioA = do
       x <- ioA -- x :: a
       y = fx -- y :: ioB
       返回y
    

    可以缩短为

    加入 x = do
       ioY<-x
       IOY
    fmap f ioA = do
       x<-ioA
       返回效果
    

参考文献

Here is another take with an explicit example using the monad IO.

Possible confusion 1: A Monad is not a Monoid!

In the Haskell documentation for Monoid above, you can see the source code reads

class Semigroup a => Monoid a where
    ⟨additional conditions⟩

In English: "Define Monoid to be a typeclass: a type a is a Monoid if it is a Semigroup and ⟨additional conditions⟩".

You don't quite see the same thing in Monad. Because a Monad is not a Haskell monoid.

Why? The word "monoid" has 2 meanings:

The typeclass Monoid in Haskell is a monoid in the abstract algebra sense.

an illustrative diagram

In particular,

  • a monad does not consist of a set of objects. (IO certainly don't contain anything like does.)
  • the binary operation of a monad does not take in two objects and return one. (+ takes in two integers and return one, but join does not take in two anything --- there isn't anything inside IO to take!)

Possible confusion 2: a monad (category_theory) is not a Haskell Monad

The relationship between the different concepts:

  1. A monoid in abstract algebra is a monoid (category theory) in the category of sets.

    This is an equivalence --- a monoid in the category of sets is a monoid in abstract algebra.

  2. A monad is a monoid (category theory) in the category of endofunctors.

    This is also an equivalence.

  3. A Monad in Haskell is a monad. For example, IO is a monad.

    This is not an equivalence! Not all monads are Haskell monads.

What is a monoid in category theory?

For clarification, there's an abuse of terminology here. When we say

is a ring

we actually means

(ℤ, +, 0, ×, 1) is a ring

Of course, which operators are being used are often implicit.

Similarly, when we say

is a monoid

we means

(ℕ, +, 0) is a monoid.

On the other hand, (ℕ, ×, 1) is also a monoid.

When we say

X is a monoid in the category Y

there is similarly an abuse of notation here. It actually means

(X, μ, η) is a monoid in the category (Y, *, e)

or

X is an object in the category Y, and (X, μ, η) satisfies the monoid axioms

or, shorter,

X is a monoidal object in the category Y with respect to (μ, η, *, e).

(I'll use η (eta) here. Some other answers use ν (nu).)

Example

Consider the example of above. We wants to say

(ℕ, +, ⟨• → 0⟩) is a monoid in the category (Sets, ×, {•}).

You can check the types of things:

  • is an object in the category of sets.
  • + is a morphism of sets from ℕ × ℕ to .
  • ⟨• → 0⟩ is a morphism of sets from the singleton set {•} to , that sends the only element to 0.
  • × is a Sets2 → Sets functor, ℕ × ℕ is an object in Sets,
  • {•} is a singleton set in the category Sets.

So what is a Haskell monad?

The Hask category is the category where

  • the objects are the types in Haskell (e.g. String, Integer, etc.),
  • the morphisms are Haskell functions (e.g. (+ 1) :: Integer -> Integer, read :: String -> Integer).

For any two categories A and B, we have the category of functors, here denoted Fun(A, B) where

  • the objects are the functors between A and B,
  • the morphisms are the natural transformations between two such functors.

(If you don't know what a functor and a natural transformations are, read up on them.)

Specifically, if A = B, then the category Fun(A, A) is the category of endofunctors of A.

Piecing these together,

A Haskell Monad is a monoid (category theory) in the monoidal category (Fun(Hask, Hask), Compose, Identity).

Here,

  • Compose is a Fun(Hask, Hask)2 → Fun(Hask, Hask) functor, Compose IO IO is the endofunctor that sends String to IO (IO String),
  • Identity is an object in the category Fun(Hask, Hask), Identity String is equal to String.

Example

IO is a Haskell Monad

which means:

  • IO is an object in the category Fun(Hask, Hask):

    • from each object in the category Hask, IO sends it to another object.

      e.g. from String, you get IO String. (Note that String is a type, but in the category Hask it is an object.)

    • from each morphism in the category Hask, IO sends it to another morphism.

      e.g. from read :: String -> Integer, it gets mapped to fmap read :: IO String -> IO Integer.

  • return is a morphism in the category Fun(Hask, Hask).

    • That is, it is a natural transformation from the functor Identity to the functor IO.

      Here are some diagrams.

      diagram

  • join is a morphism in the category Fun(Hask, Hask).

    • That is, it is a natural transformation from the functor Compose IO IO to the functor IO.

      (What is the functor Compose IO IO? It just takes in e.g. String and returns IO (IO String)).

  • ((IO, fmap), join, return) satisfies the monoidal axioms.

That's not the definition?

Indeed, there are two equivalent ways to define a monad.

  • The Haskell way: (IO, bind, return) --- where bind is written as >>=.
  • The way that corresponds to a monoid in category theory: (IO, fmap, join, return).

They're interchangeable:

  • bind can be implemented with fmap and join as follows:

    -- a :: IO x          e.g. getLine :: IO String
    -- f :: x -> IO y     e.g. putStrLn :: String -> IO ()
    -- bind a f :: IO y   e.g. getLine >>= putStrLn :: IO ()
    bind a f = join (fmap f a)
    
  • join and fmap can be implemented with bind as follows:

    -- x :: IO (IO y)     e.g. fmap putStrLn getLine :: IO (IO String)
    -- join x :: IO y
    join x = x >>= (\ioY -> ioY)
    -- f :: a -> b
    -- fmap f :: IO a -> IO b
    fmap f ioA = ioA >>= (return . f)
    

    Or in Haskell syntactic sugar (the do notation desugar to return and bind):

    join x = do
       ioY <- x  -- ioY :: IO ()
       -- if you call `join (fmap putStrLn getLine)`
       -- then `ioY` has the value `putStrLn "⟨the input⟩"` here
       result <- ioY
       return result
    fmap f ioA = do
       x <- ioA  -- x :: a
       y = f x  -- y :: ioB
       return y
    

    which can be shortened to

    join x = do
       ioY <- x
       ioY
    fmap f ioA = do
       x <- ioA
       return f x
    

References

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文