Haskell/Type declarations
From Wikibooks, the open-content textbooks collection
Haskell has three basic ways to declare a new type:
- The data declaration for structures and enumerations.
- The type declaration for type synonyms.
- The newtype declaration, which is a cross between the other two.
In this chapter, we will focus on the most essential way, data, and to make life easier, type. You'll find out about newtype later on, but don't worry too much about it; it's there mainly for optimisation.
[edit] data for making your own types
Here is a data structure for a simple list of anniversaries:
data Anniversary = Birthday String Int Int Int -- name, year, month, day
| Wedding String String Int Int Int -- first name, second name, year, month, day
This declares a new data type Anniversary with two constructor functions called Birthday and Wedding. As usual with Haskell the case of the first letter is important: type names and constructor functions must always start with capital letters. Note also the vertical bar: this marks the point where one alternative ends and the next begins; you can think of it almost as an or - which you'll remember was || - except used in types.
The declaration says that an Anniversary can be one of two things; a Birthday or a Wedding. A Birthday contains one string and three integers, and a Wedding contains two strings and three integers. The comments (after the "--") explain what the fields actually mean.
Now we can create new anniversaries by calling the constructor functions. For example, suppose we have John Smith born on 3rd July 1968:
johnSmith :: Anniversary johnSmith = Birthday "John Smith" 1968 7 3
He married Jane Smith on 4th March 1987:
smithWedding :: Anniversary smithWedding = Wedding "John Smith" "Jane Smith" 1987 3 4
These two objects can now be put in a list:
anniversaries :: [Anniversary] anniversaries = [johnSmith, smithWedding]
(Obviously a real application would not hard-code its entries: this is just to show how constructor functions work).
Constructor functions can do all of the things ordinary functions can do. Anywhere you could use an ordinary function you can use a constructor function.
Anniversaries will need to be converted into strings for printing. This needs another function:
showAnniversary :: Anniversary -> String showAnniversary (Birthday name year month day) = name ++ " born " ++ showDate year month day showAnniversary (Wedding name1 name2 year month day) = name1 ++ " married " ++ name2 ++ " " ++ showDate year month day
This shows the one way that constructor functions are special: they can also be used to deconstruct objects. showAnniversary takes an argument of type Anniversary. If the argument is a Birthday then the first version gets used, and the variables name, month, date and year are bound to its contents. If the argument is a Wedding then the second version is used and the arguments are bound in the same way. The parentheses indicate that the whole thing is one argument split into five or six parts, rather than five or six separate arguments.
Notice the relationship between the type and the constructors. All versions of showAnniversary convert an Anniversary to a String. One of them handles the Birthday case and the other handles the Wedding case.
It also needs an additional showDate routine:
showDate y m d = show y ++ "-" ++ show m ++ "-" ++ show d
Of course, it's a bit clumsy having the date passed around as three separate integers. What we really need is a new datatype:
data Date = Date Int Int Int -- Year, Month, Day
Constructor functions are allowed to be the same name as the type, and if there is only one then it is good practice to make it so.
[edit] type for making type synonyms
It would also be nice to make it clear that the strings in the Anniversary type are names, but still be able to manipulate them like ordinary strings. The type declaration does this:
type Name = String
This says that a Name is a synonym for a String. Any function that takes a String will now take a Name as well, and vice versa. The right hand side of a type declaration can be a more complex type as well. For example String itself is defined in the standard libraries as
type String = [Char]
So now we can rewrite the Anniversary type like this:
data Anniversary = Birthday Name Date | Wedding Name Name Date
which is a lot easier to read. We can also have a type for the list:
type AnniversaryBook = [Anniversary]
The rest of the code needs to be changed to match:
johnSmith :: Anniversary johnSmith = Birthday "John Smith" (Date 1968 7 3) smithWedding :: Anniversary smithWedding = Wedding "John Smith" "Jane Smith" (Date 1987 3 4) anniversaries :: AnniversaryBook anniversaries = [johnSmith, smithWedding] showAnniversary :: Anniversary -> String showAnniversary (Birthday name date) = name ++ " born " ++ showDate date showAnniversary (Wedding name1 name2 date) = name1 ++ " married " ++ name2 ++ showDate date showDate :: Date -> String showDate (Date y m d) = show y ++ "-" ++ show m ++ "-" ++ show d