Typeclassopedia
https://wiki.haskell.org/wikiupload/8/85/TMR-Issue13.pdf
By Brent Yorgey, byorgey@gmail.com
Originally published 12 March 2009 in issue 13 of the Monad.Reader. Ported to the Haskell wiki in November 2011 by Geheimdienst.
This is now the official version of the Typeclassopedia and supersedes the version published in the Monad.Reader. Please help update and extend it by editing it yourself or by leaving comments, suggestions, and questions on the talk page.
1 Abstract
The standard Haskell libraries feature a number of type classes with algebraic or category-theoretic underpinnings. Becoming a fluent Haskell hacker requires intimate familiarity with them all, yet acquiring this familiarity often involves combing through a mountain of tutorials, blog posts, mailing list archives, and IRC logs.
The goal of this document is to serve as a starting point for the student of Haskell wishing to gain a firm grasp of its standard type classes. The essentials of each type class are introduced, with examples, commentary, and extensive references for further reading.
2 Introduction
Have you ever had any of the following thoughts?
- What the heck is a monoid, and how is it different from a monad?
- I finally figured out how to use Parsec with do-notation, and someone told me I should use something called
Applicativeinstead. Um, what?
- Someone in the #haskell IRC channel used
(***), and when I asked Lambdabot to tell me its type, it printed out scary gobbledygook that didn’t even fit on one line! Then someone usedfmap fmap fmapand my brain exploded.
- When I asked how to do something I thought was really complicated, people started typing things like
zip.ap fmap.(id &&& wtf)and the scary thing is that they worked! Anyway, I think those people must actually be robots because there’s no way anyone could come up with that in two seconds off the top of their head.
If you have, look no further! You, too, can write and understand concise, elegant, idiomatic Haskell code with the best of them.
There are two keys to an expert Haskell hacker’s wisdom:
- Understand the types.
- Gain a deep intuition for each type class and its relationship to other type classes, backed up by familiarity with many examples.
It’s impossible to overstate the importance of the first; the patient student of type signatures will uncover many profound secrets. Conversely, anyone ignorant of the types in their code is doomed to eternal uncertainty. “Hmm, it doesn’t compile ... maybe I’ll stick in an fmap here ... nope, let’s see ... maybe I need another (.) somewhere? ... um ...”
The second key—gaining deep intuition, backed by examples—is also important, but much more difficult to attain. A primary goal of this document is to set you on the road to gaining such intuition. However—
- There is no royal road to Haskell. —Euclid
This document can only be a starting point, since good intuition comes from hard work, not from learning the right metaphor. Anyone who reads and understands all of it will still have an arduous journey ahead—but sometimes a good starting point makes a big difference.
It should be noted that this is not a Haskell tutorial; it is assumed that the reader is already familiar with the basics of Haskell, including the standard Prelude, the type system, data types, and type classes.
The type classes we will be discussing and their interrelationships (source code for this graph can be found here):

∗ Apply can be found in the semigroupoidspackage, and Comonad in the comonad package.
- Solid arrows point from the general to the specific; that is, if there is an arrow from
FootoBarit means that everyBaris (or should be, or can be made into) aFoo. - Dotted lines indicate some other sort of relationship.
MonadandArrowApplyare equivalent.ApplyandComonadare greyed out since they are not actually (yet?) in the standard Haskell libraries ∗.
One more note before we begin. The original spelling of “type class” is with two words, as evidenced by, for example, the Haskell 2010 Language Report, early papers on type classes like Type classes in Haskell and Type classes: exploring the design space, and Hudak et al.’s history of Haskell. However, as often happens with two-word phrases that see a lot of use, it has started to show up as one word (“typeclass”) or, rarely, hyphenated (“type-class”). When wearing my prescriptivist hat, I prefer “type class”, but realize (after changing into my descriptivist hat) that there's probably not much I can do about it.
We now begin with the simplest type class of all: Functor.
3 Functor
The Functor class (haddock) is the most basic and ubiquitous type class in the Haskell libraries. A simple intuition is that a Functor represents a “container” of some sort, along with the ability to apply a function uniformly to every element in the container. For example, a list is a container of elements, and we can apply a function to every element of a list, using map. As another example, a binary tree is also a container of elements, and it’s not hard to come up with a way to recursively apply a function to every element in a tree.
Another intuition is that a Functor represents some sort of “computational context”. This intuition is generally more useful, but is more difficult to explain, precisely because it is so general. Some examples later should help to clarify the Functor-as-context point of view.
In the end, however, a Functor is simply what it is defined to be; doubtless there are many examples of Functor instances that don’t exactly fit either of the above intuitions. The wise student will focus their attention on definitions and examples, without leaning too heavily on any particular metaphor. Intuition will come, in time, on its own.
3.1 Definition
Here is the type class declaration for Functor:
Functor is exported by the Prelude, so no special imports are needed to use it. Note that the (<$) operator is provided for convenience, with a default implementation in terms of fmap; it is included in the class just to give Functor instances the opportunity to provide a more efficient implementation than the default. To understand Functor, then, we really need to understand fmap.
First, the f a and f b in the type signature for fmap tell us that f isn’t a concrete type like Int; it is a sort of type function which takes another type as a parameter. More precisely, the kind of f must be * -> *. For example, Maybe is such a type with kind * -> *: Maybe is not a concrete type by itself (that is, there are no values of type Maybe), but requires another type as a parameter, like Maybe Integer. So it would not make sense to say instance Functor Integer, but it could make sense to say instance Functor Maybe.
Now look at the type of fmap: it takes any function from a to b, and a value of type f a, and outputs a value of type f b. From the container point of view, the intention is that fmap applies a function to each element of a container, without altering the structure of the container. From the context point of view, the intention is that fmap applies a function to a value without altering its context. Let’s look at a few specific examples.
Finally, we can understand (<$): instead of applying a function to the values a container/context, it simply replaces them with a given value. This is the same as applying a constant function, so (<$) can be implemented in terms of fmap.
3.2 Instances
∗ Recall that [] has two meanings in Haskell: it can either stand for the empty list, or, as here, it can represent the list type constructor (pronounced “list-of”). In other words, the type [a] (list-of-a) can also be written [] a.
∗ You might ask why we need a separate mapfunction. Why not just do away with the current list-only map function, and rename fmap to mapinstead? Well, that’s a good question. The usual argument is that someone just learning Haskell, when using map incorrectly, would much rather see an error about lists than about Functors.
As noted before, the list constructor [] is a functor ∗; we can use the standard list function map to apply a function to each element of a list ∗. The Maybe type constructor is also a functor, representing a container which might hold a single element. The function fmap g has no effect on Nothing(there are no elements to which g can be applied), and simply applies g to the single element inside a Just. Alternatively, under the context interpretation, the list functor represents a context of nondeterministic choice; that is, a list can be thought of as representing a single value which is nondeterministically chosen from among several possibilities (the elements of the list). Likewise, the Maybe functor represents a context with possible failure. These instances are:
As an aside, in idiomatic Haskell code you will often see the letter f used to stand for both an arbitrary Functor and an arbitrary function. In this document, f represents only Functors, and g or h always represent functions, but you should be aware of the potential confusion. In practice, what f stands for should always be clear from the context, by noting whether it is part of a type or part of the code.
There are other Functor instances in the standard library as well:
Either eis an instance ofFunctor;Either e arepresents a container which can contain either a value of typea, or a value of typee(often representing some sort of error condition). It is similar toMaybein that it represents possible failure, but it can carry some extra information about the failure as well.
((,) e)represents a container which holds an “annotation” of typeealong with the actual value it holds. It might be clearer to write it as(e,), by analogy with an operator section like(1+), but that syntax is not allowed in types (although it is allowed in expressions with theTupleSectionsextension enabled). However, you can certainly think of it as(e,).
((->) e)(which can be thought of as(e ->); see above), the type of functions which take a value of typeeas a parameter, is aFunctor. As a container,(e -> a)represents a (possibly infinite) set of values ofa, indexed by values ofe. Alternatively, and more usefully,((->) e)can be thought of as a context in which a value of typeeis available to be consulted in a read-only fashion. This is also why((->) e)is sometimes referred to as the reader monad; more on this later.
IOis aFunctor; a value of typeIO arepresents a computation producing a value of typeawhich may have I/O effects. Ifmcomputes the valuexwhile producing some I/O effects, thenfmap g mwill compute the valueg xwhile producing the same I/O effects.
- Many standard types from the containers library (such as
Tree,Map, andSequence) are instances ofFunctor. A notable exception isSet, which cannot be made aFunctorin Haskell (although it is certainly a mathematical functor) since it requires anOrdconstraint on its elements;fmapmust be applicable to any typesaandb. However,Set(and other similarly restricted data types) can be made an instance of a suitable generalization ofFunctor, either by makingaandbarguments to theFunctortype class themselves, or by adding an associated constraint.
| Exercises |
|---|
|
3.3 Laws
As far as the Haskell language itself is concerned, the only requirement to be a Functor is an implementation of fmap with the proper type. Any sensible Functor instance, however, will also satisfy the functor laws, which are part of the definition of a mathematical functor. There are two:
∗ Technically, these laws make f and fmap together an endofunctor on Hask, the category of Haskell types (ignoring ⊥, which is a party pooper). See Wikibook: Category theory.
Together, these laws ensure that fmap g does not change the structure of a container, only the elements. Equivalently, and more simply, they ensure that fmap g changes a value without altering its context ∗.
The first law says that mapping the identity function over every item in a container has no effect. The second says that mapping a composition of two functions over every item in a container is the same as first mapping one function, and then mapping the other.
As an example, the following code is a “valid” instance of Functor (it typechecks), but it violates the functor laws. Do you see why?
Any Haskeller worth their salt would reject this code as a gruesome abomination.
Unlike some other type classes we will encounter, a given type has at most one valid instance of Functor. This can be proven via the free theorem for the type of fmap. In fact, GHC can automatically derive Functorinstances for many data types.
∗ Actually, if seq/undefinedare considered, it is possible to have an implementation which satisfies the first law but not the second. The rest of the comments in this section should be considered in a context where seq and undefinedare excluded.
A similar argument also shows that any Functor instance satisfying the first law (fmap id = id) will automatically satisfy the second law as well. Practically, this means that only the first law needs to be checked (usually by a very straightforward induction) to ensure that a Functor instance is valid.∗
| Exercises |
|---|
|
3.4 Intuition
There are two fundamental ways to think about fmap. The first has already been mentioned: it takes two parameters, a function and a container, and applies the function “inside” the container, producing a new container. Alternately, we can think of fmap as applying a function to a value in a context (without altering the context).
Just like all other Haskell functions of “more than one parameter”, however, fmap is actually curried: it does not really take two parameters, but takes a single parameter and returns a function. For emphasis, we can write fmap’s type with extra parentheses: fmap :: (a -> b) -> (f a -> f b). Written in this form, it is apparent that fmap transforms a “normal” function (g :: a -> b) into one which operates over containers/contexts (fmap g :: f a -> f b). This transformation is often referred to as a lift; fmap “lifts” a function from the “normal world” into the “f world”.
3.5 Utility functions
There are a few more Functor-related functions which can be imported from the Data.Functor module.
(<$>)is defined as a synonym forfmap. This enables a nice infix style that mirrors the($)operator for function application. For example,f $ 3applies the functionfto 3, whereasf <$> [1,2,3]appliesfto each member of the list.($>) :: Functor f => f a -> b -> f bis justflip (<$), and can occasionally be useful. To keep them straight, you can remember that(<$)and($>)point towards the value that will be kept.void :: Functor f => f a -> f ()is a specialization of(<$), that is,void x = () <$ x. This can be used in cases where a computation computes some value but the value should be ignored.
3.6 Further reading
A good starting point for reading about the category theory behind the concept of a functor is the excellent Haskell wikibook page on category theory.
4 Applicative
A somewhat newer addition to the pantheon of standard Haskell type classes, applicative functorsrepresent an abstraction lying in between Functor and Monad in expressivity, first described by McBride and Paterson. The title of their classic paper, Applicative Programming with Effects, gives a hint at the intended intuition behind the Applicative type class. It encapsulates certain sorts of “effectful” computations in a functionally pure way, and encourages an “applicative” programming style. Exactly what these things mean will be seen later.
4.1 Definition
Recall that Functor allows us to lift a “normal” function to a function on computational contexts. But fmapdoesn’t allow us to apply a function which is itself in a context to a value in a context. Applicative gives us just such a tool, (<*>) (variously pronounced as "apply", "app", or "splat"). It also provides a method, pure, for embedding values in a default, “effect free” context. Here is the type class declaration for Applicative, as defined in Control.Applicative:
Note that every Applicative must also be a Functor. In fact, as we will see, fmap can be implemented using the Applicative methods, so every Applicative is a functor whether we like it or not; the Functor constraint forces us to be honest.
(*>) and (<*) are provided for convenience, in case a particular instance of Applicative can provide more efficient implementations, but they are provided with default implementations. For more on these operators, see the section on Utility functions below.
∗ Recall that ($) is just function application: f $ x = f x.
As always, it’s crucial to understand the type signatures. First, consider (<*>): the best way of thinking about it comes from noting that the type of (<*>) is similar to the type of ($) ∗, but with everything enclosed in an f. In other words, (<*>) is just function application within a computational context. The type of (<*>) is also very similar to the type of fmap; the only difference is that the first parameter is f (a -> b), a function in a context, instead of a “normal” function (a
https://wiki.haskell.org/Typeclassopedia
Typeclassopedia的更多相关文章
- Typeclassopedia 阅读笔记:导言与 Functor
Typeclassopedia 阅读笔记 本文是对介绍 Haskell 中类型类(type classes)的文档 Typeclassopedia 的阅读笔记和简短总结,包含此文档中重要的知识点.读者 ...
- 给 JavaScript 开发者讲讲函数式编程
本文译自:Functional Programming for JavaScript People 和大多数人一样,我在几个月前听到了很多关于函数式编程的东西,不过并没有更深入的了解.于我而言,可能只 ...
- monad - the Category hierachy
reading the "The Typeclassopedia" by Brent Yorgey in Monad.Reader#13 ,and found that " ...
随机推荐
- Java基础学习总结(72)——提升 java 代码的运行效率
前言 代码 优化 ,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没 ...
- MongoDB整库备份与还原以及单个collection备份、恢复
备份前的检查> show dbsMyDB 0.0625GBadmin (empty)bruce 0.0625GBlocal (empty)test 0.0625GB> use MyDBsw ...
- 清北学堂模拟赛d3t3 c
分析:一开始拿到这道题真的是无从下手,暴力都很难打出来.但是基本的方向还是要有的,题目问的是方案数,dp不行就考虑数学方法.接下来比较难想.其实对于每一行或者每一列,我们任意打乱顺序其实对答案是没有影 ...
- [cogs396] [网络流24题#4] 魔术球 [网络流,最大流,最小路径覆盖]
本题枚举每多一个球需要多少个柱子,可以边加边边计算,每次只需要判断$i-Dinic()$即可:特别注意边界. #include <iostream> #include <algori ...
- windows 2013(codevs 1695)
题目描述 Description 话说adamyi编的Windows 2013超时了(- -!),所以他不得不在自己家门口亲眼见证这个电影般的场景.虽然他不想错过这个美妙的时刻,但是他的肚子一再抗议, ...
- 关于linux中使用vim打开文件出现^M的解决方法
在linux下,不可避免的会用VIM打开一些windows下编辑过的文本文件.我们会发现文件的每行结尾都会有一个^M符号,这是因为 DOS下的编辑器和Linux编辑器对文件行末的回车符处理不一致, 各 ...
- 使用applescript脚本方式以管理员权限运行
- (BOOL) runProcessAsAdministrator:(NSString*)scriptPath withArguments:(NSArray ...
- SpringBoot多数据源改造(二)
在上一篇的内容中,主要介绍了spring boot项目的多数据源改造的涉及的基本配置及改动.在spring项目中,常用Mybatis做ORM操作数据库,并且分页操作是避免不了的. 因此,这一篇主要介绍 ...
- N天学习一个linux命令之du
用途 统计文件或者目录占用硬盘空间大小 用法 du [OPTION] [FILE]du [OPTION] --files0-from=F 常用参数 -a, --all统计所有文件,不仅仅是目录 -b, ...
- ASP.NET—016:ASP.NET中保存文件对话框
本想在asp.net中使用savediallog保存文件,结果提示:当应用程序不是以 UserInteractive 模式执行时显示模式对话框或窗口是无效操作. 在ASP.NET中使用例如以下方式.保 ...