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.

Contents

[hide

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 used fmap 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:

  1. Understand the types.
  2. 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 Foo to Bar it means that every Bar is (or should be, or can be made into) a Foo.
  • Dotted lines indicate some other sort of relationship.
  • Monad and ArrowApply are equivalent.
  • Apply and Comonad 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 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 e is an instance of FunctorEither e a represents a container which can contain either a value of type a, or a value of type e (often representing some sort of error condition). It is similar to Maybe 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 type e 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 the TupleSections 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 type e as a parameter, is a Functor. As a container, (e -> a) represents a (possibly infinite) set of values of a, indexed by values of e. Alternatively, and more usefully, ((->) e) can be thought of as a context in which a value of type e 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 a Functor; a value of type IO a represents a computation producing a value of type a which may have I/O effects. If m computes the value x while producing some I/O effects, then fmap g m will compute the value g x while producing the same I/O effects.
  • Many standard types from the containers library (such as TreeMap, and Sequence) are instances of Functor. A notable exception is Set, which cannot be made a Functor in Haskell (although it is certainly a mathematical functor) since it requires an Ord constraint on its elements; fmap must be applicable to any types a and b. However, Set (and other similarly restricted data types) can be made an instance of a suitable generalization of Functor, either by making a and b arguments to the Functor type class themselves, or by adding an associated constraint.
Exercises
  1. Implement Functor instances for Either e and ((->) e).
  2. Implement Functor instances for ((,) e) and for Pair, defined as 
    data Pair a = Pair a a

    Explain their similarities and differences.

  3. Implement a Functor instance for the type ITree, defined as
    data ITree a = Leaf (Int -> a)
    | Node [ITree a]
  4. Give an example of a type of kind * -> * which cannot be made an instance of Functor (without using undefined).
  5. Is this statement true or false? 
    The composition of two Functors is also a Functor.

    If false, give a counterexample; if true, prove it by exhibiting some appropriate Haskell code.

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.

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
  1. Although it is not possible for a Functor instance to satisfy the first Functor law but not the second (excluding undefined), the reverse is possible. Give an example of a (bogus) Functor instance which satisfies the second law but not the first.
  2. Which laws are violated by the evil Functor instance for list shown above: both laws, or the first law alone? Give specific counterexamples.

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 liftfmap “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 for fmap. This enables a nice infix style that mirrors the ($) operator for function application. For example, f $ 3 applies the function f to 3, whereas f <$> [1,2,3] applies fto each member of the list.
  • ($>) :: Functor f => f a -> b -> f b is just flip (<$), 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的更多相关文章

  1. Typeclassopedia 阅读笔记:导言与 Functor

    Typeclassopedia 阅读笔记 本文是对介绍 Haskell 中类型类(type classes)的文档 Typeclassopedia 的阅读笔记和简短总结,包含此文档中重要的知识点.读者 ...

  2. 给 JavaScript 开发者讲讲函数式编程

    本文译自:Functional Programming for JavaScript People 和大多数人一样,我在几个月前听到了很多关于函数式编程的东西,不过并没有更深入的了解.于我而言,可能只 ...

  3. monad - the Category hierachy

    reading the "The Typeclassopedia" by Brent Yorgey in Monad.Reader#13 ,and found that " ...

随机推荐

  1. [BZOJ 3221][Codechef FEB13] Obserbing the tree树上询问

    [BZOJ 3221]Obserbing the tree树上询问 题目 小N最近在做关于树的题.今天她想了这样一道题,给定一棵N个节点的树,节点按1~N编号,一开始每个节点上的权值都是0,接下来有M ...

  2. [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 ...

  3. Android:阻止输入法将图片压缩变形

    Scrollview定义中添加一行: android:isScrollContainer="false"

  4. 切换div位置

    通过数组来存放div的属性以及属性值,鼠标点击的时候,切换数组中的元素,然后赋值给div <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tr ...

  5. Java怎样获取Content-Type的文件类型Mime Type

    在Http请求中.有时须要知道Content-Type类型,尤其是上传文件时.更为重要.尽管有些办法可以解决,但都不太准确或者繁琐,索性我发现一个开源的类库可以解决相对完美的解决问题,它就是jMime ...

  6. [Android L or M ]解除SwitchPreference与Preference的绑定事件

    需求描写叙述 默认情况,Android的两个控件SwitchPreference和CheckBoxPreference的事件处理是和Preference整个区域的事件绑定在一起的,然而,有时须要将其事 ...

  7. 开源 免费 java CMS - FreeCMS1.9 移动APP生成栏目列表数据

    项目地址:http://www.freeteam.cn/ 生成栏目列表数据 提取当前管理网站下同意移动APP訪问的栏目列表,生成json数据到/site/网站文件夹/mobile/channels.h ...

  8. WPF中控件TextBlock使用(简单)

    TextBlock主要用来显示文字.比方: <TextBlock Name="txtBlockOutpuMessage"   Text="hello" / ...

  9. java调用c++ dll出现中文乱码

    近期的开发用到了使用java调用本机动态连接库的功能,将文件路径通过java调用C++代码对文件进行操作. 在调用中假设路径中包括有中文字符就会出现故障.程序执行就会中止. 以下用一个小样例,来说明记 ...

  10. 学习笔记——DISTINCT

    DISTINCT印象中向来被人诟病,说它效率低下.但网上那些SQL 面试题答案,却时有用之.其中 COUNT(DISTINCT 句式,我以前很少用,这里做个笔记. 为管理岗位业务培训信息,建立3个表: ...