Let’s see how Bool
is implemented.
data Bool = False | True
a bool is either False or True.
Here’s how Int is defined:
data Int = -214783648 | ... | -1 | 0 | 1 | ... | 214783647
Let’s make our own type to represent a shape:
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
A Shape can either be a Circle that takes three Float values, or a Rectangle, which takes four Float values.
> :t Circle
ghciCircle :: Float -> Float -> Float -> Shape
> :t Rectangle
ghciRectangle :: Float -> Float -> Float -> Float -> Shape
Let’s make a function that takes a Shape and returns its area.
surface :: Shape -> Float
Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x2) * (abs $ y2 - y1) surface (
Here’s how that works:
> surface $ Circle 10 20 10
ghci314.15927
> surface $ Rectangle 0 0 100 100
ghci10000.0
We can’t print out the circle, until we have it derive show:
data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)
Now we can print out the Circle:
> Circle 10 20 5
ghciCircle 10.0 20.0 5.0
> Rectangle 50 230 60 90
ghciRectangle 50.0 230.0 60.0 90.0
Value constructors are functions, so we can map them and partially apply them:
> map (Circle 10 20) [4,5,6,6]
ghciCircle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0] [
Let’s make an intermediate Point to simplify our definition:
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
We can simplify our surface definition now:
surface :: Shape -> Float
Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1) surface (
Now this works fine.
> surface (Rectangle (Point 0 0) (Point 100 100))
ghci10000.0
> surface (Circle (Point 0 0) 24)
ghci1809.5574
We can add a nudge function to move it around as well:
nudge :: Shape -> Float -> Float -> Shape
Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1+a) (y1+b)) (Point (x2+a) (y2+b)) nudge (
> nudge (Circle (Point 34 34) 10) 5 10
ghciCircle (Point 39.0 44.0) 10.0
How about some base constructors?
baseCircle :: Float -> Shape
= Circle (Point 0 0) r
baseCircle r
baseRect :: Float -> Float -> Shape
= Rectangle (Point 0 0) (Point width height) baseRect width height
And their use:
> nudge (baseRect 40 100) 60 23
ghciRectangle (Point 60.0 23.0) (Point 100.0 123.0)
How would we export this?
module Shapes
Point(..)
( Shape(..)
,
, surface
, nudge
, baseCircle
, baseRectwhere )
Here’s a data type that describes a person: Their: first name last name age height phone number favorite ice cream flavor
data Person = Person String String Int Float String String deriving (Show)
It’s very cumbersome to use this without labeling them.
> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
ghci> guy
ghciPerson "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"
Here’s how we create value constructors for everything:
firstName :: Person -> String
Person firstname _ _ _ _ _) = firstname
firstName (
lastName :: Person -> String
Person _ lastname _ _ _ _) = lastname
lastName (
age :: Person -> Int
Person _ _ age _ _ _) = age
age (
height :: Person -> Float
Person _ _ _ height _ _) = height
height (
phoneNumber :: Person -> String
Person _ _ _ _ number _) = number
phoneNumber (
flavor :: Person -> String
Person _ _ _ _ _ flavor) = flavor flavor (
This is a bit painful and difficult to remember, so Haskell provided us with record syntax:
data Person = Person {
firstName :: String
lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String
,deriving (Show) }
This also provides us with getters for that person.
So we can do this just like the previous example:
> let guy = Person "Buddy" "Finklestein" 43 184.2 "526-2928" "Chocolate"ghci> firstName guy
ghci"Buddy"
Record syntax is generally preferred when the order of values is vague, and it’s not obvious which field is which.
A value constructor can take a value (or set of values) of some types and return a value of a different type.
The Maybe type is defined like this:
data Maybe a = Nothing | Just a
If this type is Null, it returns Left
, whereas if it
holds something, it returns Right
which is
Just a
.
Since Maybe
is generic, it works with any type.
Playing around with that:
> Just "Haha"
ghciJust "Haha"
> Just 84
ghciJust 84
> :t Just "Haha"
ghciJust "Haha" :: Maybe [Char]
> :t Just 84
ghciJust 84 :: (Num t) => Maybe t
> :t Nothing
ghciNothing :: Maybe a
> Just 10 :: Maybe Double
ghciJust 10.0
This is useful for some type definitions, like Map.
data (Ord k) => Map k v = ...
We can require that the Ord typeclass has to be satisfied for a key, so the Map can be ordered.
Let’s create a Vector 3D vector type and add operations for it. In the type, don’t add the constraint, because we have to for the functions.
data Vector a = Vector a a a deriving (Show)
vplus :: (Num t) => Vector t -> Vector t -> Vector t
Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
(
vectMult :: (Num t) => Vector t -> t -> Vector t
Vector i j k) `vectMult` m = Vector (i*m) (j*m) (k*m)
(
scalarMult :: (Num t) => Vector t -> Vector t -> t
Vector i j k) `scalarMult` (Vector l m n) = i*l + j*m + k*n (
Typeclasses are more like interfaces with constraints that can be automatically derived.
data Person = Person {
firstName :: String
lastName :: String
, age :: Int
,deriving (Show, Read, Eq) }
We’ll create a person type and then derive Show, Read and Eq for it.
> let mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghci> let adRock = Person {firstName = "Adam", lastName = "Horovitz", age = 41}
ghci> let mca = Person {firstName = "Adam", lastName = "Yauch", age = 44}
ghci> mca == adRock
ghciFalse
> mikeD == adRock
ghciFalse
> mikeD == mikeD
ghciTrue
> mikeD == Person {firstName = "Michael", lastName = "Diamond", age = 43}
ghciTrue
Show is like toString, as it creates a string representation of our type, whereas read turns a string representation of our type into the type.
> read "Person {firstName ="Michael", lastName ="Diamond", age = 43}" :: Person
ghciPerson {firstName = "Michael", lastName = "Diamond", age = 43}
Ord
is another typeclass that allows for creating an
order.
If we make our Bool
typeclass like this:
data Bool = False | True deriving (Ord)
We can compare them:
> True `compare` False
ghciGT
> True > False
ghciTrue
> True < False
ghciFalse
Likewise, Maybe
derives Ord
as well.
data Maybe a = Nothing | Just a deriving (Ord)
> Nothing < Just 100
ghciTrue
> Nothing > Just (-49999)
ghciFalse
> Just 3 `compare` Just 2
ghciGT
> Just 100 > Just 50
ghciTrue
We can use Algebraic Data Types to make enumerations of the
Enum
(a type with no value constructor), and
Bounded
, which has a lowest possible value and a highest
possible value.
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum)
We can use Show and Read:
> Wednesday
ghciWednesday
> show Wednesday
ghci"Wednesday"
> read "Saturday" :: Day
ghciSaturday
We can use Eq and Ord:
> Saturday == Sunday
ghciFalse
> Saturday == Saturday
ghciTrue
> Saturday > Friday
ghciTrue
> Monday `compare` Wednesday
ghciLT
We can use Bounded:
> minBound :: Day
ghciMonday
> maxBound :: Day
ghciSunday
We can use succ and Pred and make ranges:
> succ Monday
ghciTuesday
> pred Saturday
ghciFriday
> [Thursday .. Sunday]
ghciThursday,Friday,Saturday,Sunday]
[> [minBound .. maxBound] :: [Day]
ghciMonday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday] [
Remember that the String
type means
[Char]
.
In Haskell that could be represented like this:
type String = [Char]
Likewise, we could simplify our Data.Map
module’s
types:
phoneBook :: [(String,String)]
= [
phoneBook
(
,(
,(
,(
,(
,( ]
We know that this is a mapping of String
->
String
, but we could make a type synonym and make that
easier to understand.
type PhoneNumber = String
type Name = String
type PhoneBook = [(Name,PhoneNumber)]
Type synonyms can also be parameterized:
type AssocList k v = [(k,v)]
Let’s talk about the Either
type:
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
We can pattern match Left and Right based on different stuff and see which one of them it was:
> Right 20
ghciRight 20
> Left "w00t"
ghciLeft "w00t"
> :t Right 'a'
ghciRight 'a' :: Either a Char
> :t Left True
ghciLeft True :: Either Bool b
Let’s make a List data type with Algebraic Data Types
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
Cons
is the :
operator. Therefore, these
are the same:
> 4 `Cons` (5 `Cons` Empty)
ghci4,5]
[> 4: (5 : [])
ghci4,5] [
We can also define how high the precedence is of our operator
:-:
.
infixr 5 :-:
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)
Let’s define our own ++
operator:
infixr 5 .++
(.++) :: List a -> List a -> List a
Empty .++ ys = ys
:-: xs) .++ ys = x :-: (xs .++ ys) (x
Let’s make a binary search tree:
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
Here are two functions: One makes a singleton tree, and another one makes a function to insert an element into a tree.
singleton :: a -> Tree a
= Node x EmptyTree EmptyTree
singleton x
treeInsert :: (Ord a) => a -> Tree a -> Tree a
EmptyTree = singleton x
treeInsert x Node a left right)
treeInsert x (| x == a = Node x left right
| x < a = Node a (treeInsert x left) right
| x > a = Node a left (treeInsert x right)
Here’s a function that checks if an Element is in a tree:
treeElem :: (Ord a) => a -> Tree a -> Bool
EmptyTree = False
treeElem x Node a left right)
treeElem x (| x == a = True
| x < a = treeElem x left
| x > a = TreeElem x right
And let’s start inserting values into the tree:
> let nums = [8,6,4,1,7,3,5]
ghci> let numsTree = foldr treeInsert EmptyTree nums
ghci> numsTree
ghciNode 5 (Node 3 (Node 1 EmptyTree EmptyTree) (Node 4 EmptyTree EmptyTree)) (Node 7 (Node 6 EmptyTree EmptyTree) (Node 8 EmptyTree EmptyTree))
Let’s see how the Eq
typeclass is implemented:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
== y = not (x /= y)
x /= y = not (x == y) x
A type conforms to the Eq
typeclass if it has a
definition for ==
and /=
and doesn’t have the
same implementation for ==
and /=
.
Let’s write a Traffic Light class:
data TrafficLight = Red | Yellow | Green
Normally we could derive it automatically, but let’s write it out:
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
== _ = False _
Because Eq
is defined in terms of each other, we can
stop right here. Eq
is fulfilled, because it can figure out
the definition for /=
.
Here’s how show would be written by hand:
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"
Let’s override Eq
for Maybe
.
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
== _ = False _
The functor typeclass allows you to use a function on every instance of something:
fmap is the defined like this:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Prev: learn-you-a-haskell-chapter-7 Next: learn-you-a-haskell-chapter-9