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
Applicative
instead. 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 fmap
and 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 semigroupoids
package, and Comonad
in the comonad
package.
- Solid arrows point from the general to the specific; that is, if there is an arrow from
Foo
toBar
it means that everyBar
is (or should be, or can be made into) aFoo
. - Dotted lines indicate some other sort of relationship.
Monad
andArrowApply
are equivalent.Apply
andComonad
are 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 map
function. Why not just do away with the current list-only map
function, and rename fmap
to map
instead? 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 Functor
s.
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 Functor
s, 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 e
is an instance ofFunctor
;Either e a
represents 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 toMaybe
in 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 typee
along 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 theTupleSections
extension 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 typee
as 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 typee
is 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.
IO
is aFunctor
; a value of typeIO a
represents a computation producing a value of typea
which may have I/O effects. Ifm
computes the valuex
while producing some I/O effects, thenfmap g m
will compute the valueg x
while 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 aFunctor
in Haskell (although it is certainly a mathematical functor) since it requires anOrd
constraint on its elements;fmap
must be applicable to any typesa
andb
. However,Set
(and other similarly restricted data types) can be made an instance of a suitable generalization ofFunctor
, either by makinga
andb
arguments to theFunctor
type 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 Functor
instances for many data types.
∗ Actually, if seq
/undefined
are 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 undefined
are 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 $ 3
applies the functionf
to 3, whereasf <$> [1,2,3]
appliesf
to each member of the list.($>) :: Functor f => f a -> b -> f b
is 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 fmap
doesn’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 " ...
随机推荐
- [BZOJ 3221][Codechef FEB13] Obserbing the tree树上询问
[BZOJ 3221]Obserbing the tree树上询问 题目 小N最近在做关于树的题.今天她想了这样一道题,给定一棵N个节点的树,节点按1~N编号,一开始每个节点上的权值都是0,接下来有M ...
- [bzoj2226][Spoj5971]LCMSum_欧拉函数_线性筛
LCMSum bzoj-2226 Spoj-5971 题目大意:求$\sum\limits_{i=1}^nlcm(i,n)$ 注释:$1\le n\le 10^6$,$1\le cases \le 3 ...
- Android:阻止输入法将图片压缩变形
Scrollview定义中添加一行: android:isScrollContainer="false"
- 切换div位置
通过数组来存放div的属性以及属性值,鼠标点击的时候,切换数组中的元素,然后赋值给div <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tr ...
- Java怎样获取Content-Type的文件类型Mime Type
在Http请求中.有时须要知道Content-Type类型,尤其是上传文件时.更为重要.尽管有些办法可以解决,但都不太准确或者繁琐,索性我发现一个开源的类库可以解决相对完美的解决问题,它就是jMime ...
- [Android L or M ]解除SwitchPreference与Preference的绑定事件
需求描写叙述 默认情况,Android的两个控件SwitchPreference和CheckBoxPreference的事件处理是和Preference整个区域的事件绑定在一起的,然而,有时须要将其事 ...
- 开源 免费 java CMS - FreeCMS1.9 移动APP生成栏目列表数据
项目地址:http://www.freeteam.cn/ 生成栏目列表数据 提取当前管理网站下同意移动APP訪问的栏目列表,生成json数据到/site/网站文件夹/mobile/channels.h ...
- WPF中控件TextBlock使用(简单)
TextBlock主要用来显示文字.比方: <TextBlock Name="txtBlockOutpuMessage" Text="hello" / ...
- java调用c++ dll出现中文乱码
近期的开发用到了使用java调用本机动态连接库的功能,将文件路径通过java调用C++代码对文件进行操作. 在调用中假设路径中包括有中文字符就会出现故障.程序执行就会中止. 以下用一个小样例,来说明记 ...
- 学习笔记——DISTINCT
DISTINCT印象中向来被人诟病,说它效率低下.但网上那些SQL 面试题答案,却时有用之.其中 COUNT(DISTINCT 句式,我以前很少用,这里做个笔记. 为管理岗位业务培训信息,建立3个表: ...