Chapter 3 in "Real World Haskell" is about defining your own types, and that is what we are going to talk about here. There are plenty of types already defined, like Int, Integer, and List. But suppose you want to define a type of your own (which you will almost surely want to do in a program of any complexity).
The RWH book tells you that "data" is used to define new types, and indeed it is! They claim that is defines Algebraic Data Types, and warns you not to confuse this with Abstract Data Types, noting that "ADT" could be used for either. This attempted clarification is all but useless without a definition of both of these and some kind of explanation of the supposedly crucial difference. Let's just ignore this and talk about Haskell types, whether they are algebraic or not (the bulk of documentation likes to call them "algebraic").
We could define an alternative to the Bool type by setting up essentially a two valued enumeration as follows:
data Quality = Good | BadThe name of the type is "Quality" and it has two Value constructors, Good and Bad. Note that both types and value constructors must begin with capitals.
We can now assign Good or Bad to variables, but we need to augment out definition if we want to print them:
data Quality = Good | Bad deriving ( Show )Here "Show" is a typeclass, something new and important and vital to understand if you want to work with Haskell. A typeclass is very much not a type. It is behavior that applies to a type, not unlike an "interface" in some other languages. When we say that our new type "Quality" participates in the "Show" typeclass, it means (among other things) that we can do this:
xx = Good main = putStrLn (show xx)Another typeclass we might want to participate in is "Eq", which allows us to compare different instances of our type:
data Quality = Good | Bad deriving ( Show, Eq )This allows us to write code like this:
xx = Good yy = Bad same = Good == Bad main = putStrLn (show same )
type Gadget = (Int, String, String)This makes "Gadget" a synonym for the 3-tuple shown. Truth be know though, it would probably be better to use "data" to create an entirely new type for whatever this is supposed to be.
An important thing to note though is that "type" is just a synonym and "data" creates an entirely new type.
data Gadget = Tool String | Pen String String data Pen = Pen String String deriving (Show) p1 = Pen "Pilot" "Custom 74" main = putStrLn (show p1 )We ignore the Gadget type for now, but I include it to show that a type can have several value constructors with associated values. In the case of "Pen" the name of the type and the name of the value constructor are the same, which is OK. The two strings are the brand and the model in this case. Note that a type like "Pen" is much like a struct in C -- it is just a bundle with a couple of values in it. They don't both have to be strings or the same.
data Pen = Pen { maker :: String, model :: String } deriving (Show)Note that the field names must begin with lower case letters. We can define new instances of this type just as before, and then use these handy field names to access components of the type, sort of like a C struct.
putStrLn (show (maker p1) )Notice though that "maker" is a function that takes the instance of the type as an argument (the reverse order of other languages).
We can also now generate new types like this:
p2 = Pen { maker = "Sailor", model = "Pro Gear" }While more verbose, this makes both for more readable "self documenting" code, and may lead to less bugs if a type has many fields.
Tom's Computer Info / tom@mmto.org