double :: Int -> Int
double x = x * 2

data List a
  = Nil
  | Cons a (List a)
  -- deriving (Eq, Show)

-- `undefined` can be used if you don't know the value yet
-- can be any type, but evaluating it causes a runtime error
-- listLength :: List -> Int
-- listLength l = undefined

listLength :: List a -> Int
-- listLength Nil = 0
-- listLength Cons _ xs = 1 + listLength xs
listLength l =
  case l of
    Nil -> 0
    Cons _ xs -> 1 + listLength xs

-- If we were to try printing one of our lists:
--     • No instance for ‘Show List’ arising from a use of ‘print’
--       There are instances for similar types:
--         instance Show a => Show [a]
--           -- Defined in ‘ghc-internal-9.1002.0:GHC.Internal.Show’

-- `Show` is a typeclass defined by Haskell for turning values into text

-- ghci> :i Show
-- type Show :: * -> Constraint
-- class Show a where
--   showsPrec :: Int -> a -> ShowS
--   show :: a -> String
--   showList :: [a] -> ShowS
--   {-# MINIMAL showsPrec | show #-}


instance Show a => Show (List a) where
    -- show :: List a -> String
    show Nil = "()"
    show (Cons x xs) = show x ++ " " ++ show xs

-- If you do something like `Cons 2 (Cons 1 Nil)`
-- you might get a warning. This is because Haskell has to choose a type for `2` and `1`
-- you can remove this warning with:
-- :set -Wno-type-defaults

element :: (Eq a) => a -> List a -> Bool
element _ Nil = False
element a (Cons x xs) = if x == a
    then True
    else element a xs

-- typeclasses must apply some rules, that the machine can't check
-- Eq relation requires:
-- 1. reflexivity ( x == x )
-- 2. transitivity ( (x == y && y == z) => x == z )
-- 3. symmetry ( `x == y => y == x )
-- 4. extensionality ( x == y => f x == f y )
instance (Eq a) => Eq (List a) where
    (==) Nil Nil = True
    (==) Nil (Cons _ _) = False
    (==) (Cons _ _) Nil = False
    (==) (Cons x xs) (Cons y ys) = if x == y
        then xs == ys
        else False

-- derives can be used to automatically generate some type class instances
-- like:
 {-
 data List a
  = Nil
  | Cons a (List a)
  deriving (Show, Eq)
 -}

someMass :: Double
someMass = 5

someVelocity :: Double
someVelocity = 9.82

energy :: Double -> Double -> Double
energy mass velocity = mass * (velocity * velocity) / 2

-- energy someVelocity someMass
-- allowed in the current definition (can be messed up p quickly)

-- A somewhat better approach is to define custom data types to enforce the correctness at compile time

data Kg = Kg Double
newtype MetersPerSec = MpS Double
data Joules = J Double

-- :godoh:
instance Num Kg where
  Kg x + Kg y = Kg (x + y)
  Kg x * Kg y = Kg (x * y)
  negate (Kg x) = Kg (-x)
  abs (Kg x) = abs (Kg x)
  signum (Kg x)= Kg (signum x)
  fromInteger i= Kg (fromInteger i)

someMass2 :: Kg
someMass2 = Kg 5

someVelocity2 :: MetersPerSec
someVelocity2 = MpS 9.82

energy2 :: Kg -> MetersPerSec -> Joules
energy2 (Kg mass) (MpS velocity) = J (mass * (velocity * velocity) / 2)

-- Next approach to wrap it smort

-- Represents a quantity
data Q u = Q Double deriving (Show)
-- u is a phantom type. It is not actually used in the definition, but it can be used to impose type restrictions

--data Kilogram = Kilogram
data Kg2 -- no need to allow the construction of the type. Uninhabited type
data MpS2
data J2

instance Num (Q u) where
    Q x + Q y = Q (x + y)
    Q x * Q y = Q (x * y)
    negate (Q x) = Q (-x)
    abs (Q x) = abs (Q x)
    signum (Q x) = Q (signum x)
    fromInteger i = Q (fromInteger i)


-- Quantity of Kilograms
someMass3 :: Q Kg2
someMass3 = Q 5

someVelocity3 :: Q MpS2
someVelocity3 = Q 123

energy3 :: Q Kg2 -> Q MpS2 -> Q J2
energy3 (Q m) (Q v) = Q (m * (v*v)/2)

result = energy3 someMass3 someVelocity3

-- Some more useful standard Haskell techniques:
{- Remember this guy:
element :: (Eq a) => a -> List a -> Bool
element _ Nil = False
element a (Cons x xs) = if x == a
    then True
    else element a xs
-}

lookupElement :: Eq a => a -> List a -> Maybe a
lookupElement _ Nil = Nothing
lookupElement x (Cons y ys) =
    if x == y
        then Just y -- ... or x
        else lookupElement x ys


mapList :: (a -> b) -> List a -> List b
mapList f Nil = Nil
mapList f (Cons x xs) = Cons (f x) (mapList f xs)

-- fmap is a more generic version of this mapList
-- it works for any type F that allows converting a function `(a -> b)` to a function `(F a -> F b)`
-- such F types are in the Functor typeclass

instance Functor List where
    fmap _ Nil = Nil
    fmap f (Cons x xs) = Cons (f x) (fmap f xs)

-- Similarly to Functors, there are Foldables. They allow accumulating some data into a single value

instance Foldable List where
    -- foldl _ acc Nil = acc
    -- foldl f acc (Cons x xs) = foldl f (f acc x) xs

    -- foldr is one of the minimal functions you can use
    -- foldr :: (a -> b -> b) -> b -> t a -> b
    foldr _ acc Nil = acc
    foldr f acc (Cons x xs) = f x (foldr f acc xs )
