-- 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