Online Book Reader

Home Category

Learn You a Haskell for Great Good! - Miran Lipovaca [113]

By Root 544 0
in a list if we want to see only the first element. Here’s an example:

ghci> head [3,4,5,undefined,2,undefined]

3

Now consider the following type:

data CoolBool = CoolBool { getCoolBool :: Bool }

It’s your run-of-the-mill algebraic data type that was defined with the data keyword. It has one value constructor, which has one field whose type is Bool. Let’s make a function that pattern matches on a CoolBool and returns the value "hello", regardless of whether the Bool inside the CoolBool was True or False:

helloMe :: CoolBool -> String

helloMe (CoolBool _) = "hello"

Instead of applying this function to a normal CoolBool, let’s throw it a curveball and apply it to undefined!

ghci> helloMe undefined

"*** Exception: Prelude.undefined

Yikes! An exception! Why did this exception happen? Types defined with the data keyword can have multiple value constructors (even though CoolBool has only one). So in order to see if the value given to our function conforms to the (CoolBool _) pattern, Haskell must evaluate the value just enough to see which value constructor was used when we made the value. And when we try to evaluate an undefined value, even a little, an exception is thrown.

Instead of using the data keyword for CoolBool, let’s try using newtype:

newtype CoolBool = CoolBool { getCoolBool :: Bool }

We don’t need to change our helloMe function, because the pattern-matching syntax is the same whether you use newtype or data to define your type. Let’s do the same thing here and apply helloMe to an undefined value:

ghci> helloMe undefined

"hello"

It worked! Hmmm, why is that? Well, as you’ve learned, when you use newtype, Haskell can internally represent the values of the new type in the same way as the original values. It doesn’t need to add another box around them; it just must be aware of the values being of different types. And because Haskell knows that types made with the newtype keyword can have only one constructor, it doesn’t need to evaluate the value passed to the function to make sure that the value conforms to the (CoolBool _) pattern, because newtype types can have only one possible value constructor and one field!

This difference in behavior may seem trivial, but it’s actually pretty important. It shows that even though types defined with data and newtype behave similarly from the programmer’s point of view (because they both have value constructors and fields), they are actually two different mechanisms. Whereas data can be used to make your own types from scratch, newtype is just for making a completely new type out of an existing type. Pattern matching on newtype values isn’t like taking something out of a box (as it is with data), but more about making a direct conversion from one type to another.

type vs. newtype vs. data


At this point, you may be a bit confused about the differences between type, data, and newtype, so let’s review their uses.

The type keyword is for making type synonyms. We just give another name to an already existing type so that the type is easier to refer to. Say we did the following:

type IntList = [Int]

All this does is allow us to refer to the [Int] type as IntList. They can be used interchangeably. We don’t get an IntList value constructor or anything like that. Because [Int] and IntList are only two ways to refer to the same type, it doesn’t matter which name we use in our type annotations:

ghci> ([1,2,3] :: IntList) ++ ([1,2,3] :: [Int])

[1,2,3,1,2,3]

We use type synonyms when we want to make our type signatures more descriptive. We give types names that tell us something about their purpose in the context of the functions where they’re being used. For instance, when we used an association list of type [(String, String)] to represent a phone book in Chapter 7, we gave it the type synonym of PhoneBook so that the type signatures of our functions were easier to read.

The newtype keyword is for taking existing types and wrapping them in new types, mostly so it’s easier to make them instances of certain type classes. When we use newtype to wrap an existing

Return Main Page Previous Page Next Page

®Online Book Reader