Scala(Scala)的Monad进一步消灭:一种范畴论方法

Some of you may have read my article on monads. This time I want to talk about monads from a different, more theoretical point of view. It is not mandatory to read the previous article first (in case you haven’t), but if you don’t know anything about the topic, I guess it wouldn’t hurt to become familiar with it before jumping on this one.

你们中有些人可能已经读过我有关单子的文章 。 这次,我想从不同的,更理论的角度来谈论单子。 不一定要先阅读上一篇文章(以防万一),但是如果您对该主题一无所知,那么我想在跳到这篇文章之前熟悉它不会有任何伤害。

Let’s start. Some of you may have heard the popular quote:

开始吧。 你们中有些人可能听到过这样的名言:

A monad is just a monoid in the category of endofunctors, what’s the problem?
一个monad只是endofunctors类别中的一个monoid,这是什么问题?

Original statement is from Categories for the Working Mathematician by Saunders Mac Lane, but it has been quoted and rephrased numerous times. I took this statement as a reference point; a teaching objective, if you will. So I will attempt to explain monads from the category theory point of view by explaining that particular sentence.

原始声明摘自Saunders Mac Lane 的《工作数学家类别》 ,但已被多次引用和措辞。 我以这句话为参考。 教学目标,如果可以的话。 因此,我将通过解释该特定句子来尝试从范畴论的角度解释单子。

Important note: A lot of stuff written here is simplified and may not be 100% mathematically accurate. Category theory is a very complex field of mathematics (sometimes even jokingly referred to as “abstract nonsense”). This is not a math article. This is simply my attempt of creating an introductory, easy-to-follow text that should serve as a starting point to get you all excited about category theory and continue the exploration on your own.

重要说明:此处编写的许多内容均经过简化,在数学上可能并非100%准确。 范畴论是一个非常复杂的数学领域(有时甚至被戏称为“ 抽象废话 ”)。 这不是数学文章。 这仅仅是我尝试创建一个入门,易于理解的文本,应该以此为起点,让大家对类别理论感到兴奋,并继续自己进行探索。

代数结构 (Algebraic structures)

What is a category? A category is an algebraic structure. OK, but what is an algebraic structure? Algebraic structure is basically a set (when used in this context of being an algebraic structure’s set, it is commonly referred to as underlying set) of one or more elements with one or more operations on them.

什么是类别? 范畴是代数结构 。 好的,但是什么是代数结构? 代数结构基本上是一个或多个对它们进行一个或多个运算的 元素集合 (当在本文中用作代数结构的集合时,通常称为基础 集合 )。

There are two main things that differentiate algebraic structures from one another:

代数结构彼此之间有两个主要区别:

  • laws that need to hold for operations on set elements

    需要对集合元素进行操作的法律
  • existence of identity element

    身份元素的存在

Here are some laws that appear across different algebraic structures (operation in an algebraic structure is denoted as ○):

以下是在不同的代数结构中出现的一些定律(代数结构中的运算表示为○):

  • Closure: For all a, b in algebraic structure M, the result of operation ab is also in M

    闭包 :对于代数结构M中的所有ab ,运算ab的结果也在M中

  • Invertibility: For each element a in M there is an element b where ab = ba = e , where e is the identity

    可逆性 :对于M中的每个元素a ,都有一个元素b ,其中ab = ba = e ,其中e是恒等式

  • Commutativity: for all a, b in M: a ○ b = b ○ a

    可交换性 :对于M中的所有a,ba○b = b○a

  • Associativity: for all a, b, c in M: (a ○ b) ○ c = a ○ (b ○ c)

    关联性 :对于M中的所有a,b,c :(a○b)○c = a○(b○c)

Identity element is an element which (quoting the wiki) “leaves other elements unchanged when combined with them”. It’s the element that doesn’t affect the result when used in operation ○. We can formulate that into a law too:

标识元素是这样的元素(引用wiki )“与其他元素结合时保留其他元素不变”。 在运算○中使用时,不会影响结果的元素。 我们也可以将其制定为法律:

  • Identity: for any a in M there is an element e where e a = a e = a

    身份 :对于M中的任何a ,都有一个元素e ,其中ea = ae = a

So, starting from a simple set with one or more operations and by adding a certain combination of the laws that are required, we can end up with various algebraic structures. Here are some:

因此,从具有一个或多个运算的简单集合开始,通过添加所需定律的某种组合,我们可以得到各种代数结构。 这里有一些:

类别 (Category)

Category is an algebraic structure that consists of objects with defined mappings between some of them. Those mappings are called morphisms or arrows. Arrows go from one object to another. Some pairs of objects have an arrow between them defined, some don’t.

类别是一种代数结构,它由在某些对象之间具有定义映射的对象组成。 这些映射称为射态射影 。 箭头从一个物体到另一个物体。 有些对象对之间定义了一个箭头,有些则没有。

Categories have an important feature: if there is some arrow f: A → B and an arrow g: B → C, there is also automatically a composition of arrows f and g which forms an arrow A → C. We write the composition as g ○ f. Recall that algebraic structure is defined as a set with some operation(s) on its elements; in case of category, those elements are arrows and the operation is arrow composition.

类别具有一个重要的特征:如果有一些箭头f:A→B和箭头G:B→C,还有自动形成的箭头A→C。我们写出该组合物为g箭头fg组合物 ○f 。 回想一下,代数结构被定义为一个在其元素上进行一些运算的集合; 在类别的情况下,那些元素是箭头,而操作是箭头组成。

Categories are completely abstract and this is quite tricky to wrap your mind around. “Give me an example of a category”, you ask. OK, here’s one — Set. Set is the category of sets. Which sets? Does it include the set of integers, set of letters in english alphabet, set of all animals with four legs? Yes, it includes any and every set there is.

类别是完全抽象的,这使您难以置信。 你问:“给我一个类别的例子”。 好,这是一个-Set类别 。 哪套? 它是否包括整数集,英文字母中的字母集,所有具有四只脚的动物集? 是的,它包括其中的所有设置。

It’s not easy, but try not to think in completely concrete terms. Don’t try to materialise those sets. We don’t care how many sets are inside the category or which elements are inside those sets. We just care about the fact that objects in the Set category are sets and arrows are functions between sets that can be composed. I mean, that’s not all, there is a whole science behind category of sets with lots of axioms and laws. But we have all we need for now.

这并不容易,但是请不要以完全具体的方式思考。 不要尝试实现这些集合。 我们不在乎类别内有多少个集合或这些集合内的元素。 我们只关心Set类别中的对象是集合,而箭头是可以组合的集合之间的函数这一事实。 我的意思是,这还不是全部,在具有大量公理和定律的集合类别后面还有一整套科学 。 但是,我们现在拥有所有需要的东西。

Regarding our algebraic structure laws, category obeys two: identity and associativity. Since category is an algebraic structure with arrows as its elements and arrow composition as the operation between the elements, following those laws means that we have an identity arrow and that composition of arrows is associative.

关于我们的代数结构定律,范畴服从两个: 恒等式结合性 。 由于类别是一个以箭头为元素的代数结构,而箭头的构成为元素之间的运算,因此遵循这些定律意味着我们拥有一个恒等箭头,并且箭头的组成具有关联性。

Identity arrow is an arrow that goes from an object to itself. Composition of any arrow f with identity arrow e is arrow f:

身份箭头是从对象到其自身的箭头。 具有标识箭头e的任何箭头f的组成为箭头f:

e ○ f = f = f ○ e

e○f = f = f ○e

Given arrows f, g, h and their composition , the associativity law is:

给定箭头fgh及其组成 ,结合律是:

h ○ (g ○ f) = (h ○ g) ○ f

h○(g○f)=(h○g)○f

Functions in your favourite programming language are another example of a category. Objects in this category are types and morphisms are functions that take a type and return a type. For any type there exists a function that returns that same type (identity) and functions compose easily, obeying associativity law. Note that functions of two or more arguments can be curried and therefore viewed as functions of one argument that return a higher-order function.

您最喜欢的编程语言中的函数是该类别的另一个示例。 此类中的对象是类型,态射是采用类型并返回类型的函数。 对于任何类型,都有一个函数返回相同的类型(标识),并且函数遵循联合律容易地组成。 注意,两个或更多个参数的函数,可以咖喱 ,因此作为返回高阶函数一个参数的函数被观看。

That’s it for now regarding categories. Objects, arrows, composition, identity, associativity. Good. We’ll get back to them shortly.

就类别而言,仅此而已。 对象,箭头,组成,标识,关联性。 好。 我们将尽快与他们联系。

单体 (Monoid)

Getting warmer. What is a monoid?

逐渐回暖。 什么是monoid?

Monoid can be defined as a:

Monoid可以定义为:

- single set S - with an associative binary operation - and an identity element e- following two laws: (a ○ b) ○ c = a ○ (b ○ c) a ○ e = e ○ a = a

-单集S -与一个关联的二进制运算 -和身份要素e -下列两名法:(A○B)○C = A○(B○c) 一种○E =Ë○A = A

Note how similar this looks to a category. We will soon see what this means.

请注意,这与类别看起来有多相似。 我们将很快看到这意味着什么。

But first an example for a monoid. OK, so we need a set with an associative binary operation and with some identity element. We could take addition on the set of integers Z. Alright, we have an identity element (it’s zero) and we have the associativity, because (x + y) + z= x + (y + z). If you take another look at that table of laws for various algebraic structures, you’ll notice that addition in Z is in fact a group, because it also has an inverse (subtraction).

但首先是一个半身像的例子。 好的,因此我们需要一个具有关联二进制运算和某些标识元素的集合。 我们可以对整数Z进行加法运算。好的,我们有一个恒等式元素(它为零),并且具有关联性,因为(x + y)+ z = x +(y + z)。 如果您再次查看该表中各种代数结构的定律,您会注意到Z中的加法实际上是一个组,因为它也有一个逆数(减法)。

But here’s something cool and possibly a bit disturbing at the same time — monoid is actually a category, just a bit of a special one: it has only one object.

但是,这里有些很棒的东西,同时可能有点令人不安– id半身实际上是一个类别 ,只是一个特殊的类别它只有一个对象

If you scroll back to the table we saw earlier you’ll see that monoid is actually pretty similar to a category, only difference being the closure law. Recall that closure says “for all a, b in algebraic structure M, the result of operation ab is also in M”. It’s pretty logical we got the closure property in a monoid since we always start from and end up in the same object (the only one we have). Categories don’t have closure because not all arrows can necessarily be composed (e.g. you can’t compose a → b and c → d) so the property “combine any two elements and you’ll arrive at something that’s also a part of the set” doesn’t hold. Other two laws (identity and associativity) are the same.

如果您回滚到前面看到的表,您会发现monoid实际上与类别非常相似,唯一的不同是闭合法则。 回想一下,闭包表示“对于代数结构M中的所有ab ,运算ab的结果也在M中 ”。 这是很合乎逻辑的,因为我们总是从同一个对象开始并结束于同一个对象(唯一的对象),所以我们将其闭包属性设置为一个monoid。 类别没有闭包,因为并非所有箭头都必须组成(例如,您不能组成a→bc→d ),因此属性“将任何两个元素组合在一起,您将到达对象的一部分”设置”不成立。 其他两个定律(同一性和关联性)相同。

When switching the viewpoint from monoid as a set to monoid as a category, elements of the set become arrows of the category, identity element becomes the identity arrow, and binary operation becomes arrow composition. And yes, as soon as we enter the twilight zone also known as category theory, stuff gets weird. But with a bit of mindset shifting, our addition-in-Z example can be seen as a category.

当将视点从作为组monoid切换 作为类别的monoid时 ,该组的元素变为该类别的箭头,标识元素变为该标识箭头,而二元运算变为箭头组成。 是的,一旦我们进入也称为类别理论的暮光区,事情就会变得很奇怪。 但是随着思维方式的转变,我们的Z值添加示例可以视为一个类别。

First of all, numbers are now arrows. Think of an arrow as an “add X” function. These functions compose quite nicely, since adding 3 to a number and adding 5 to the result is the same as adding 8 to the original number. Furthermore, composing add3 with add5 followed by composing with add7 is the same as composing add3 with the composition of add5 and add7:

首先,数字现在是箭头。 将箭头视为“添加X”功能。 这些函数的组合非常好,因为将3加到一个数字上并将5加到该结果上与将8加到原始数字上相同。 此外,先将add3与add5组合,然后再与add7组合,这与将add3与add5和add7组成的组合相同:

(add3 ○ add5) ○ add7 = add3 ○ (add5 ○ add7)

(add3○add5)○add7 = add3○(add5○add7)

To cite a particular great source on category theory from which I took the adders example:

为了列举类别理论的一个特殊来源 ,我以加法器为例:

Instead of giving you the traditional rules of addition, I could as well give you the rules of composing adders, without any loss of information.
除了为您提供传统的加法规则外,我还可以为您提供组成加法器的规则,而不会造成任何信息丢失。

So, by shifting our viewpoint a little, we were able to see the monoid both as a set (on the left) and a single-object category (on the right).

因此,通过稍微改变视点,我们可以将单面体视为集合(在左侧)和单对象类别(在右侧)。

Also, all the laws that we had in the first case are also present in the second case. Nothing more, nothing less.

同样,我们在第一种情况下拥有的所有法律也都在第二种情况下存在。 仅此而已。

OK, so we no longer have numbers; we have add-arrows which represent single-parameter adding functions. Second operand of adding operation is “hardcoded” (operation is now unary, not binary) so we have one function for each natural number. Total count of natural numbers and total count of arrows in our category are the same. Finally, arrows all share the same starting and ending object.

好,所以我们不再有数字; 我们有代表单个参数加法函数的加法箭头。 加法运算的第二个操作数是“硬编码的”(运算现在是一元的,不是二进制的),因此每个自然数都有一个函数。 自然数的总计数与我们类别中的箭头的总计数相同。 最后,所有箭头共享相同的开始和结束对象。

This object is completely irrelevant; think of it as a massless blob of nothingness. If you want, we could rename arrows from “addX’ to simply “x” and then the whole thing becomes really similar to our normal good old set Z with addition, just instead of “adding two and two equals four” we would say “composing two and two equals four”.

这个对象是完全不相关的。 认为它是虚无的无质量斑点。 如果需要的话,我们可以将箭头从“ addX”重命名为“ x”,然后整个事情就变得与我们正常的旧集合Z真正相加,而不仅仅是“相加两个等于四个”,我们会说“组成两个和两个等于四”。

Here are the wikis for both viewpoints, perhaps you’ll find them useful:

以下是这两种观点的Wiki,也许您会发现它们很有用:

函子 (Functor)

Alright, now we know what algebraic structures are and how they are defined, and we also took a closer look into two of them — category and monoid. We saw that monoid is a category too, just with only one object.

好了,现在我们知道什么是代数结构以及如何定义它们,并且我们还仔细研究了其中的两个-类别和类半体。 我们看到,monoid也是一个类别,只有一个对象。

What we need now is a functor (by the way, I already wrote a bit about functors too).

我们现在需要的是一个函子 (顺便说一句,我也已经写了一些关于函子的文章 )。

Compared to monoids and categories, functor is a bit different. It is not really an algebraic structure; it’s more of a function that maps from algebraic structures to algebraic structures. But not just any algebraic structures: functor is a mapping between categories.

与monoid和类别相比,函子有所不同。 它不是真正的代数结构。 它更多的是从代数结构映射到代数结构的函数。 但不仅仅是任何代数结构:函子是类别之间映射。

Category, as we know by now, is a set of objects with arrows between them. Well, functor knows how to map the category to another category. It will map all the objects and all the arrows, and it will preserve the connections between objects (if there is an arrow between a and b in original category, there will be one between Fa and Fb in the resulting category). Given some object a or some arrow f: a → b from the original category, corresponding object (the one functor maps into) is denoted as F(a) and corresponding arrow is denoted as F(f): F(a) → F(b).

众所周知,类别是一组对象,它们之间带有箭头。 好吧,函子知道如何将类别映射到另一个类别。 它将映射所有对象和所有箭头,并保留对象之间的连接(如果原始类别中的ab之间有一个箭头,则在所得类别中FaFb之间将有一个箭头)。 给定某个对象a或某个箭头f:原始类别中的a→b ,对应的对象(一个函子映射到其中)表示为F(a) ,对应的箭头表示为F(f):F(a)→F (b)

Functor that maps a category back to that same category is called an endofunctor. Quick detour from the category theory into the real world — in programming, we deal with functors in category of types, and they are all endofunctors. They map a category of types back to category of types. Whenever you mapped something (map in Scala, fmap in Haskell) you had an endofunctor in your hands. For example, Option, List and Future are all valid endofunctors. Basically anything that has a map() function is an endofunctor.

将类别映射回相同类别的函子称为endofunctor。 从类别理论快速绕道进入现实世界—在编程中,我们处理类别类别的函子,它们都是内函子。 他们将类型的类别映射回类型的类别。 当你映射的东西( Scala中,FMAP在Haskell)你必须在你的手中的endofunctor。 例如,Option,List和Future都是有效的endofunctors。 基本上任何具有map()函数的东西都是endofunctor。

Don’t take this for granted though; it’s merely a convention. Some library may provide you with an object whose map() doesn’t obey the functor laws (see the old article) which would mean that the object itself cannot be considered a functor.

但是,不要认为这是理所当然的。 这只是一个约定。 某些库可能会为您提供其map()不遵守函子定律的对象(请参阅旧文章 ),这意味着该对象本身不能视为函子。

Example: in Scala we can map from List[Int] to List[String] (in other words, to map all elements of a List from Int to String):

示例:在Scala中,我们可以从List [Int]映射到List [String](换句话说,可以将List的所有元素从Int映射到String):

List(1, 2, 3).map((x: Int) => x.toString)

We can have all kinds of mappings: from Int to String, from String to List[String], from List[String] to Banana etc. All of them are Scala types. This means we always map from a category of Scala types back to the category of Scala types. Hence the “endo” part.

我们可以有各种各样的映射:从Int到String,从String到List [String],从List [String]到Banana等。它们都是Scala类型。 这意味着我们总是从Scala类型的类别映射回Scala类型的类别。 因此是“ endo”部分。

Now let’s “rip out” the map() method from some functor F and view it as a function of two arguments — a functor object and a function which we map it with. For example, List(1, 2, 3).map(f) becomes map(f, List(1, 2, 3)).

现在,让我们从某些仿函数F中“提取” map()方法,并将其视为具有两个参数的函数—仿函数对象和与其进行映射的函数。 例如,List(1、2、3).map(f)变为map(f,List(1、2、3))。

If we curry that, it’s easy to see that signature of map is:

如果我们对此加以说明,很容易看到map的签名是:

(a → b) → F[a] → F[b]

(a→b)→F [a]→F [b]

Function which we map with is denoted as a → b, starting functor object is F[a] and result functor is F[b]. Note that instead of providing both arguments, function a → b (e.g. (x: Int) => _.toString()) and a functor instance F[a] (e.g. List(1, 2, 3)), we can provide only the function f, in which case we get back a function F[a] → F[b] (if this is not something you’re comfortable with, do a quick research on currying and partially applied functions). So yeah, instead of providing both arguments of type A and B and getting back a value of type C, we can provide only parameter of type A and get back a function B → C. Providing only number 4 to a function which sums two numbers gives us back a function that takes some number n and returns n+4.

我们映射的函数表示为a→b ,起始函子对象为F [a] ,结果函子为F [b]。 请注意,函数a→b (例如(x:Int)=> _.toStrin g())和函子实例F [ a](例如List(1、2、3 ))不提供两个参数),我们只能 f 提供函子,在这种情况下,我们会在F [a]→F [b] 取回函子(如果您不满意,请对curring进行快速研究并部分应用函数的N s )。 是的,我们可以只提供类型A的参数并返回函数,而不是提供类型A和B的参数并获取类型C的值, 乙→(三)提供仅数4到总结两个数字的功能让我们回到一个函数,有些麻木了 n和retur NS N + 4。

Let’s take another look at this interesting function (a → b) → F[a] → F[b]. It’s a function from (a → b) to F[a] → F[b]. To put it in category theory terms — it maps a category to another category, where objects of destination category carry the F symbol. Remember what we said earlier? “Given some object a or some arrow a → b from the original category, corresponding object (the one functor maps into) is denoted as F(a) and corresponding arrow is denoted as F(a) → F(b).” That’s exactly what map does.

让我们再来看一下这个有趣的函数(a→b)→F [a]→F [b] 。 它是从(a→b)F [a]→F [b]的函数 用类别理论的术语来说-将类别映射到另一个类别,其中目标类别的对象带有F符号。 还记得我们之前说的话吗? “给定原始类别中的某个对象a或某个箭头a→b ,相应的对象(一个函子映射到其中)表示为F(a) ,相应的箭头表示为F(a)→F(b) 。” 地图正是这么做的。

But we are missing something. We saw just one half of being a functor: the one that maps morphisms. A function on functions. It takes a function and returns the “lifted” version of that function, that is, one that instead of taking a and returning b now takes F[a] and returns F[b]. By the way, in practice we often don’t just lift functions and save them for later. Instead we immediately apply them to a functor instance. For example, instead of lifting a function Int → String to List[Int] → List[String], we usually pass in an instance of List[Int] right away (or, in case of Scala, we invoke map() as a method on an instance of List[Int])).

但是我们缺少了一些东西。 我们只看到了一半是函子:映射态射的那一。 功能上的功能 。 它接受一个函数并返回该函数的“提升”版本,即,一个不再使用a返回b的函数现在取F [a]并返回F [b] 。 顺便说一句,在实践中,我们通常不只是取消功能并将其保存以供以后使用。 相反,我们立即将它们应用于函子实例。 例如,我们通常不立即传递一个Int→StringList [Int]→List [String] ,而不是直接传递一个List [Int]的实例(或者,对于Scala,我们将map()作为List [Int]实例的方法))。

However, we know that functor not only maps all source arrows to destination arrows, but also maps all source objects to destination objects, right? But we only saw the mapping of arrows, when we lifted a → b to F(a) → F(b). How to map a itself (and b, for that matter)? This is another side of a functor: apart from the function on functions, there is also a (somewhat implicit) function on types. Functor’s mapping of arrows is represented by the function-on-functions part, while mapping of objects is represented by the function-on-types part.

但是,我们知道functor不仅将所有源箭头映射到目标箭头,而且还将所有源对象映射到目标对象,对吗? 但是,当我们将a→b提升到F(a)→F(b)时,我们仅看到箭头的映射。 如何映射一个自身(和b )? 这是函子的另一方面:除了函数上的函数外, types上还有一个(有点隐式) 函数 。 函子的箭头映射由功能函数部分表示,而对象的映射由功能类型部分表示。

What is this function on types? It’s a unary type constructor. This means that, given some type, functor produces another new type. So if we take List functor as an example, given a String it gives us a List[String]. Given some type A, it gives us a List[A]. Type constructor for functors can be described in a more general way as * → * (takes one type as a parameter and produces a new type from it). Here’s some more useful reading on type constructors.

类型上的此功能是什么? 这是一元类型构造函数 。 这意味着,给定某种类型,函子会产生另一种新类型。 因此,如果我们以List函子为例,给定一个String,它将给我们一个List [String]。 给定某种类型A,它给我们一个List [A]。 函子的类型构造函数可以更一般地描述为*→*(将一个类型作为参数并从中产生一个新类型)。 这是有关类型构造函数一些更有用的读物​​。

Quick wrap-up of functors. Each functor is a mapping of categories. It maps some source category C to destination category F by mapping objects in C to objects in F and morphisms in C to morphisms in F. We say that (quoting myself) “given some object a or some arrow f: a → b from the original category, corresponding object (the one functor maps into) is denoted as F(a) and corresponding arrow is denoted as F(f): F(a) → F(b)”.

快速包装函子。 每个函子都是类别的映射。 它通过将C中的对象映射到F中的对象并将C中的射态映射到F中的射态来将某些源类别C映射到目标类别F。我们说(引用自己)“给了一些对象a或某些箭头f:a→b原始类别,相应的对象(一个函子映射到其中)表示为F(a) ,相应的箭头表示为F(f):F(a)→F(b) ”。

As (Scala?) programmers, we are working with the category of types in Scala (category of types in Haskell is called Hask, so I guess the Scala one could be called Sca or something). Here objects are Scala types and morphisms are functions between those types. Each functor in Sca maps the objects (Scala types) via the type constructor, and morphisms (functions on Scala types) via function map. If we take Option as an example, it maps every object in Sca (that is, every Scala type) into Option(thatObject), and every morphism between objects in Scala (that is, function between Scala types) into Option(thatFunction).

作为(Scala?)程序员,我们正在使用Scala中的类型类别(Haskell中的类型类别称为Hask ,所以我猜Scala可以称为Sca或其他名称)。 这里的对象是Scala 类型和态素是这些类型之间的函数。 Sca中的每个函子都通过类型构造函数映射对象(Scala类型),并通过函数map映射射态(Scala类型上的函数)。 如果以Option为例,它将Sca中的每个对象(即每个Scala类型)映射到Option(thatObject)中,并将Scala中的对象之间的每个态射(即Scala类型之间的函数)映射到Option(thatFunction)中。

And since it’s a functor Sca → Sca, it’s more specifically an endofunctor.

而且,由于它是Sca→Sca的函子,因此更具体地说是endofunctor

单子 (Monad)

Now that we know what category, monoid and endofunctor are, we can imagine a category of endofunctors and try to find a monoid in that category. As the famous statement by mr. Mac Lane tells us, monoid in the category of endofunctors is in fact a monad.

既然我们知道了类,monoid和endofunctor是什么类别,我们可以想象一个endofunctors类别,并尝试在该类别中找到一个monoid。 正如先生所发表的著名讲话。 麦克·莱恩告诉我们,在类终结者中,类人动物实际上是一个单子。

What does it mean to be a monoid in some category? For example, a monoid in the category Set (remember, that’s the category of all sets). What is it? Well, take a look at the category; it contains all imaginable sets. Now pick out those that satisfy monoid laws, that is, pick out all those sets for whose elements we can find an identity element and define an associative binary operation.

在某些类别中成为类人动物意味着什么? 例如,类别Set中的一个monoid(请记住,这是所有集合的类别)。 它是什么? 好吧,看看类别; 它包含所有可以想象的集合。 现在,选择那些满足单调律的律,即,选择所有那些其元素可以找到恒等元素并定义关联二进制运算的 集合

One such set is the set of integers with identity element being zero and binary operation being addition. Note that monoidal binary operation on set S must operate on two operands, both from S, and return something that is also an element of S.

一个这样的集合是一组整数,其中标识元素为零,二进制运算为加法。 请注意,对集合S的单项二进制运算必须对两个操作数(均来自S)进行运算,并返回也是S元素的东西。

Cool, so a monoid in the category Set is any set that has those properties, that is, those two operations (for example, the set of natural numbers with addition). Here are some more examples of monoids in the category of X.

很酷,因此Set类别中的monoid是具有这些属性(即这两个操作)的任何集合(例如,带有加法的自然数集合)。 是X类别中的类半体的更多示例。

Let’s now see about “monoid in the category of endofunctors”. In this category, objects are endofunctors and arrows are mappings between those functors. An extra bit of terminology before we continue: mappings between functors are called natural transformations. They operate on a yet higher level of abstraction:

现在让我们看一下“ endofunctors类别中的monoid”。 在此类别中, 对象是功能终结者,箭头是这些函子之间的映射 。 在继续之前,需要多加一些术语:函子之间的映射称为自然变换 。 它们在更高的抽象级别上运行:

  • arrows map objects within a category from one to another

    箭头将类别中的对象从一个映射到另一个

  • functors map categories from one to another

    函子将类别从一个映射到另一个

  • natural transformations map functors from one to another

    自然变换将函子从一个映射到另一个

We could also say that a natural transformation is an “arrow between functors”, and that’s exactly what we have here — we have a category of endofunctors, which means that objects are endofunctors themselves, and arrows between those endofunctors are by definition natural transformations.

我们也可以说自然变换是“函子之间的箭头”,这正是我们在这里所拥有的-我们有一类endofunctors,这意味着对象本身就是endofunctors,而根据定义,这些endofunctors之间的箭头是自然变换。

Back to our search for monads. So what we are doing is reaching into the category of endofunctors and looking for such objects of that category for which two particular arrows are defined — identity and associative binary operation. When we have those two arrows, we have a monoid (in the category of endofunctors).

返回我们的单子搜索。 因此,我们要做的是进入endofunctors类别,并寻找该类别的此类对象,并为其定义了两个特定的箭头-身份和关联二进制操作。 当我们拥有这两个箭头时,我们就有一个monoid(在endofunctors类别中)。

So we need an endofunctor F for which the following operations are defined:

因此,我们需要一个endofunctor F,为其定义以下操作:

  • η: I → F

    ηI→F

  • μ: F x F → F

    μF x F→F

where I is the identity endofunctor, η (eta) is the fancy math name for identity on endofunctors, and μ (mu) is the fancy math name for associative binary operation on endofunctors. If you’re unsure where these two came from all of a sudden, remember that when we talked about monoid as a set, I showed you a wiki link which, as you can see, uses those exact two operations (more precisely, arrows in the monoidal category) to define a monoid (in the monoidal category).

其中,I是身份终结者, η (eta)是终结者身上身份的奇特数学名称, μ (mu)是终结者身上关联二进制运算的奇特数学名称。 如果您不确定这两个突然从何而来,请记住,当我们谈到monoid作为一个集合时,我向您展示了一个Wiki链接 ,如您所见,它使用了这两个操作(确切地说是箭头) (monoidal类别)来定义一个monoid(在monoidal类别中)。

Let’s examine those two operations in the context of category of endofunctors and see what they become in that case:

让我们在endofunctors类别的上下文中检查这两个操作,看看它们在这种情况下会变成什么样:

  • Identity is a natural transformation mapping from an identity endofunctor (a functor which maps a category to itself) to another endofunctor.

    身份是从身份终结者(将类别映射到自身的函子)到另一个终结者的自然转换映射。

  • Asssociative binary operation is some operation x that knows how to take two endofunctors and turn them into one. Keep in mind that this operation must be associative, so (F x F) x F must yield the same result as F x (F x F).

    关联二进制运算是一些x的运算,它知道如何获取两个endofunctors并将它们转换为一个。 请记住,此操作必须是关联的,因此(F x F)x F必须产生与F x(F x F)相同的结果。

Now let’s see how each of these two operations materialize in the Scala world.

现在,让我们看看这两个操作在Scala世界中如何实现。

First, η: I → F. What identity functor I does is it maps any category back to itself. That means its “type constructor” doesn’t actually create any new type F(value). It just leaves the value as it is. So what nat. transformation η really does is — it takes an identity functor and wraps it into a functor F context. In category of Scala types, identity functor maps a type back to itself, so we can view this whole transformation as lifting a Scala type into some F context, where F can be any functor.

首先, ηI→F。 做的身份函子是将任何类别映射回自身。 这意味着其“类型构造函数”实际上不会创建任何新的类型F(value) 。 它只是保持原样。 那什么 确实转化η 确实是-它需要一个身份函子并将其包装到函子F上下文中。 在Scala类型的类别中,身份函子将类型映射回其自身,因此我们可以将整个转换视为将Scala类型提升到某个F上下文中,其中F可以是任何函子。

What about μ: F x F → F? It’s a composition of two identical functors which results in just one. It’s a way to turn F[F[T]] into F[T].

那么μF x F→F呢? 它是由两个相同的函子组成的,结果只有一个。 这是将F [F [T]]转换为F [T]的一种方法。

Based on what we just said, it’s pretty logical to have apply as η and flatten as μ. Method apply constructs a type F[x] from x , while flatten “flattens” the composition F[F[x]] into F[x].

根据我们刚才所说的,将其应用η并将其展平μ是很合乎逻辑的 apply方法从x构造类型F [x],而将组合F [F [x]] 平“ 平”为F [x]。

So whenever you see an endofunctor in Scala (in practice this is almost any object with method map, but remember to check the functor laws before you declare something a functor; for example, there’s a way to break those laws for Set) that also has flatten and unit, you are in fact dealing with a monad.

因此,每当您在Scala中看到endofunctor时(实际上,这几乎是任何带有方法map的对象,但记住在声明某个函子之前要检查函子定律;例如,有一种方法可以打破Set的那些定律) 拼合单位 ,实际上您正在处理一个monad。

三种monad定义和monad法则 (Three monad definitions and monad laws)

We saw that monad is an endofunctor (remember, any functor in a programming language is actually an endofunctor because it maps from the category of types back to category of types) with two natural transformations:

我们看到monad是一个endofunctor(记住,编程语言中的任何functor实际上都是一个endofunctor,因为它从类型的类别映射回类型的类别)具有两个自然的转换:

  • identity/unit implemented as apply, and

    身份/单位已按适用方式实施,并且

  • associative binary operation implemented as flatten (note that terms unit and identity are used interchangeably; I tend to use unit when talking from a practical, programming viewpoint, and identity when talking about category theory, but sometimes I may mix them up; they’re the same thing).

    关联的二进制操作实现为扁平化 (请注意,术语单位身份可互换使用;从实际的编程角度讲时,我倾向于使用单位;在谈论类别理论时,我倾向于使用单位,但有时我可能将它们混淆;它们是同一件事情)。

Also, since we are talking about an (endo)functor, we know that there is a map method available too.

另外,由于我们正在谈论(内)functor,因此我们知道也有可用的map方法。

You may have heard about monad consisting of unit and flatMap. Yep, that’s correct; we can simply “squash” the flatten (coming from μ) and map (coming from its functor nature) methods into a single flatMap method. Our monad would still retain the same properties, it’s just that its associative binary operation would no longer be flatten, but flatMap. Monad laws (explained later) are not harmed.

您可能已经听说过由unitflatMap组成的monad。 是的,没错; 我们可以简单地“压扁” 扁平化 (来自μ ) 并将方法(由于其函子性质而来) 映射到单个flatMap方法中。 我们的monad仍将保留相同的属性,只是它的关联二进制运算不再是flatten了 ,而是flatMap 。 Monad法律(稍后说明)不受损害。

So there are two valid definitions of a monad? One that involves unit and flatMap and one that involves unit, flatten and map?

那么单子有两个有效的定义? 一种涉及unitflatMap ,一种涉及unitflattenmap?

Yes. And not just that — there’s also a third one. Monads can be expressed in three different ways. All three definitions are equivalent and we can easily transform one into another. Here they are:

是。 不仅如此-还有第三个。 Monad可以三种不同的方式表达。 这三个定义都是等效的,我们可以轻松地将它们转换为另一个。 他们来了:

  • unit + flatMap

    单位+ flatMap
  • unit + flatten + map

    单位+展平+地图
  • unit + compose

    单元+撰写

All three are equally powerful and each one can be expressed by using one of the other two. We’ll talk more about them later.

这三个函数都具有同等的功能,可以使用其他两个函数之一来表示每个函数。 稍后我们将详细讨论。

Now, every monad implementation (e.g. List, Option etc.) needs to obey the monad laws. Monad laws can be expressed in three different ways, depending on which monad definition you are using, but they all resemble the same core concept.

现在,每个monad实现(例如List,Option等)都需要遵守monad法律 。 可以使用三种不同的方式来表示Monad法则,具体取决于您所使用的monad定义,但它们都类似于相同的核心概念。

Here are the monad laws presented using the unit + flatMap definition:

以下是使用unit + flatMap定义给出的单子法则:

  • left-identity law:

    左恒定律

    left-identity law: unit(x).flatMap(f) == f(x)

    左恒律unit(x).flatMap(f)== f(x)

  • right-identity law:

    权利认同法

    right-identity law: m.flatMap(unit) == m

    右恒律m.flatMap(unit)== m

  • associativity law:

    结合律

    associativity law:m.flatMap(f).flatMap(g) == m.flatMap(x ⇒ f(x).flatMap(g))

    关联律m.flatMap(f).flatMap(g)== m.flatMap(x⇒f(x).flatMap(g))

It is obvious that unit + flatMap definition is easily transformed to unit + flatten + map and vice versa because flatMap = flatten + map. I will now show the connection between unit + flatMap and unit + compose. I like the unit + compose definition because it makes the laws easier to express.

显而易见,因为flatMap = flatten + map ,所以unit + flatMap的定义很容易转换为unit + flatten + map ,反之亦然 现在,我将展示unit + flatMapunit + compose之间的连接。 我喜欢单位+组成定义,因为它使法律更容易表达。

Let’s see the compose function:

让我们看一下compose函数:

def compose: (A => F[B]) => (B => F[C]) => A => F[C]

This is a two-parameter function where both parameters are functions of type A → F[B], and the result is also a function of the same type. Note that types A and B are completely free and may represent any concrete types in Scala (A→F[B] could be e.g. Int → List[String] or Int → Set[Int]). We use the letters simply to denote that the parameter of the first function is the same type as the parameter of the result function (here denoted as “A”).

这是一个两参数函数,其中两个参数都是A→F [B]类型的函数,结果也是相同类型的函数。 请注意,类型A和B是完全自由的,并且可以表示Scala中的任何具体类型( A→F [B]可以是例如Int→List [String]Int→Set [Int] )。 我们仅使用字母来表示第一个函数的参数与结果函数的参数(在此表示为“ A”)具有相同的类型。

By the way, functions of type A F[B] are called Kleisli arrows, just in case you stumble upon the term somewhere. We say that the function we just defined has Kleisli arrows as parameters and also as return type. It’s a composition of Kleisli arrows.

顺便说一下,类型A F [B]的函数称为Kleisli箭头 ,以防万一您偶然发现该术语。 我们说刚定义的函数具有Kleisli箭头作为参数以及返回类型。 这是克雷斯利箭组成

Now let’s also define “unit”:

现在让我们定义“单位”:

def unit: A => F[A]

We can easily check that this is indeed unit: it’s a neutral element that, when composed with, gives back the original element. Just take the signature and replace F[C] with F[B] so that the second parameter becomes an identity function:

我们可以很容易地检查出它确实是单位:它是一个中性元素,当与之组合时,会返回原始元素。 只需获取签名并将F [C]替换为F [B],以便第二个参数成为一个标识函数:

(A => F[B]) => (B => F[B]) => A => F[B]

Again, types A, B and C are not fixed and can represent any concrete type so A → F[A] from the first identity expression is the same function as B → F[B] in this expression. You can think of identity function not as A F[A], but as Whatever F[Whatever] if you wish:

同样,类型A,B和C不是固定的,可以表示任何具体类型,因此第一个标识表达式中的A→F [A]与该表达式中的B→F [B]具有相同的功能。 如果您希望,可以将身份函数不视为A F [A],而是视为Whatever F [What]。

(A => F[Whatever]) => (Whatever => F[Whatever]) => A => F[Whatever]

So if we apply identity function as a second operand to compose, we will arrive back at our first operand. Good, composing identity with some function results in the same function. Identity holds. Associativity is pretty clear too; I will leave it to you to try and prove it by yourself.

因此,如果将身份函数用作第二个操作数来编写,我们将回到第一个操作数。 好的,将身份与某些功能组合会产生相同的功能。 身份持有。 关联性也很清楚。 我将它留给您尝试自己证明。

What I wanted to show you is that compose can be expressed by using flatMap. Same goes in the other direction too; flatMap can be expressed by using compose.

我想向您展示的是可以通过使用flatMap来表达撰写 。 同样也朝着另一个方向发展。 flatMap可以使用compose表示。

Here it is:

这里是:

trait Monad[F[_]] {

  def flatMap[A, B](fa: F[A], f: A => F[B]): F[B] = ???
  
  def compose[A, B, C](f1: A => F[B], f2: B => F[C]): A => F[C] = {
    a => flatMap[B, C](f1(a), f2)
  }
}

And the other direction:

另一个方向:

trait Monad[F[_]] {

  def compose[A, B, C](f1: A => F[B], f2: B => F[C]): A => F[C] = ???

  def flatMap[A, B](fa: F[A], f: A => F[B]): F[B] = {
    compose[Unit, A, B]((u: Unit) => fa, f)()
  }
}

As I said earlier, unit + compose is particularly great for one specific purpose, and that is expressing monad laws. They become simpler to read and understand. Here they are again, this time using compose instead of flatMap:

就像我之前说的, unit + compose对于一个特定目的特别有用,那就是表达单子法则。 它们变得更易于阅读和理解。 再次出现,这一次是使用compose而不是flatMap

  • left-identity law:

    左恒定律

    left-identity law: unit.compose(f) == f

    左恒定律unit.compose(f)== f

  • right-identity law:

    权利认同法

    right-identity law: f.compose(unit) == f

    右恒律f.compose(unit)== f

  • associativity law:

    结合律

    associativity law:f.compose(g.compose(h)) == (f.compose(g)).compose(h)

    关联定律f.compose(g.compose(h))==(f.compose(g))。compose(h)

Remember that all three definitions are equally “good”, that is, all three are minimal sets of operations needed to define a monad and each one can be expressed by using one of the other two.

请记住,所有三个定义都是“良好”的,也就是说,所有三个定义都是定义一个monad所需的最少操作集,并且每个表达式都可以使用其他两个表达式之一来表示。

Always remember that monad laws are the essence of what makes monads what they are. Having monad operations (e.g. unit + flatMap) is not enough if the laws are not satisfied.

永远记住,单子法则是使单子成真的本质。 如果不满足法律要求,仅执行monad操作(例如unit + flatMap )是不够的。

摘要 (Summary)

Our initial objective is complete. We now understand that famous sentence. Monoid in the category of endofunctors is any endofunctor with operations η and μ, and we call such endofunctor a monad (reminder: objects of that category are endofunctors and arrows are natural transformations).

我们的最初目标是完整的。 现在我们了解了那个著名的句子。 endofunctors类别中的类人动物是具有操作ημ的任何endofunctor,我们称这种endofunctor为monad (提醒:该类别的对象是endofunctors,箭头是自然变换)。

So, monad can be defined in many ways, such as:

因此,可以通过多种方式定义monad,例如:

  • monoid in the category of endofunctors

    类名的终结者
  • object in the category of endofunctors with arrows η and μ

    带有箭头ημ的终结符类别中的对象

  • endofunctor with natural transformations η and μ

    具有自然变换ημ的 endofunctor

In Scala, η is implemented as apply and μ is implemented as flatten, which means that monad is any functor (that is, a construct with map method) that is additionally equipped with apply and flatten. There are two more completely equally valid ways of defining a monad in terms of its operations: unit + flatMap and unit + compose. They are completely equal and there is nothing one can do that others can’t. We saw how flatMap can be expressed using compose and vice versa; this is possible for all combinations.

在Scala中,将η实施为apply ,将μ实施为flatten ,这意味着monad是任意配备了applyflatten的函子(即具有map方法的构造)。 按照其操作定义monad的方法有两种完全相同的有效方法: unit + flatMapunit + compose 。 他们是完全平等的,没有人能做别人不能做的。 我们看到了如何使用compose来表达flatMap ,反之亦然; 这对于所有组合都是可能的。

And don’t forget about the monad laws.

而且不要忘了单子法则。

最后的话 (Final word)

Hopefully this article helped you gain some perception as to how certain category theory constructs fit together and, more specifically, what our old friends monads “really are” — monoids in the category of endofunctors.

希望本文能帮助您了解某些类别理论的构造如何组合在一起,更具体地说,是我们的老朋友monads“真正是”的东西-内爆者类别中的类人动物。

By the way, the name monad comes from “monoid” and “triad”; monoid because it’s a monoid in the category of endofunctors, and triad because it’s a package of three things: an endofunctor equipped with two natural transformations.

顺便说一句,monad这个名字来自“ monoid”和“ triad”。 monoid是因为它是endofunctors类别中的monoid,而triad是因为它是三件事情的组合:具有两个自然转换的endofunctor。

That’s all for now. As usual, if you find any mistakes please do let me know via email (sinisalouc[at]gmail[dot]com). You can also find me on Twitter.

目前为止就这样了。 与往常一样,如果您发现任何错误,请通过电子邮件(sinisalouc [at] gmail [dot] com)告诉我。 您也可以在Twitter上找到我。

Cheers!

干杯!

翻译自: https://www.freecodecamp.org/news/demistifying-the-monad-in-scala-part-2-a-category-theory-approach-2f0a6d370eff/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值