原文链接:http://bartoszmilewski.wordpress.com/2011/01/09/monads-for-the-curious-programmer-part-1/
The Monad is like a bellows:
单子就像一个风箱:
it is empty yet infinitely capable.
它什么也没有,却有无限能力
The more you use it, the more it produces;
用的越多,它产的也就越多;
the more you talk about it, the less you understand.
你谈论的越多,你理解的越少。
–Monad Te Ching
I don’t know if I’m exaggerating but it seems like every programmer who gets monads posts a tutorial about them. (And each post begins with: There’s already a lot of monad tutorials on the Internet, but…) The reason is that getting monads it’s like a spiritual experience that you want to share with others.
我不知道是不是有点夸张,但是基本每一个理解了单子的程序员都会贴一个相关的教程(教程一般如此开始:网络上已经有大量的关于单子的教程,但是……)。其原因是理解单子的过程就像是一种精神体验,你总是想要和其他人分享。
When facing a monad, people often behave like the three blind men describing an elephant. You’ll see monads described as containers and monads described as actions. Some people see them as a cover-up for side effects, others as examples of endofunctors in Category Theory.
当描述单子的时候,人们更像是三个摸象的盲人。你会看到有人把单子当作容器,有人把单子当作动作。一些人把单子看作是副作用的封装,而另一些则认为单子是范畴论中的自函子。
Monads are hard to describe because they don’t correspond to anything in our everyday experience. Compare this with Objects in Object-Oriented programming. Even an infant knows what an object is (something you can put in your mouth). What do you do with a monad?
和面向对象编程中的对象比起来,单子难于描述是缘于日常生活中没有可以对应的东西。即使一个婴儿也知道对象是什么(就是那种可以塞到嘴巴里的东西)。你怎么处理单子呢?
But first, let me answer the pertinent question:
不过,我们先回答一个相关的问题:
Why Bother?
为啥要这般麻烦?
Monads enable pure functional programmers to implement mutation, state, I/O, and a plethora of other things that are not functions. Well, you might say, they brought it on themselves. They tied their hands behind their backs and now they’re bragging that they can type with their toes. Why should we pay attention?
单子使得实现变换,状态,I/O以及其大量的不是函数式的东西成为可能。不过,你会说,这是他们自找的。他们把手绑在身后,然后吹牛说可以用脚趾打字。谁会在意这些?
The thing is, all those non-functional things that we are so used to do in imperative programming are also sources of a lot of troubles. Take side effects for instance. Smart programmers (read: the ones who burnt their fingers too many times) try to minimize the use of global and static variables for fear of side effects. That’s doable if you know what you’re doing. But the real game changer is multithreading. Controlling the sharing of state between threads is not just good programming practice– it’s a survival skill. Extreme programming models are in use that eliminate sharing altogether, like Erlang’s full isolation of processes and its restriction of message passing to values.
实际情况是,所有那些我们在命令式语言中使用的非函数式的东西是混乱之源。就举副作用的例子,聪明的程序员(注:就是那些多次烧掉他们自己手指的家伙)总是尽最大努力较少全局变量和静态变量,就是因为害怕副作用。如果你知道你在做什么,这没问题。但是真正改变游戏规则的是多线程。控制线程之间的共享状态早已超越所谓的“良好编程实践”而成为必备的生存技能。现在用着的极限编程模型则完全消除共享,如Erlang语言完全隔离了进程以及限制了传递的消息必须是值。
Monads stake the ground between total anarchy of imperative languages and the rigid dictatorship of Erlang-like isolationism. They don’t prohibit sharing or side effects but let you control them. And, since the control is exercised through the type system, a program that uses monads can be checked for correctness by the compiler. Considering how hard it it to test for data races in imperative programs, I think it’s worth investing some time to learn monads.
单子处于完全命令式语言和混乱和类似Erlang独裁式的完全隔离的中间地带。单子不禁止共享或者副作用,同时也允许你来控制。另外,由于控制是被类型系统检查的,用到了单子的程序可以借助编译器来保证正确性。考虑到命令式语言中测试数据竞争的困难性,窃以为投点时间学习单子还是值得的。
There is also a completely different motivation: metaprogramming. The template language used for metaprogramming in C++ is a pure functional language (see my blog post, What does Haskell have to do with C++?). If monads are so important in functional programming, they must also pop up in C++ metaprogramming. And indeed they do. I hope to discuss this topic in a future post.
另一个完全不同的驱动是元编程。C++中用于元编程的模板语言是纯函数式语言(参见我的博文,Haskell能对C++干什么?)。如果单子在函数式编程中如此重要,其重要性在C++元编程中也应该体现出来。事实上也是如此,我会在将来的博文中讨论这个主题。
So what’s a monad?
那么,什么是单子呢?
A Categorical Answer
范畴论的答案
If you don’t know anything about category theory, don’t get intimidated. This is really simple stuff and it will clarify a lot of things, not to mention earning you some bragging rights. My main goal is to share some intuitions from mathematics that will build foundations for a deeper understanding of monads in programming. In this installment I will explain categories, functors, and endofunctors, leading up to monads. I will give examples taken both from everyday life and from programming. I will really get into monads and their practical applications in the next installment, so be patient.
如果你对范畴论一无所知也不必害怕。这真的很简单的东西,这东西会说明不少事情,更不用说你还能赚点吹牛的资本。我的主要目标是分享一些数学直觉,而这将成为编程中深入理解单子的基础。这部分中,我会解释范畴,函子,自函子,然后引出单子。我也会举一些来自日常生活和编程中的例子。我会在下一部分真正进入单子以及实际中的应用,所以请有一点点耐心。
Categories
范畴
A category is a natural extension of our notion of sets and functions. The generalization of a set in a category is called an object (a pretty neutral term with little semantic ballast), and the generalization of a function is called a morphism. In fact, the standard example of a category is the category of sets and functions called (capital letter) Set.
范畴是集合和函数概念的自然扩充。泛化的集合在范畴中被称为对象(这是一个稍微带有实体语义的中性词),泛化的函数则被称为映射。实际上,范畴论中范畴的标准例子就是有集合和函数组成的名为Set(首字母大写)的范畴。
A morphism (read “function”) goes from one object (read “set”) to another. Mathematical functions like sin or exp usually go from the set of real numbers to the set of real numbers. But you may also define functions like isPrime that go from natural numbers to Booleans, or a function price that goes from a set of goods to the set of numbers.
映射(读作函数)从一个对象(读作集合)变换到另一个。数学函数,如sin,exp,一般都是从实数集到实数集。但是你可以定义从自然数到布尔值的函数,如isPrime函数,或者Price函数,从货物的集合到数字的集合。
The only thing a mathematician needs to know about morphisms is that they can be composed. If you have a morphism from A to B, A->B
, and another going from B to C, B->C
, then they can be composed to a morphism from A to C, A->C
. And just like the standard composition of functions, morphism composition must be associative, so we don’t need parentheses when composing more than two of them.
对数学家来说, 关于映射唯一需要知道的事情是它们可以被组合。如果有一个从A到B的映射,还有一个从B到C的映射,那么就可以组合他们成为一个从A到C的映射。和标准的函数组合泪洗,映射组合必须是可结合的,所以我们在组合它们的时候不不要括号。
Actually, two things. There must be, for every object, a special morphism called identity that essentially does nothing and when composed with any other morphism reproduces the same morphism.
噢,要知道两件事。每一个对象必须有一个特别的映射:当它与其他的映射组合的时候得到同样的映射。这样的映射叫做单位映射。
Just to throw you off the track, a category doesn’t have to be built on sets and functions. You can easily construct simple categories from blobs and arrows. Fig 1 shows such a category that contains two objects and four morphisms: arrows between them (formally, those arrows are ordered pairs of objects so, for instance, f is a pair (A, B)). You can easily check that any two morphisms can be composed and that the two moprphisms iA
and iB
serve as identities.
一点题外话。范畴不必一定是基于集合和函数。你可以使用气泡和箭头来构建范畴。下图一是这样的一个范畴,有两个对象,四个映射:对象之间的箭头(正式地,这些箭头是有序对,例如,f是有序对(A, B))。可以检查,任何的映射都可以自由组合,映射ia和ib是单位映射。
That’s it! Hopefully I have just convinced you that a category is not a big deal. But let’s get down to Earth. The one category that’s really important in programming languages is the category of types and functions, in particular its Haskell version called Hask. There usually is a finite set of basic types like integers or Booleans, and an infinite set of derived types, like lists of integers, functions from integers to Booleans, etc. In Hask, a type is just a set of values. For instance, the type Char
is a set {‘a’, ‘b’, ‘c’, … }.
就这么多了。希望能我已经向你证明这不是什么大不了的。不过我们就不务虚 了。在编程语言中真正重要的一个范畴就是类型和函数的范畴,特别是在被称为Hask的一个Haskell版本中。一般有一个有效基本类型集合,如整数,布尔数,和一个无限的派生类型的集合,如整数链表,从整数到布尔数的函数等。Hask中,类型仅仅是一些值的集合。例如,类型Char的集合是{‘a’, ‘b’, ‘c’, …}。
So, in the category Hask, types are objects and functions are morphisms. Indeed, a function maps one type into another (forget for a moment functions of multiple arguments– they can be modeled with currying– and polymorphic functions– they are families of functions). And these are functions in the functional-programming sense: called with the same values they return the same values–no side effects allowed.
因此,在范畴Hask中,类型是对象,而函数是映射。实际上,一个函数就是把从一个类型映射到另一个类型(暂且忘掉带多个参数的函数以及多态函数。前者可以通过Currying来处理而后者不过是函数簇)。而且,也存在函数式编程意义上的函数:以相同的值调用返回相同的值,不允许副作用。
Function composition is just passing the result of one function as an argument to another. The identity function takes x and immediately returns it back.
函数组合仅仅是把函数作为参数传给另一个函数。单位映射函数以x为参数并且立即返回x。
This is all fine, but what’s in it for me, you might ask. So here’s the first insight and a moment of Zen. If there is one thing that you can call the essence of programming, it’s composability. In any style of programming you always compose your program from smaller pieces, and those pieces from even smaller pieces, and so on. That’s why categories with their composable morphisms are so important. The essence of Lego blocks is the way they fit together, their composability, not the color or size. The essence of functional programming is how functions work together: how you can build larger functions from smaller ones.
这一切都不错,但是你可能要问:这对我有什么用?这是第一个观察,有点算顿悟。如果你可以调用编程的最基本的构建成立的话,它就是可组合的。在任何种类的编程中,你总是从小的东西开始组合程序;而那些小的东西是有更小的东西组合而来,等等。这就是为什么范畴论以及其可组合的映射如此重要的原因。乐高积木的根本在于其可以相互配合,其可组合性而不是色彩或者大小。函数式编程的根本是函数如何可以协调工作:你如何从小的东西来构建更大的东西。
Every category is defined by its choice of objects and morphisms. But is there something that can characterize a given category that’s independent of its choice of particular objects and morphisms? How do you expose the inner structure of a particular category? Mathematicians know exactly how to do that. You have to be able to map categories into other categories while preserving some constraints imposed by the way morphisms are attached to objects and the way they compose. Such maps let you find similarities between categories and catalog different kinds of categories. That’s when things get really interesting.
每个范畴都由其选择的对象和映射定义。存在独立于其选择的对象和映射的用来描述一个范畴的方法吗?如果表述特定内部结构的范畴?数学家确实知道怎么做。你需要能够把范畴转换成其他范畴,同时保留有对象之间映射的限制和映射之间的可组合形式。这样的转换可以让我们知道不同类型范畴之间的相似情况。这里事情才真正变得有趣起来。
Functors
函子
A functor, F
, is a map from one category to another: it maps objects into objects and morphisms into morphisms. But it can’t do it in a haphazard way because that would destroy the very structures that we are after. So we must impose some “obvious” (mathematicians love that word) constraints.
函子F是从一个范畴到另一个范畴的转换。它把对象对应到对象,把映射对应到映射。但是不能任意对应,因为这样会破坏我们关注的结构,所以我们需要施加一些“明显的”(数学家喜欢这个词)限制。
First of all, if you have a morphism between two objects in the first category then it better be mapped into a morphism between the corresponding objects in the second category. Fig 2 explains this diagrammatically. Object A is mapped into F(A), object B into F(B). A morphism f from A to B is mapped into a morphism F(f) from F(A) to F(b). Mathematicians say that such diagram must commute, that is the result must be the same whether you go from A to F(A) and then apply F(f), or first apply f and then go from B to F(B).
第一,如果在第一个范畴中两个对象之间有一个映射,它最好对应到对应范畴的对应对象之间的那个映射。图2解释了这种情况。对象A对应于F(A),对象B对应于F(B)。A到B之间的映射f则对应于F(A)和F(B)之间的映射F(f)。数学家说这样的图必须是可通的,这意味着无论从A到F(A)然后施加F(f),还是先施加f然后在从B到F(B),得到的结果都是相同的。
Fig 2. Diagram showing the action of a functor F on objects A and B and a morphism f. The bottom part lives in F's domain (source) category, the top part in its codomain (the target).
图2。图示函子F在对象A和B以及映射f上的行为。下面部分粗在与F的作用域(源范畴),上面部分是其值域(目标范畴)。
Moreover, such mapping should preserve the composition property of morphisms. So if morphism h
is a composition of f
and g
, then F(h)
must be a composition of F(f)
and F(g)
. And, of course, the functor must map identity morphisms into identity morphisms.
除此之外,映射之间的组合属性也应该保留。因此如果映射h是f和g的组合,那么F(h)就必须是F(f)和F(g)的组合。当然,函子也必须把单元映射对应到单元映射。
To get a feel for how constrained functors are by these conditions, consider how you could map the category in Fig 1 into itself (such a functor just rearranges things inside one category). There are two trivial mappings that collapse both objects into one (either A or B), and turn all morphisms into identity. Then there is the identity functor that maps both objects into themselves and all morphisms into themselves. Finally, there is just one “interesting” functor that maps A into B and B into A with f and g switching roles. Now imagine a similar category but with the g arrow removed (yes, it’s still a category). Suddenly there is no functor other than the collapsing ones between Fig 1 and that new category. That’s because the two categories have completely different structure.
为了理解有了这些限制的函子是什么样子,考虑如果才能后把图1中的范畴对应到它自己(这样的函子仅仅是重新排列范畴中的东西)。有两种简单的函子可以把是所有的对象都坍缩到其中一个(或者A或者B),并且把所有的映射都对应到单位映射。然后有一个单位函子可以把两个对象以及所有的映射都对应到它们自己。最后,还有一个“有趣的”函子可以把A对应到B,把B对应到A,f和g的功能对换。现在想象一个类似的范畴但是删除箭头g(是的,这仍然是范畴)。突然间,在图1和这个新的范畴之间,除了坍缩函子其他所有的函子都不存在。这是因为这两个范畴有着全然不同的结构。
What’s the intuition behind the statement that mappings expose the structure of the system? Consider the schematic of the London underground in Fig 3. It’s just a bunch of circles and lines. It’s only relevant because there is a mapping between the city of London and this schematic. The circles correspond to tube stations and the lines to train connections. Most importantly, if trains run between two stations, the corresponding circles in the diagram are connected by lines and vice versa: these are the constraints that the mapping preserves. The schematic shows a certain structure that exists in London (mostly hidden underground) which is made apparent by the mapping.
这种系统结构对象的称述背后的直觉是什么呢?看下面的图3中的伦敦地铁概要图。这仅仅是一些环和线。仅仅当把这图和伦敦地铁联系起来的时候它们在有意义。这些环代表了车站,而线代表了行车线路。更重要的是,如果火车运行在两站之间,对应的环在途中是用线连接的,相反也是如此。所以这里面有一些一直保持的对应约定。这张图展示了存在于伦敦的某种结构(大部分在地下),通过对应表示出来。
Interestingly, what I’m doing here is also mapping: London and the underground map correspond to two categories. Trains stations/circles are objects and train connections/lines are morphism. How’s that for an example?
有趣的是,这里我要处理的还是对应:伦敦和这个地铁图对应于两个范畴。车站/环是对象而连接/线是映射。这个例子如何?
Let me now jump into more familiar territory. Since we are mostly interested in one category, Hask, let me define a functor that maps that category into itself (such functors are called endofunctors). An object in Hask is a type, so our functor must map types into types. The way to look at it is that a functor in Hask constructs one type from another– it’s a type constructor. Don’t get confused by the name: a type constructor creates a new type in your program, but that type has already existed in Hask.
现在跳回到熟悉的领域。既然我们只关心一个范畴,Hask,那么我们先定义一个对应到自己的函子(这样的函子称为自函子)。Hask中的对象是类型,因此这个函子必须把类型对应到类型。以函子的方式看待Hask中从一个类型构造另一个类型的东西,那就是类型构造器。不要被这个名字迷惑:类型构造器在你的程序中返回一个新的类型,但是这个类型在hask中已经存在。
A classical example is the list type constructor. Given any type it constructs a list of that type. Type Integer
is mapped into list of integers or, in Haskell notation, [Integer]
. Notice that this is not a map defined on integer values, like 1, 2, or 3. It also doesn’t add a new type to Hask– the type [Integer]
is already there. It just maps one type into another. For C++ programmers: think of mapping type T into a container of T; for instance, std::vector<T>
.
一个经典的例子就是列表类型构造器。从给定的任意类型构造一个该类型的列表。类型Integer对应到整数的列表,用Haskell的符号表示,那就是[Integer]。注意这不是从定义在整数值,如1,2,3上的。也没有在Hask中增加新的类型——类型[Integer]一直就在那里。这仅仅是把一个类型对应到另一种类型。对C++程序员来说,可以想象把类型T对应到T的容器,如std::vector<T>。
Mapping the types is the easy part, what about functions? We have to find a way to take a particular function and map it into a function on lists. That’s also easy: apply the function to each element of the list in turn. There is a (higher level) function in Haskel that does it. It’s called map
and it takes a function and a list and returns a new list (or, because of currying, you may say that it takes a function and returns a function acting on lists). In C++ there is a corresponding template function called std::transform
(well, it takes two iterators and a function object, but the idea is the same).
类型的对应是简单的部分,函数对应呢?我们必须找到一种方法,可以把一个特定函数对应到工作在列表上的函数。这也不难:在列表上对每个元素调用函数即可。Haskell中有个名为map的(高级)函数,它使用一个函数和一个列表作为参数,返回一个新的列表(或者,由于Currying,你也可以说它只带一个函数作为参数,返回一个作用在列表上的函数)。C++中,有一个对应的模板函数std::transform。(好吧,它带两个迭代器和一个函数对象,但是思想是一致的)。
Mathematicians often use diagrams to illustrate the properties of morphisms and functors (see Fig 2). The arrows for morphisms are usually horizontal, while the arrows for functors are vertical (going up). That’s why the mapping of morphisms under a functor is often called lifting. You can take a function operating on integers and “lift it” (using a functor) to a function operating on lists of integers, and so on.
数学家常常使用图来说明映射和函子的属性(见图2).用来表示映射的箭头一般是水平的,而函子的箭头一般的垂直的(向上)。这就是为什么函子下映射的对应也常被称为“提升”。以可以把一个作用在整数的函数“提升”为作用在整数列表上的函数,等等。
The list functor obviously preserves function composition and identity (I’ll leave it as an easy but instructive exercise for the reader).
列表函子显然保留了函数组合和单元映射(我把它当作一个简单但是有意义的练习给读者)。
And now for another moment of Zen. What’s the second most important property of programming? Reusability! Look what we have just done: We took all the functions we’ve implemented so far and lifted them to the level of lists. We’ve got functions operating on lists essentially for free (well, we’ve got a small but important subset of those functions). And the same trick may be applied to all kinds of containers, arrays, trees, queues, unique_ptr
s and more.
现在到了另一个顿悟点。什么是编程的第二重要属性?可复用性!看看我们刚才做了什么:我们把我们目前实现的所有函数提升到可以在列表上可以工作的层次。我们基本是不花什么(好吧,我们得到的是那些函数的一个小且重要的子集)就取得了在列表上操作的函数。而且同样的技巧可以使用所有的容器,什么数组啦,树啦,队列啦,unique_ptr啦等等。
It’s all beautiful, but you don’t really need category theory to apply functions to lists. Still it’s always good to see patterns in programming, and this one is definitely a keeper. The real revolution starts with monads. And, guess what, the list functor is actually a monad. You just need a few more ingredients.
这很漂亮,而且你也不需要什么范畴论就可以在列表上使用函数。同样,能看到编程中的模式总是好事情,这个则更是基本。真正的变革从单子。猜猜怎么?列表函数正好就是单子。你需要的就是更多的配料而已
Endofunctors
自函子
Mathematicians love mappings that preserve “obvious” constraints. As I explained, such mappings abstract inner structures away from the details of implementation. But you can also learn a lot about structure by studying non-trivial mappings into itself. Functors that map a category into itself are called endofunctors (like endo-scopes they let you look inside things). If functors expose similarities, endofunctors expose self-similarities. Take one look at the fractal fern, Fig 4, and you’ll understand how powerful self-similarity can be.
数学家喜欢保留了“明显”约束的对应。如上所述,这样的对应了内部结构和实现细节抽象开来。然而你仍然可以从研究重要的自我对应中学到很多东西。把一个范畴映射到它自身的函子称为自函子(就像内窥镜可以让你看到内部一样)。如果函子显示出相似性,那么自函子就显示出自相似。看下面的蕨类分形,你就知道自相似是可以多么地强大!
With a little bit of imagination you can see the list functor exposing fern-like structures inside Hask (Fig 5). Chars fan out into lists of Chars, which then fan out into lists of lists of Chars, and so on, ad infinitum. Horizontal structures described by functions from Char
to Bool
are reflected at higher and higher levels as functions on lists, lists of lists, etc.
用一点想象力就可以注意到Hask中列表函数表现出蕨类分形类似的结构(图5)。字符聚集为字符列表;字符列表聚集为字符列表的列表,如此以至无穷。由从Char到Bool的函数描述的水平结构反映了更高级别的列表函数,列表的列表函数,等等。
Fig 5. The action of the list type constructor reveals fractal-like structure inside Hask. The functor lifts things up, the functions act horizontally.
图5 列表构造器的行为揭示了Hask内类型分形的结构。函子把类型提升,而函数水平地起作用。
A C++ template that takes a type parameter could be considered a type constructor. How likely is it that it also defines a functor (loosely speaking– C++ is not as mathematized as Haskell)? You have to ask yourself: Is the type parameter constrained in any way? It’s often hard to say, because type constraints are implicit in the body of a template and are tested only during instantiation. For instance, the type parameter for a std::vector
must be copyable. That eliminates, for instance, classes that have private or deleted
(in C++0x) copy constructors. This is not a problem though, because copyable types form a subcategory (I’m speaking really loosely now). The important thing is that a vector of copyable is itself copyable, so the “endo-” part of the endomorphism holds. In general you want to be able to feed the type created by the type constructor back to the type constructor, as in std::vector<std::vector<Foo>>
. And, of course, you have to be able to lift functions in a generic way too, as in std::transform
.
带一个类型参数的C++模板可以看作是类型构造器。同样定义一个函子的话有多相似呢(不严格地说,C++不向Haskell那样数学化)?你必须问你自己:这个类型参数有什么约束?这常常很难回答,因为在模板中类型约束在隐含的,只有在实例化的时候才被检查。例如,std::vector的类型参数必须可拷贝。这就限制了,比如说那些把拷贝构造函数声明为私用或者deleted(C++0x中)的类。然而这并不是问题,因为可拷贝的类型形成了一个子范畴(这里我说的很不严格)。重要的是一个可拷贝的vector本身也是可拷贝的,因此满足自映射中“自”的要求。一般来说,你可以把类型构造器构造的哦类型作为参数重传给类型构造器,就像这样std::vector<std::vector<Foo>>。当然,你也更一般地把函数提升到一个更泛化的层次,如std::transform实现的那样。
Monads
单子
Ooh, Monads!
–Haskell Simpson
It’s time to finally lift the veil. I’ll start with the definition of a monad that builds on the previous sections and is mostly used by mathematicians. There is another one that’s less intuitive but easier to use in programming. I’ll leave that one for later.
是到揭开最后面纱的时候了。我会从单子的定义开始,该定义基于前面的章节而且被数学家广泛使用。还有一个稍微不那么直观但是更在编程中易于使用的定义,我留待后面。
A monad is an endofunctor together with two special families of morphisms, both going vertically, one up and one down (for “directions” see Fig 5). The one going up is called unit and the one going down is called join.
单子是一个带有两种特殊映射簇的自函子。这些映射簇都是垂直的,一个向上一个向下(参见图5中的“方向”)。向上的那个称为单元,向下的那个称为联合。
Now we are juggling a lot of mappings so let’s slow down to build some intuition. Remember, a functor maps objects: in our case, types, which are sets of values. The functor doesn’t see what’s inside the objects; morphisms, in general, do. In our case, a morphism is a function that maps values of one type into values of another type. Our functors, which are defined by type constructors, usually map poorer types into richer types; in the sense that Bool contains just two elements, True and False, but [Bool] contains infinitely many lists.
我们现在以及把玩了不少对应关系了,现在先停一下,建立一点直觉。记住,函子把对象对应起来。我们的例子中,对象就是类型,它是一些值的集合。函子看不到对象的内部是什么,然而映射一般可以看到。我们的例子中,映射就是一个把一种类型的值对应到另一种类型的值的函数。我们的由类型构造器定义的函子一般把较简单的类型对应到较丰富的类型;Bool仅仅包含两个值,True和False,但是[Bool]却包含无穷多的序列。
Unit takes a value from the poorer type, then picks one value from the richer type, and pronounces the two roughly equivalent. Such a rough equivalent of True from the Bool object is the one-element list [True] from the [Bool] object. Similarly, unit would map False into [False]. It would also map integer 5 into [5] and so on.
单元从较简单的类型中取一个值,然后从叫丰富的类型区一个值,然后宣布它们等价。Bool对象中True粗略等价与从[Bool]对象中的[True]。类似,单元应该把False对应于[False],把整数5对应于[5].如此等等。
Unit can be though of as immersing values from a lower level into the higher level in the most natural way possible. By the way, in programming we call a family of functions defined for any type a polymorphic function. In C++, we would express unit as a template, like this:
从最自然的方式考虑,单元可以想象为把从底层的值包装为高层。还有,编程中我们把对任何类型都适用的一簇函数称为多态函数。C++中,我们可以把单元表达为模板:
template<class T>
std::vector<T> unit(T value) {
std::vector<T> vec;
vec.push_back(value);
return vec;
}
To explain join, imagine the functor acting twice. For instance, from a given type T
the list functor will first construct the type [T]
(list of T
), and then [[T]]
(list of list of T
). Join removes one layer of “listiness” by joining the sub-lists. Plainly speaking, it just concatenates the inner lists. Given, for instance, [[a, b], [c], [d, e]]
, it produces [a, b, c, d, e]
. It’s a many-to-one mapping from the richer type to the poorer type and the type-parameterized family of joins also forms a polymorphic function (a template, in C++).
要解释联合,可以想象一个执行两次的函子。例如。给定类型T,列表构造器首先构造出类型[T](T的列表),然后是[[T]](T的列表的列表)。联合通过把子序列连接在一起消除了“列表”的过程。大白话说就是它仅仅被内部列表给链起来。例如,给定[[a, b], [c], [d, e]],联合会输出[a, b, c, d, e]。这是一个多对一的对应,从较丰富的类型对应到较简单的类型,而且一簇类型参数话的联合也形成一个对台函数(C++中就是模板)。
There are a few monadic axioms that define the properties of unit and join (for instance that unit an join cancel each other), but I’m not going to elaborate on them. The important part is that the existence of unit and join imposes new constraints on the endofunctor and thus exposes even more structure.
单元和联合的属性是由几个一元公理定义的,我不会详述。重要的单元和联合的存在给自函子施加了新的约束,这样展示了更多的结构。
Mathematicians look at join
as the grandfather of all multiplication with unit
being its neutral element. It’s heaven for mathematicians because multiplication leads to algebraic structures and indeed monads are great for constructing algebras and finding their hidden properties.
数学家把联合看作是所有乘法的祖父,而单元被作为其中性元。这对于数学家很重要,因为乘法导出了代数结构而实际上单子对于构造代数以及发现其隐含的属性非常有好处。
Unlike mathematicians, we programmers are not that interested in algebraic structures. So there must be something else that makes monads such a hit. As I mentioned in the beginning, in programming we often face problems that don’t naturally translate into functional paradigm. There are some types of computations that are best expressed in imperative style. It doesn’t mean they can’t be translated into functions, it’s just that the translation is somewhat awkward and tedious. Monads provide an elegant tool to do this translation. Monads made possible the absorption and assimilation of imperative programming into functional programming, so much so that some people claim (tongue in cheek?) that Haskell is the best imperative language. And like all things functional monads are bound to turn around and find their place in imperative programming. But that’s material for my next blog post.
和数学家不一样,我们程序关关注的不是代数结构。因此一定有什么东西使得单子如此重要。我在开始已经说过,编程中,我们常常面临着不能自然地转化为函数式编程范式的问题。有一些计算问题使用命令式的方式表达最有效。这并不意味着它们不能被转换为函数,这只是说这个转化有时候显很勉强或者复杂。单子提供了一个优雅的工具来处理这种转换。单子使得吸收和同化命令式编程到函数式编程成为可能,以至于有些人宣称(开玩笑吗?)Haskell是最好的命令式语言。和所有的其他东西一样,函数式单子在命令式编程中找到了自己的位置。但是这是我的下一篇博客的主题了。
Bibliography
文献
- The Catsters. An excellent series of videos about category theory.
- 关于范畴论的极好的一系列视频
- Mike Vanier, Yet Another Monad Tutorial. Explains Haskel monads in great detail.
- Mike Vanier, 另一个单子教程,极为详尽地解释了Haskell单子
- Dan Pipone, Neighborhood of Infinity. Interesting blog with a lot of insights into category theory and Haskell.
- Dan Pipone. 无限的邻居。有趣的博客文章,提供关于范畴论和Haskell的很多洞察
- Eugenio Moggi, Notions of Computation and Monads. This is a hard core research paper that started the whole monad movement in functional languages.
- Eugenio Moggi, 计算和单子的概念。这是真正的研究论文,开启了函数式语言中单子运动的先河
- Philip Wadler, Monads for Functional Programming. The classic paper introducing monads into Haskell.
- Philip Wadler, 函数式编程中的单子。把单子引进Haskell的经典论文