Introduction To Haskell

Lecture 5

Make Your Own Data Type

Using These Slides

Every slide has a secret note.

  • On Chrome: press F12, then click Console
  • On IE: press F12, then click Console
  • On Firefox: Ctrl+Shift+k

Shortcut Keys:

, PgDn, n, j next slide
, PgUp, p, k prev slide
Esc enables ctrl+f globally

Review of Homework 4

Write a function to convert units

-- Designed by Matthew R. Bongiovi
-- using guards

convert :: (Double, [Char]) -> (Double, [Char])
convert (x,l)
    | (l == "m") = (1.09361 * x,"yd")
    | (l == "L") = (0.264172 * x,"gal")
    | (l == "kg") = (2.20462 * x,"lb")
    | (l == "yd") = (x / 1.09361, "m")
    | (l == "gal") = (x / 0.264172,"L")
    | (l == "lb") = (x / 2.20462,"kg")
    | otherwise = error "Invalid input"

Data Types

  • Bool = False | True

  • Int = -231 ... -1 | 0 | 1 ... 231-1

  • [Char] = [] | Char : [Char]

  • Double

Making Data Types

  • for well structured code

  • for better readability

  • for improving type safety

data keyword

In a new file MyData.hs

create a Data type called MetricUnits

data MetricUnit = Meter | Liter | KiloGram

MetricUnit is called the type constructor

Everything after the equal sign is called the value constructor

Notice the capitalization!

What Exactly is a Type?

Let's look at Meter, Liter, and KiloGram

Prelude> :type True
Bool :: True

Prelude> :load MyData.hs

Prelude> :type Meter
Meter :: MetricUnit

Prelude> :type Liter
Liter :: MetricUnit

Prelude> :type KiloGram
KiloGram :: MetricUnit

Meter has the type MetricUnit

Remember Show?

Show is a Typeclass that enables data to be shown as a String

Prelude> True

Prelude> Meter
 No instance for (Show MetricUnit) arising from a use of `print'
 Possible fix: add an instance declaration for (Show MetricUnit)

Why did we get this error?

deriving (Show)

  • GHCi is trying to call the print function

  • print only works for things that derive Show

  • but our MetricUnit doesn't derive Show

data MetricUnit = Meter | Liter | KiloGram  deriving (Show)

Type Safety

  • Haskell programs are type safe.

  • We can't use our MetricUnit type unless a function accepts it

So let's make our own function

Function that uses MetricUnit

Write a simple function that takes in a MetricUnit, and returns its symbol

  • Meter → "m"
  • Liter → "L"
  • KiloGram → "kg"

symbol :: MetricUnit -> String

(clear answer) (show answer)

Same Function With Guards

Rewrite symbol using guards

symbol :: MetricUnit -> String

(clear answer) (show answer)

Try compiling it. What happens?

deriving (Eq)

GHCi is yelling at us because we used '=='

    No instance for (Eq MetricUnit) arising from a use of `=='
    Possible fix: add an instance declaration for (Eq MetricUnit)
    In the expression: x == Meter

So MetricUnit should derive Eq in this case

data MetricUnit = Meter | Liter | KiloGram  deriving (Show, Eq)

Metric to Imperial

Let's write a convert function with our types.

We will convert from Metric to Imperial.

Just like last week's homework

But this time it will be elegant!

1. Define MetricUnit and ImperialUnit

data MetricUnit = Meter
                | Liter
                | KiloGram
                  deriving (Show, Eq)

data ImperialUnit = Yard
                  | Gallon
                  | Pound
                    deriving (Show)

2. Create a Measurement Data Type

Look closely. This syntax is new!

data Measurement = MetricMeasurement Double MetricUnit 
                 | ImperialMeasurement Double ImperialUnit 
                   deriving (Show)

(MetricMeasurement 3.1 Liter) :: Measurement (ImperialMeasurement 200 Pound) :: Measurement

3. Create the convert function

convert :: Measurement -> Measurement

Implement it with pattern matching and guards

convert (MetricMeasurement x u)
    | u==Meter    = ImperialMeasurement (1.0936*x) Yard
    | u==Liter    = ImperialMeasurement (0.2642*x) Gallon
    | u==KiloGram = ImperialMeasurement (2.2046*x) Pound

convert (ImperialMeasurement x u)
    | u==Yard   = MetricMeasurement (0.9144*x) Meter
    | u==Gallon = MetricMeasurement (3.7854*x) Liter
    | u==Pound  = MetricMeasurement (0.4536*x) KiloGram
Full code listing here

4. Compile and Test

Prelude> let m = MetricMeasurement 2 Meter

Prelude> convert m
ImperialMeasurement 2.18722 Yard

Prelude> convert (convert m)
MetricMeasurement 2 Meter

Record Syntax

Remember, Haskell is lazy, and so are we.

Creating getter functions is monotonous!

data Point = Point Double Double

xval :: Point -> Double
xval (Point x _) = x

yval :: Point -> Double
yval (Point _ y) = y

Haskell provides a shortcut called record syntax

data Point = Point { xval::Double, yval::Double }

Record Syntax Looks Nice

It makes code more readable

let a = Point 2 3

let b = Point {xval = 2, yval = 3}

There's more to write, but anyone can easily figure out what's going on.

Especially for large data types!

Type Parameters

Oh yea, even types can have arguments.

data ThreeThings a = ThreeThings a a a  deriving (Show)

ThreeThings is bundle of three identical types

Prelude> ThreeThings 1 2 3
ThreeThings 1 2 3

Prelude> ThreeThings "hello" "there" "lad"
ThreeThings "hello" "there" "lad"


We can export useful code to a module

so that other projects can reuse them.

Let's make a module out of our convert code.

Step 1

module MyData

No step 2. That was easy!

Note: your file name should be the same as your module name!

Importing Code

Use convert from anywhere!

-- OtherFile.hs
import MyData

-- display a measurement in Metric
reportMeasurement :: Measurement -> String

-- if metric, print out the measurement
reportMeasurement (MyData.MetricMeasurement x u) 
    = (show x) ++ " " ++ (show u)

-- otherwise if imperial, convert the measurement and try again
reportMeasurement m 
    = reportMeasurement (convert m)


Trees in Haskell

  1. Fill out this week's form!
  2. Create a binary tree data type of Int nodes
  3. data Tree = ... 
  4. Write a function to sum all nodes in the tree
  5. add :: Tree -> Int