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.
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 -- partner1name, partner2name, 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.
這裡定義了一個新的數據類型 Anniversary ，它有兩個 constructor (構造)函數: Birthday 和 Wedding. 通常情況下在Haskell里，單詞的第一個字母大小寫是很重要的: 類型名 和 構造函數必須以大寫開頭.注意這裡的符號'|': 它代表著一個構造函數的結束和一個可選構造函數的開始。你可以把它記成'或' -- '||'，用在指定類型的時候少一豎就可以了.
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:
現在我們可以通過調用兩個構造函數來創建新的紀念日。比如，John Smith 的生日是1968年7月3號：
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 parenthesis 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.
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