You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
haskell/09-types-and-typeclasses-re...

180 lines
5.0 KiB

-- data keyword defines a new data type. e.g. from prelude:
data Bool = False | True
data Shape =
Circle Float Float Float
| Rectangle Float Float Float Float
deriving (Show)
-- Value constructors are actually functions returning the data type
-- :t Circle
Circle :: Float -> Float -> Float -> Shape
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
-- Note the signature. We can't do `surface :: Circle -> Float` because `Circle` is NOT a type, `Shape` is.
-- Because value constructors are functions, we can partially apply them!
map (Circle 10 20) [4,5] -- == [Circle 10 20 4, Circle 10 20 5]
-- Let's make the data type more clear
data Point =
Point Float Float
deriving (Show)
data Shape =
Circle Point Float
| Rectangle Point Point
deriving (Show)
-- now, we pattern match:
surface :: Shape -> Float
surface (Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
-- To export a data type from a module, export Shape(..) for all constructors, or, e.g., Shape(Circle,Rectangle)
-- We can use the record syntax to get named fields with automatic lookup functions:
data Person =
Person {
firstName :: String,
lastName :: String,
age :: Int
} deriving (Show)
personTest1 = Person {firstName="Garrett", lastName="Mills", age=21}
-- This also creates automatic attribute access functions:
personTest2 = firstName personTest1 -- == "Garrett"
-- Types can have zero or more parameters:
data Maybe a = Nothing | Just a
-- We can pattern match on the record type:
sayHi :: Person -> String
sayHi (Person {firstName=f, lastName=l}) = "Hello, " ++ [f, ' ', l]
-- 3D vector type example:
data Vector a =
Vector a a a
deriving (Show)
vplus :: (Num t) => Vector t -> Vector t -> Vector t
(Vector i j k) `plus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
-- In this case, it is best to put the typeclass restriction on the functions
-- where it matters, instead of the data declaration itself, to avoid unnecessary restrictions.
-- Example of a nullary, enumerable typeclass:
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Bounded, Enum)
-- The `type` keyword can be used to alias types. e.g.
type String = [Char]
-- Aliases can be parameterized:
type AssocList k v = [(k,v)]
-- We can also partially-apply type constructors as aliases. TFAE:
type IntMap v = Map Int v
type IntMap = Map Int
-- Data types can be recursive. e.g. implementing our own cons-list:
data List a =
Empty
| Cons a (List a)
deriving (Show, Read, Eq, Ord)
-- We can make an op infix by using only special chars. We also define the fixity to determine the tightness of the bind:
infixr 5 :-:
data List a =
Empty
| a :-: (List a)
deriving (Show, Read, Eq, Ord)
-- Let's implement a binary search tree:
data Tree =
Empty
| Node a (Tree a) (Tree a)
deriving (Show, Read, Eq)
singleton :: a -> Tree a
singleton x = (Node x Empty Empty)
treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x Empty = singleton x
treeInsert (Node a left right)
| x == a = Node x left right
| x < a = Node a (treeInsert x left) right
| x > a = Node a left (treeInsert x right)
treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem _ Empty = False
treeElem x (Tree a left right)
| x == a = True
| x < a = (treeElem x left)
| otherwise = (treeElem x right)
-- Defining typeclasses by hand
-- Here's the typeclass Eq:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
-- Now, we can make our custom data types instances of a typeclass by hand:
data TrafficLight = Red | Yellow | Green
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = False
-- Defining (==) and (/=) in terms of mutual recursion means that we only need to
-- specify one of them in our instance declarations.
-- Similarly, we can do:
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"
-- We can force typeclasses to be subclasses of others. Partial example:
class (Eq a) => Num a where
-- ...
-- In this case, Num instances must satisfy Num AND Eq.
-- :info TypeClass in GHCi will show the functions for a particular typeclass or constructor
-- Functors
-- Consider:
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- e.g. how map is a functor
instance Functor [] where
fmap = map
-- Notably, we need f to be a type constructor, not a concrete type. [a] is concrete, [] is a constructor
-- Similarly, Maybe:
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
-- What about Either? It takes 2 parameters. Partially apply it!
data Either a b = Left a | Right b
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
-- Use :k Type to get the kind of a type or type constructor in GHCi