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
A
B
C
D

-- 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
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)
	    
F T F Y

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"
	    

Modules

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
    (MetricUnit(..),
     ImperialUnit(..),
     Measurement(..),
     convert)
where
	    

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)
	    

Homework

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