Online Book Reader

Home Category

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

By Root 446 0
list type. Although there’s some syntactic sugar in play, the list type takes a parameter to produce a concrete type. Values can have an [Int] type, a [Char] type, or a [[String]] type, but you can’t have a value that just has a type of [].

Note

We say that a type is concrete if it doesn’t take any type parameters at all (like Int or Bool), or if it takes type parameters and they’re all filled up (like Maybe Char). If you have some value, its type is always a concrete type.

Let’s play around with the Maybe type:

ghci> Just "Haha"

Just "Haha"

ghci> Just 84

Just 84

ghci> :t Just "Haha"

Just "Haha" :: Maybe [Char]

ghci> :t Just 84

Just 84 :: (Num a) => Maybe a

ghci> :t Nothing

Nothing :: Maybe a

ghci> Just 10 :: Maybe Double

Just 10.0

Type parameters are useful because they allow us to make data types that can hold different things. For instance, we could make a separate Maybe-like data type for every type that it could contain, like so:

data IntMaybe = INothing | IJust Int

data StringMaybe = SNothing | SJust String

data ShapeMaybe = ShNothing | ShJust Shape

But even better, we could use type parameters to make a generic Maybe that can contain values of any type at all!

Notice that the type of Nothing is Maybe a. Its type is polymorphic, which means that it features type variables, namely the a in Maybe a. If some function requires a Maybe Int as a parameter, we can give it a Nothing, because a Nothing doesn’t contain a value anyway, so it doesn’t matter. The Maybe a type can act like a Maybe Int if it must, just as 5 can act like an Int or a Double. Similarly, the type of the empty list is [a]. An empty list can act like a list of anything. That’s why we can do [1,2,3] ++ [] and ["ha","ha","ha"] ++ [].

Should We Parameterize Our Car?


When does using type parameters make sense? Usually, we use them when our data type would work regardless of the type of the value it then holds, as with our Maybe a type. If our type acts as some kind of box, it’s good to use parameters.

Consider our Car data type:

data Car = Car { company :: String

, model :: String

, year :: Int

} deriving (Show)

We could change it to this:

data Car a b c = Car { company :: a

, model :: b

, year :: c

} deriving (Show)

But would we really benefit? Probably not, because we would just end up defining functions that work on only the Car String String Int type. For instance, given our first definition of Car, we could make a function that displays the car’s properties in an easy-to-read format.

tellCar :: Car -> String

tellCar (Car {company = c, model = m, year = y}) =

"This " ++ c ++ " " ++ m ++ " was made in " ++ show y

We could test it like this:

ghci> let stang = Car {company="Ford", model="Mustang", year=1967}

ghci> tellCar stang

"This Ford Mustang was made in 1967"

It’s a good little function! The type declaration is cute, and it works nicely.

Now what if Car was Car a b c?

tellCar :: (Show a) => Car String String a -> String

tellCar (Car {company = c, model = m, year = y}) =

"This " ++ c ++ " " ++ m ++ " was made in " ++ show y

We would need to force this function to take a Car type of (Show a) => Car String String a. You can see that the type signature is more complicated, and the only actual benefit would be that we could use any type that’s an instance of the Show type class as the type for c:

ghci> tellCar (Car "Ford" "Mustang" 1967)

"This Ford Mustang was made in 1967"

ghci> tellCar (Car "Ford" "Mustang" "nineteen sixty seven")

"This Ford Mustang was made in \"nineteen sixty seven\""

ghci> :t Car "Ford" "Mustang" 1967

Car "Ford" "Mustang" 1967 :: (Num t) => Car [Char] [Char] t

ghci> :t Car "Ford" "Mustang" "nineteen sixty seven"

Car "Ford" "Mustang" "nineteen sixty seven" :: Car [Char] [Char] [Char]

In real life though, we would end up using Car String String Int most of the time. So, parameterizing the Car type isn’t worth it.

We usually use type parameters when the type that’s contained inside the data type’s various value constructors isn’t really that important for the type to work.

Return Main Page Previous Page Next Page

®Online Book Reader