Lecture 5
Make Your Own Data Type
F12, then click ConsoleF12, then click ConsoleCtrl+Shift+k↓, PgDn, n, j |
next slide |
↑, PgUp, p, k |
prev slide |
Esc |
enables ctrl+f globally |
-- 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"
-- Designed by James C. Sun
-- using pattern matching
convert :: (Double, [Char]) -> (Double, [Char])
convert (n, "m") = (n*1.09361, "yd")
convert (n, "L") = (n*0.264172, "gal")
convert (n, "kg") = (n*2.20462, "lb")
convert (n, "yd") = (n/1.09361, "m")
convert (n, "gal") = (n/0.264172, "L")
convert (n, "lb") = (n/2.20462, "kg")
convert _ = error "conversion not defined"
-- Designed by Anthony A. Teate
-- the "I'll do it my own way!" solution
convert :: (Double, [Char]) -> (Double, [Char])
convert(v,s)=head[(a,b)|(a,b)<-(head[[(v*y,z),(v/y,x)]|(x,y,z)<-[("m",1.09361,"yd"),("L",0.264172,"gal"),("kg",2.20462,"lb")],x==s||z==s]),b/=s]
-- Designed by François Beausoleil
-- the "right way"
module Homework where
data Unit = Meter
| Yard
| Litre
| Gallon
| Kilogram
| Pound
deriving (Ord, Eq)
instance Show Unit where
show Meter = "m"
show Yard = "yd"
show Litre = "l"
show Gallon = "gal"
show Kilogram = "kg"
show Pound = "lb"
factor :: Unit -> Double
factor Meter = 1.09361
factor Litre = 0.264172
factor Kilogram = 2.20462
factor Yard = 1 / (factor Meter)
factor Gallon = 1 / (factor Litre)
factor Pound = 1 / (factor Kilogram)
inverse :: Unit -> Unit
inverse Meter = Yard
inverse Yard = Meter
inverse Litre = Gallon
inverse Gallon = Litre
inverse Kilogram = Pound
inverse Pound = Kilogram
convert :: (Double, Unit) -> (Double, Unit)
convert (n, u) = (n * (factor u), inverse u)
data Measurement = Measurement Double Unit deriving (Eq)
instance Show Measurement where
show (Measurement n u) = (show n) ++ " " ++ (show u)
conv (Measurement n u) = measurement $ convert (n, u)
where measurement (n1, u1) = Measurement n1 u1
Bool = False | True
Int = -231 ... -1 | 0 | 1 ... 231-1
[Char] = [] | Char : [Char]
Double
data keywordIn 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!
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
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
Haskell programs are type safe.
We can't use our MetricUnit type unless a function accepts it
So let's make our own function
Write a simple function that takes in a MetricUnit, and returns its symbol
symbol :: MetricUnit -> String
symbol :: MetricUnit -> String
symbol Meter = "m"
symbol Liter = "L"
symbol KiloGram = "kg"
(clear answer)
(show answer)
Rewrite symbol using guards
symbol :: MetricUnit -> String
symbol :: MetricUnit -> String
symbol x
| x==Meter = "m"
| x==Liter = "L"
| x==KiloGram = "kg"
(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)
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
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 }
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!
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.
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!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)
data Tree = ...
add :: Tree -> Int
