Movatterモバイル変換


[0]ホーム

URL:


Jump to content
WikibooksThe Free Textbook Project
Search

Haskell/Classes and types

From Wikibooks, open books for an open world
<Haskell
(Redirected fromHaskell/Class declarations)
Classes and types
Intermediate Haskell

Modules
Standalone programs
Indentation
More on datatypes
Other data structures
Classes and types
The Functor class

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]

Classes and instances

[edit |edit source]

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:

  • The classEq 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.
  • Classes are not types, but categories of types, and so the instances of a class are types instead of values.[2]
  • The definition of(==) 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).
  • Type synonyms defined withtype keyword cannot be made instances of a class.

Deriving

[edit |edit source]

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:

Eq
Equality operators== and/=
Ord
Comparison operators< <= > >=;min,max, andcompare.
Enum
For enumerations only. Allows the use of list syntax such as[Blue .. Green].
Bounded
Also for enumerations, but can also be used on types that have only one constructor. ProvidesminBound andmaxBound as the lowest and highest values that the type can take.
Show
Defines the functionshow, which converts a value into a string, and other related functions.
Read
Defines the functionread, which parses a string into a value of the type, and other related functions.

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]

Class inheritance

[edit |edit source]

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

Standard classes

[edit |edit source]

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.

Summary of the hierarchy of base typeclasses

Type constraints

[edit |edit source]

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.

Other uses

[edit |edit source]

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]


A concerted example

[edit |edit source]

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

A word of advice

[edit |edit source]

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.

Notes

  1. To programmers coming from object-oriented languages: A class in Haskell in all likelihood isnot what you expect - don't let the terms confuse you. While some of the uses of type classes resemble what is done with abstract classes or Java interfaces, there are fundamental differences which will become clear as we advance.
  2. This is a key difference from most OO languages, where a class is also itself a type.
  3. There are ways to make the magic apply to other classes. GHC extensions allowderiving 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.
  4. If you check the full definition in thePrelude specification, the reason for that becomes clear: the default implementations involve applying(==) to the values being compared.
  5. Constraints for the type defining the class should be set via class inheritance.
  6. Andnewtype declarations as well, but nottype.
  7. Extra note for the curious: This issue is related to some of the problems tackled by the advanced features discussed in the "Fun with types" chapter of the Advanced Track.
Retrieved from "https://en.wikibooks.org/w/index.php?title=Haskell/Classes_and_types&oldid=3809484"
Category:

[8]ページ先頭

©2009-2026 Movatter.jp