| Classes and types |
| Intermediate Haskell |
Modules |
Back inType basics II we had a brief encounter with type classes as the mechanism used with number types. As we hinted back then, however, classes have many other uses.
Broadly speaking, the point of type classes is to ensure that certain operations will be available for values of chosen types. For example, if we know a type belongs to (or, to use the jargon,instantiates) the classFractional, then we are guaranteed, among other things, to be able to perform real division with its values.[1]
Up to now we have seen how existing type classes appear in signatures such as:
(==)::(Eqa)=>a->a->Bool
Now it is time to switch perspectives. First, we quote the definition of theEq class from Prelude:
classEqawhere(==),(/=)::a->a->Bool-- Minimal complete definition:-- (==) or (/=)x/=y=not(x==y)x==y=not(x/=y)
The definition states that if a typea is to be made aninstance of the classEq it must support the functions(==) and(/=) - theclass methods - both of them having typea -> a -> Bool. Additionally, the class provides default definitions for(==) and(/=)in terms of each other. As a consequence, there is no need for a type inEq to provide both definitions - given one of them, the other will be generated automatically.
With a class defined, we proceed to make existing types instances of it. Here is an arbitrary example of an algebraic data type made into an instance ofEq by aninstance declaration:
dataFoo=Foo{x::Integer,str::String}instanceEqFoowhere(Foox1str1)==(Foox2str2)=(x1==x2)&&(str1==str2)
And now we can apply(==) and(/=) toFoo values in the usual way:
*Main> Foo 3 "orange" == Foo 6 "apple"False*Main> Foo 3 "orange" /= Foo 6 "apple"True
A few important remarks:
Eq is defined in the Standard Prelude. This code sample defines the typeFoo and then declares it to be an instance ofEq. The three definitions (class, data type, and instance) arecompletely separate and there is no rule about how they are grouped. This works both ways: you could just as easily create a new classBar and then declare the typeInteger to be an instance of it.(==) forFoo relies on the fact that the values of its fields (namelyInteger andString) are also members ofEq. In fact, almost all types in Haskell are members ofEq (the most notable exception being functions).Since equality tests between values are commonplace, in all likelihood most of the data types you create in any real program should be members ofEq. A lot of them will also be members of other Prelude classes such asOrd andShow. To avoid large amounts of boilerplate for every new type, Haskell has a convenient way to declare the "obvious" instance definitions using the keywordderiving. So,Foo would be written as:
dataFoo=Foo{x::Integer,str::String}deriving(Eq,Ord,Show)
This makesFoo an instance ofEq with an automatically generated definition of== exactly equivalent to the one we just wrote, and also makes it an instance ofOrd andShow for good measure.
You can only usederiving with a limited set of built-in classes, which are describedvery briefly below:
The precise rules for deriving the relevant functions are given in the language report. However, they can generally be relied upon to be the "right thing" for most cases. The types of elements inside the data type must also be instances of the class you are deriving.
This provision of special "magic" function synthesis for a limited set of predefined classes goes against the general Haskell philosophy that "built in things are not special", but it does save a lot of typing. Besides that, deriving instances stops us from writing them in the wrong way (an example: an instance ofEq such thatx == y would not be equal toy == x would be flat out wrong).[3]
Classes can inherit from other classes. For example, here is the main part of the definition ofOrd in Prelude:
class(Eqa)=>Ordawherecompare::a->a->Ordering(<),(<=),(>=),(>)::a->a->Boolmax,min::a->a->a
The actual definition is rather longer and includes default implementations for most of the functions. The point here is thatOrd inherits fromEq. This is indicated by the=> notation in the first line, which mirrors the way classes appear in type signatures. Here, it means that for a type to be an instance ofOrd it must also be an instance ofEq, and hence needs to implement the== and/= operations.[4]
A class can inherit from several other classes: just put all of its superclasses in the parentheses before the=>. Let us illustrate that with yet another Prelude quote:
class(Numa,Orda)=>Realawhere-- | the rational equivalent of its real argument with full precisiontoRational::a->Rational
This diagram, adapted from the Haskell Report, shows the relationships between the classes and types in the Standard Prelude. The names in bold are the classes, while the non-bold text stands for the types that are instances of each class ((->) refers to functions and[], to lists). The arrows linking classes indicate the inheritance relationships, pointing to the inheriting class.

With all pieces in place, we can go full circle by returning to the very first example involving classes in this book:
(+)::(Numa)=>a->a->a
(Num a) => is atype constraint, which restricts the typea to instances of the classNum. In fact,(+) is a method ofNum, along with quite a few other functions (notably,(*) and(-); but not(/)).
You can put several limits into a type signature like this:
foo::(Numa,Showa,Showb)=>a->a->b->Stringfooxyt=showx++" plus "++showy++" is "++show(x+y)++". "++showt
Here, the argumentsx andy must be of the same type, and that type must be an instance of bothNum andShow. Furthermore, the final argumentt must be of some (possibly different) type that is also an instance ofShow. This example also displays clearly how constraints propagate from the functions used in a definition (in this case,(+) andshow) to the function being defined.
Beyond simple type signatures, type constraints can be introduced in a number of other places:
instance declarations (typical with parametrized types);class declarations (constraints can be introduced in the method signatures in the usual way for any type variable other than the one defining the class[5]);data declarations,[6] where they act as constraints for the constructor signatures.Note
Type constraints indata declarations are less useful than it might seem at first. Consider:
data(Numa)=>Fooa=F1a|F2aString
Here,Foo is a type with two constructors, both taking an argument of a typea which must be inNum. However, the(Num a) => constraint is only effective for theF1 andF2 constructors, and not for other functions involvingFoo. Therefore, in the following example...
fooSquared::(Numa)=>Fooa->FooafooSquared(F1x)=F1(x*x)fooSquared(F2xs)=F2(x*x)s
... even though the constructors ensurea will be some type inNum we can't avoid duplicating the constraint in the signature offooSquared.[7]
To provide a better view of the interplay between types, classes, and constraints, we will present a very simple and somewhat contrived example. We will define aLocated class, aMovable class which inherits from it, and a function with aMovable constraint implemented using the methods of the parent class, i.e.Located.
-- Location, in two dimensions.classLocatedawheregetLocation::a->(Int,Int)class(Locateda)=>MovableawheresetLocation::(Int,Int)->a->a-- An example type, with accompanying instances.dataNamedPoint=NamedPoint{pointName::String,pointX::Int,pointY::Int}deriving(Show)instanceLocatedNamedPointwheregetLocationp=(pointXp,pointYp)instanceMovableNamedPointwheresetLocation(x,y)p=p{pointX=x,pointY=y}-- Moves a value of a Movable type by the specified displacement.-- This works for any movable, including NamedPoint.move::(Movablea)=>(Int,Int)->a->amove(dx,dy)p=setLocation(x+dx,y+dy)pwhere(x,y)=getLocationp
Do not read too much into theMovable example just above; it is merely a demonstration of class-related language features. It would be a mistake to think that every single functionality which might be conceivably generalized, such assetLocation, needs a type class of its own. In particular, if all yourLocated instances should be able to be moved as well thenMovable is unnecessary - and if there is just one instance there is no need for type classes at all! Classes are best used when there are several types instantiating it (or if you expect others to write additional instances) and you do not want users to know or care about the differences between the types. An extreme example would beShow: general-purpose functionality implemented by an immense number of types, about which you do not need to know a thing before callingshow. In the following chapters, we will explore a number of important type classes in the libraries; they provide good examples of the sort of functionality which fits comfortably into a class.
deriving for a few other common classes for which there is only one correct way of writing the instances, and the GHC generics machinery make it possible to generate instances automatically for custom classes.(==) to the values being compared.newtype declarations as well, but nottype.| Classes and types |
| Intermediate Haskell |
Modules >> Standalone programs >> Indentation >> More on datatypes >> Other data structures >> Classes and types >> The Functor class |
| Haskell |
Haskell Basics>>Elementary Haskell>>Intermediate Haskell>>Monads |