[Starting on removing duplicate ch14 material John Goerzen **20080405154115] { hunk ./en/ch04-defining-types.xml 289 + + + There's an example of the good use of distinct data type for + storage: the ClockTime type in the standard + Haskell library moduleFor more information on + modules, see . + System.Time: + + + data ClockTime = TOD Integer Integer + deriving (Eq, Ord) + + + A ClockTime consists of two &Integer; values. The first + is the number of whole seconds since midnight UTC on January 1, 1970. + The second is an additional number of picoseconds. Since an &Integer; + can be negative and is unbounded, a ClockTime can + effectively represent any moment in history down to the picosecond. + + + By using the ClockTime type, rather than a + tuple, it is completely clear when we are dealing with a time + value as opposed to two arbitrary &Integer; values that could + have many other meanings. + hunk ./en/ch04-defining-types.xml 803 + hunk ./en/ch14-data.xml 35 - - Creating New Types - - To create a new type, we use the &data; keyword. You can create an - amazing variety of types using &data;. We'll take a look at them, - starting with the most simple, and moving on to more complex types. - - - - Basic Type Creation - - To create a new type, we use the &data; keyword. In its most simple, - though probably uselessThe built-in type - () carries no data, so there is little need to - define your own unless you are writing FFI interfaces to C types. - , form, you can create a type like this: - - &data1.hs:useless; - - This defines a new type called Silly. There is one - data constructor for - Silly, and it is - Foo. A data constructor is a value or - function that is used to create values of a particular type. - When you write Foo in your - program, this Foo is a value of type - Silly. You could express this in Haskell - as Foo :: Silly. Note that there is no - prohibition against using the same word for the type name and - its constructor. - - - Looking at it with &ghci;, there's not much you can do with it yet: - - &data1.ghci:useless; - - Note that &ghci; doesn't know how to display Foo to - the screen, or how to compare it to itself. That's because we haven't - made our new type a member of the &Show; and &Eq; typeclasses. We'll - make a new example that is a member of these classes, and then we'll - get more information out of &ghci;. For more on typeclasses, refer to - . - - &data1.hs:silly2; - - We've also defined a function that takes any parameter and returns - a value of type Silly2. Let's play with this in - &ghci;. - - &data1.ghci:useless2; - - You can see here how the types interact. Since Foo - is of type Silly and Foo2 is of - type Silly2, you can't compare them directly. - - - Let's now expand on this - foundation with some more things that can be done with &data;. - - - - - Multiple Type Constructors - - One way to make &data; more useful is to have multiple type - constructors. Here's an example: - - &data1.hs:color; - - This defines one new type named Color. You can - create a value of type Color by using any of the - three literals Red, Green, or - Blue. There are several reasons this might be - useful as opposed to using something like a &String; to store simple - color names: - - - You are guaranteed that a Color - always represents one of these three values - When you do pattern matching on a - Color, the compiler can warn you if you don't - consider all three possible values if you use -Wall - It is possible to hide the implementation of - Color from users when exporting symbols from the - module. Do do so, you can include - Color in the export list, but omit the - three constructors. This will let other modules pass - around Color values, but not create or - inspect them. For more information, consult - . - - - - Let's look at some example code that uses the Color: - - &data1.hs:color2; - - That's pretty simple: a function that takes a Color - and converts it to - a &String;. Note that we don't have to deal with the case where the - input to the function is something other than our three colors, because - that can't possibly happen. - - - You use a type defined in this way all the time. Haskell 98 defines - this: - - - data Bool = False | True deriving - (Read, Show, Eq, Ord, Enum, Bounded) - - - As you'll see in the rest of this chapter, many of the core Haskell - features you rely upon are actually defined in the prelude using - &data;. That is, they're not built into the compiler in any special - way. The only thing special about them is that they are loaded for you - by default since they're in the prelude. - - FIXME: have we discussed prelude? - - - - Defining Records - - Custom types need not be defined solely in terms of static - data. Data constructors - can also take parameters. Let's say that we wanted to take our - color example a new direction and let a user represent arbitrary colors - in the RGB (red, green, blue) colorspace. We could use an - (Int, Int, Int) tuple for this, but to illustrate - records, let's define a new type. - - &color.hs:custom; - - This defines a new type CustomColor and one data - constructor also named CustomColor. This type - constructor, however, requires three parameters, all ∬. - - - The data constructor here is a unique beast in Haskell. You can use it - as a function that takes three parameters, and it will return to you - one value ot type CustomColor. You can also use it - to perform pattern matching in function or case definitions. - - - Let's use &ghci; to inspect this for a bit: - - &color.ghci:custom; - - - You can see that CustomColor 100 0 50 returns a - single value of type CustomColor. Now you're - probably wondering how to extract the data from that - CustomColor. We use pattern matching to do that. - Here's how: - - &color.hs:extract; - - We used pattern matching to match the CustomColor. - The three ∬ values were assigned to red, - green, and blue, and then printed - out. The matching is positional; you could just as easily have used - (CustomColor x y z), and x - would have held the first ∬. - You can see that this worked by using &ghci;: - - &color.ghci:extract; - - This sort of record is used in the standard Haskell library in the - System.Time.ClockTime type: - - - data ClockTime = TOD Integer Integer - deriving (Eq, Ord) - - - A ClockTime consists of two &Integer; values. The first - is the number of whole seconds since midnight UTC on January 1, 1970. - The second is an additional number of picoseconds. Since an &Integer; - can be negative and is unbounded, a ClockTime can - effectively represent any moment in history down to the picosecond. - - - This is useful as it is, but when you have half a dozen or more - values to store in your record, it can get annoying to have to match - them all. There's where named fields come in. - - - - - Records with Named Fields - - Our earlier example of a type that holds a color looked easy enough. - But you have to remember the ordering of the fields that are part of - the type. Also, you always have to pattern match all fields, even if - you're interested in only one. Of course, you could write a function - such as getRed to do that, but there's an easier - way. - - - In Haskell, records can have named fields. When you name the fields - in a record, you can still access it just as you would without the - named fields. But you gain two things: automatic functions for - picking out specific fields, plus an easier way to create and update - these records. - - - Let's take a look at a re-designed CustomColor - type that uses named fields: - - &colornamed.hs:custom; - - This record stores exactly the same amount of information as our - ealier CustomColor. But now we can take advantage - of named fields. Let's see how that works with &ghci;: - - &colornamed.ghci:custom; - - First, we inspected the type of the CustomColor2 - constructor. Note that it's type is exactly the same as the - constructor for the type - that didn't use named fields. Then, we created a record - with identical data three different ways. The first way didn't make - use of the named fields. The second and third ways did. Notice that - when you use named fields, you don't have to specify the values in - order. - - - Haskell automatically creates accessor functions for each of the - named fields. For this reason, your field names must be unique in - your entire module. Let's look at how we extract the red component of our - color: - - &colornamed.ghci:extract; - - We can use that to write a modified color2string - function that accesses each named field without having to pattern - match each individual field. For reference, the original - color2string implementation is included here as - color2string2. - - &colornamed.hs:extract; - &colornamed.hs:extract2; - - In the first function, we took a CustomColor2 and - assigned the entire thing to cc. In - color2string2, we used pattern matching to pick it apart up front. - }