{-#LANGUAGE TypeSynonymInstances #-}

member [] y = False
member (x:xs) y = (x == y) || (member xs y)

sumOfSquares [] = 0
sumOfSquares (x:xs) = x*x + sumOfSquares xs

square :: Num a => a -> a
square x = x * x

squares :: (Num t, Num t1, Num t2) => (t, t1, t2) -> (t, t1, t2)
squares (x,y,z) = (square x, square y, square z)



-- Haskell member function
mmember :: Eq a => [a] -> a -> Bool
mmember [] y = False
mmember (x:xs) y = x == y || (mmember xs y)

-- Haskell member function, passing equality explicity
member' :: (a->a->Bool) -> [a] -> a -> Bool
member' eq [] y = False
member' eq (x:xs) y = (eq x y) || (member' eq xs y)

-- Sorting function with explicit comparison
qsort:: (a -> a -> Bool) -> [a] -> [a]
qsort comp [] = []
qsort comp (x:xs) = qsort comp (filter (comp x) xs) 
		    ++ [x] ++ 
                    qsort comp (filter (not.comp x) xs)

myCmp x y = x > y
sortedList = qsort myCmp [2,4,2,7,2,3]

-- Haskell numeric function using overloading
parabola :: Num a => a -> a
parabola x = (x * x) + x 

-- Haskell numeric function, passing + and * explicity
parabola' :: ((a->a->a), (a->a->a)) -> a -> a
-- parabola' (plus,times) x = (x `times` x) `plus` x
parabola' (plus, times) x = plus (times x x) x

-- Haskell numeric function, Dictionary translation

-- Type of dictionary: What a type class declaration specifies.
data PlusTimesDict a = MkPlusTimesDict (a->a->a) (a->a->a)
-- Accessor functions
get_plus :: PlusTimesDict a -> (a->a->a)
get_plus (MkPlusTimesDict p t) = p

get_times :: PlusTimesDict a -> (a->a->a)
get_times (MkPlusTimesDict p t) = t

intPlus :: Int -> Int -> Int
intPlus = (+)

intTimes :: Int -> Int -> Int
intTimes = (*)

floatPlus :: Float -> Float -> Float
floatPlus = (+)

floatTimes :: Float -> Float -> Float
floatTimes = (*)

-- Dictionary construction: what a type class instance specifies.
intDict = MkPlusTimesDict intPlus intTimes
floatDict = MkPlusTimesDict floatPlus floatTimes

-- Function with explicit dictionary
parabola''' :: PlusTimesDict a -> a -> a
parabola''' dict x = let plus = get_plus dict
                         times = get_times dict
                     in plus (times x x) x

-- Or more concisely...
parabola'' :: PlusTimesDict a -> a -> a
parabola'' dict x = get_plus dict (get_times dict x x) x

y = parabola'' intDict 10
z = parabola'' floatDict 3.14


-- Only types that have a corresponding dictionary of type
-- PlusTimesDict can be passed to the parabola'' function.

-- Type classes provide a way of managing this plumbing automatically.
-- Type class causes compiler to generate dictionary type.
-- Each instance declaration causes compiler to generate a value of the dictionary,
-- containing the operations for the type being instantiated.
-- Each function that has a qualified type will be rewritten by the compiler
-- to take an extra dictionary parameter and to use the dictionary to look up the 
-- appropriate code for each "overloaded" function.  For example, 
--   parabola :: PlusTimesDict a => a -> a
--   parabola x = x * x + x
-- will lead to parabola'' definition.


-- User defined types can be made instances of existing type classes.
-- For example, we can declare a dataype Cpx to represent complex numbers,
-- and make that type an instance of the Num class.

-- A complex number over type a is a pair of a's.
-- Intuitively, a will be some kind of numeric type.
data Cpx a = Cpx a a
   deriving (Eq, Show)

-- If type a is an instance of Num, then so is Cpx a.
-- The instance declaration for Cpx a defines the necessary
-- operations of the Num class in terms of the existing 
-- operations on a's.  The fact that a is an instance of Num
-- guarantees that a will have the necessary operations defined.
instance Num a => Num (Cpx a) where
  (Cpx r1 i1) + (Cpx r2 i2) = Cpx (r1+r2) (i1+i2)
  (Cpx r1 i1) * (Cpx r2 i2) = Cpx (r1*r2) (i1*i2)
  (Cpx r1 i1) - (Cpx r2 i2) = Cpx (r1-r2) (i1-i2)
  negate (Cpx r i) = Cpx (negate r) (negate i)
  abs (Cpx r i)    = Cpx (abs r) (abs i)
  signum (Cpx r i) = Cpx (signum r) (signum i)
  fromInteger n = Cpx (fromInteger n) 0

-- Given that we have Num (Cpx a), we can use all the
-- operations defined in the Num class on our complex numbers,
-- including being able to have literals have type Cpx a
c1 = 1 :: Cpx Int
c2 = 2 :: Cpx Int
c3 = Cpx 1 3
c4 = c1 + c3
c5 = c1 * c2


-- We can also apply user-defined functions for the Num
-- class on our complex numbers:
c6 = parabola c4
i1 = parabola 3 

-- It is convenient to have the compiler automatically
-- generate instance declarations for some type classes.
-- In particular, the Read, Show, Eq, and Ord classes.
-- The "deriving" clause annotating the Color data type
-- declaration instructs the compiler to generate functions
-- for converting from a string (Read), converting to a 
-- string (Show), comparing for equality (Eq), and comparing
-- for order (Ord).  
data Color = Red | Green | Blue
     deriving (Read, Show, Eq, Ord)

s1 = show Red
c = (read "Red" :: Color )

p1 = (2,3)
p2 = (5,1)

-- Newtype declarations allow us to introduce a new type
-- that is implemented in terms of existing types but is
-- not considered equal to the underlying implementation
-- by the type checker.  This facilty allows us to specify
-- different implementations for overloaded functions than
-- any existing implementations for the underlying types.
-- The deriving clause below instructs the compiler to
-- reuse the underlying defintions for Eq, Ord, and Show.
newtype UniqueID = UniqueID (Int,Int)
   deriving (Eq, Ord, Show)

-- The class Foo specifies the existence of an operator '@@@@' 
-- that takes two 'a' values and returns a boolean.  
class Foo a where
  (@@@@) :: a -> a -> Bool

-- Make Int an instance of class Foo by defining the (@@@@)
-- to be the equality function on Ints.
instance Foo Int where
  i @@@@ j = i == j

-- Make Char an instance of class Foo by defining the (@@@@)
-- to be the equality function on Chars.
instance Foo Char where
  i @@@@ j = i == j

-- If a is a Foo and if b is a Foo, then make the pair (a,b)
-- an instance of Foo by defining the (@@@@) operator for
-- pairs in terms of the @@@@ operators for a and for b.
instance (Foo a, Foo b) => Foo (a,b) where
  (u,v) @@@@ (x,y) =  (u @@@@ x) && (v @@@@ y)


-- If a is a Foo, then make the list [a] an instance of Foo
-- by defining the (@@@@) operator over lists in terms
-- of the @@@@ operator for a.
instance Foo a => Foo [a] where
  [] @@@@ [] = True
  (x:xs) @@@@ (y:ys) = x @@@@ y && xs @@@@ ys

-- Make String an instance of class Foo by defining the (@@@@)
-- to be the equality function on String.
instance Foo String where
  s @@@@ s2 = s == s2
-- Because String is defined in Haskell to be [Char],
-- the compiler has two different possible implementations
-- of (@@@@) to chose from for Strings.  The one defined below
-- directly for Strings and one that can be built from the
-- instance for Char and for List.  Normally, in this situation
-- the compiler will complain about an ambiguous instance. 
-- The pragma
--     {-#LANGUAGE TypeSynonymInstances #-}
-- at the top of this file tells the compiler not to complain
-- about this issue, and to take the more specific instance,
-- which in this case for String is Foo String.


-- This example is intended to illustrate type inference
-- including type classes.  
myexample :: (Ord a) => a -> [a] -> Bool
myexample z xs = 
   case xs of
     []     -> False
     (y:ys) -> y > z || (y==z && ys ==[z])

-- We can define overloaded functions in terms of 
-- other overloaded functions.  For example,
-- both member and square are both overloaded functions:
--   member :: (Eq t) => [t] -> t -> Bool
--   square :: (Num a) => a -> a
-- and they are used to define memsq, another overloaded function.
memsq :: (Eq a, Num a) => [a] -> a -> Bool
memsq xs x = member xs (square x)

-- There are many different data structures for which it is useful
-- to define a map function.  
-- The mapList function maps a function over a list.
mapList :: (a -> b) -> [a] -> [b]
mapList f [] = []
mapList f (x:xs) = f x : mapList f xs

data Tree a = Leaf a | Node(Tree a, Tree a)
    deriving Show

-- The mapTree function maps a function over a tree.
mapTree :: (a -> b) -> (Tree a -> Tree b)
mapTree f (Leaf x) = Leaf (f x)
mapTree f (Node(l,r)) = Node (mapTree f l, mapTree f r)

t1 = Node(Node(Leaf 3, Leaf 4), Leaf 5)

data Opt a = Some a | None
 deriving Show

-- The mapOpt function maps a function over an option.
mapOpt :: (a -> b) -> (Opt a -> Opt b)
mapOpt f None = None
mapOpt f (Some x) = Some (f x)

o1 = Some 10

-- The HasMap type class takes a *type constructor*
-- as an argument, not a type.  Ie, 'f' is not a type itself,
-- rather 'f' applied to a type 'a' is a type.  
-- It is intended to be instantiated with [], Tree, Opt,
-- or similar type constructors.  (Note [] is the notation
-- for the list type constructor.) 

class HasMap f where
  map' :: (a->b) ->(f a -> f b)

-- Make the list type constructor an instance of the HasMap type constructor
-- class. the implementation of map' is just the same as the implementation
-- of the mapList function.
instance HasMap [] where
  map' f [] = []
  map' f (x:xs) = f x : map' f xs

-- Make the Tree type constructor an instance of the HasMap type constructor
-- class. the implementation of map' is just the same as the implementation
-- of the mapTree function.
instance HasMap Tree where
  map' f (Leaf x) = Leaf (f x)
  map' f (Node(t1,t2)) = Node(map' f t1, map' f t2)

-- Make the Opt type constructor an instance of the HasMap type constructor
-- class. the implementation of map' is just the same as the implementation
-- of the mapOpt function.
instance HasMap Opt where
  map' f (Some s) = Some (f s)
  map' f None = None


-- Overloaded uses of the map' function, applying the successor function
-- to lists, trees, and options.
r1 = map' (\x->x+1) [1,2,3]
r2 = map' (\x->x+1) (Node(Leaf 1, Leaf 2))
r3 = map' (\x->x+1) (Some 1)

