180 lines
5.0 KiB
Haskell
Executable File
180 lines
5.0 KiB
Haskell
Executable File
-- 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
|