-- class Functor f :: Type -> Type where
--  fmap :: (a -> b) -> f a -> f b
--
-- Rules:
-- 1. fmap id == id
-- 2. fmap (f . g) = fmap f . fmap g
--
-- "Things that contain values and then you can map over those values"

-- Simplest time we can write a functor for
data Box a = Box a
  deriving (Show, Eq)

instance Functor Box where
  -- We have:
  -- f :: a -> b
  -- x :: a
  -- Want:
  -- Box b
  -- fmap :: (a -> b) -> Box a -> Box b
  fmap f (Box a) =
    let y = f a
     in Box y

-- Equivalently:
-- fmap f (Box a) = Box (f a)
-- fmap f (Box a) = Box $ f a
--
-- Let's prove the functor rules:
-- (1.)
-- fmap id (Box x)
-- = Box (id x)
-- = Box x
-- = id (Box x)

-- Some things don't really work as containers, but can still implement Functor

data Fun a b = Fun (a -> b)

instance Functor (Fun a) where
  -- fmap :: (a -> b) -> Fun c a -> Fun c b
  -- f :: a -> b
  -- b :: c -> a
  -- Want: c -> b
  fmap f (Fun g) = Fun (f . g)

-- Alternatively:
-- fmap f (Fun g) = Fun (\a -> g(f a))

-- `Maybe`
-- Can be used to model computations that can fail.
data SomeTimes a = Nope | Yup a

instance Functor SomeTimes where
  fmap _ Nope = Nope
  fmap f (Yup a) = Yup $ f a

-- Note: fmap = (<$>)

instance Applicative SomeTimes where
  -- (<*>) :: SomeTimes (a -> b) -> SomeTimes a -> SomeTimes b
  (<*>) _ Nope = Nope
  (<*>) Nope _ = Nope
  (<*>) (Yup f) (Yup a) = Yup $ f a

  -- pure :: a -> SomeTimes a
  pure a = Yup a

-- Alternatively (partial application)
-- pure = Yup

instance Monad SomeTimes where
  -- (>>=) :: SomeTimes a -> (a -> SomeTimes b) -> SomeTimes b
  (>>=) Nope _ = Nope
  (>>=) (Yup a) f = case f a of
    Nope -> Nope
    Yup b -> Yup b

-- (>>=) (Yup a) f = f a

someTimesHead :: [a] -> SomeTimes a
someTimesHead [] = Nope
someTimesHead (x : _) = Yup x
