Chapter 16


{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}

module Chapter16 where

import Test.Hspec
import Test.Hspec.Checkers
import Test.QuickCheck
import Test.QuickCheck.Checkers
import Test.QuickCheck.Classes

functorIdentity :: (Functor f, Eq (f a)) => f a -> Bool
functorIdentity f = fmap id f == f

functorCompose :: (Eq (f c), Functor f) => f a -> Fun a b -> Fun b c -> Bool
functorCompose x (Fun _ f) (Fun _ g) = fmap (g . f) x == (fmap g . fmap f $ x)

newtype Identity a =
  Identity a
  deriving (Eq, Show)

instance Arbitrary a => Arbitrary (Identity a) where
  arbitrary = Identity <$> arbitrary

instance Functor Identity where
  fmap f (Identity a) = Identity $ f a

data Pair a =
  Pair a
       a
  deriving (Eq, Show)

instance Arbitrary a => Arbitrary (Pair a) where
  arbitrary = do
    a <- arbitrary
    a' <- arbitrary
    return $ Pair a a'

instance Functor Pair where
  fmap f (Pair a a') = Pair (f a) (f a')

data Two a b =
  Two a
      b
  deriving (Eq, Show)

instance (Arbitrary a, Arbitrary b) => Arbitrary (Two a b) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    return $ Two a b

instance Functor (Two a) where
  fmap f (Two a b) = Two a (f b)

data Three a b c =
  Three a
        b
        c
  deriving (Eq, Show)

instance (Arbitrary a, Arbitrary b, Arbitrary c) =>
         Arbitrary (Three a b c) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    c <- arbitrary
    return $ Three a b c

instance Functor (Three a b) where
  fmap f (Three a b c) = Three a b (f c)

data Three' a b =
  Three' a
         b
         b
  deriving (Eq, Show)

instance (Arbitrary a, Arbitrary b) => Arbitrary (Three' a b) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    b' <- arbitrary
    return $ Three' a b b'

instance Functor (Three' a) where
  fmap f (Three' a b b') = Three' a (f b) (f b')

data Four a b c d =
  Four a
       b
       c
       d
  deriving (Eq, Show)

instance (Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d) =>
         Arbitrary (Four a b c d) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    c <- arbitrary
    d <- arbitrary
    return $ Four a b c d

instance Functor (Four a b c) where
  fmap f (Four a b c d) = Four a b c (f d)

data Four' a b =
  Four' a
        a
        a
        b
  deriving (Eq, Show)

instance (Arbitrary a, Arbitrary b) => Arbitrary (Four' a b) where
  arbitrary = do
    a <- arbitrary
    a' <- arbitrary
    a'' <- arbitrary
    b <- arbitrary
    return $ Four' a a' a'' b

instance Functor (Four' a) where
  fmap f (Four' a a' a'' b) = Four' a a' a'' (f b)

data Possibly a
  = LolNope
  | Yeppers a
  deriving (Eq, Show)

instance Arbitrary a => Arbitrary (Possibly a) where
  arbitrary = do
    a <- arbitrary
    oneof [return LolNope, return $ Yeppers a]

instance Functor Possibly where
  fmap _ LolNope = LolNope
  fmap f (Yeppers a) = Yeppers $ f a

data Sum a b
  = First a
  | Second b
  deriving (Eq, Show)

instance (Arbitrary a, Arbitrary b) => Arbitrary (Sum a b) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    oneof [return $ First a, return $ Second b]

instance Functor (Sum a) where
  fmap _ (First a) = First a
  fmap f (Second b) = Second $ f b

data Quant a b
  = Finance
  | Desk a
  | Bloor b
  deriving (Eq, Show)

instance (Arbitrary a, Arbitrary b) => Arbitrary (Quant a b) where
  arbitrary = do
    a <- arbitrary
    b <- arbitrary
    oneof [return Finance, return $ Desk a, return $ Bloor b]

instance (Eq a, Eq b) => EqProp (Quant a b) where
  (=-=) = eq

instance Functor (Quant a) where
  fmap _ Finance = Finance
  fmap _ (Desk a) = Desk a
  fmap f (Bloor b) = Bloor $ f b

data K a b =
  K a
  deriving (Eq, Show)

instance Arbitrary a => Arbitrary (K a b) where
  arbitrary = do
    a <- arbitrary
    return $ K a

instance Eq a => EqProp (K a b) where
  (=-=) = eq

instance Functor (K a) where
  fmap _ (K a) = K a

newtype Flip f a b =
  Flip (f b a)
  deriving (Eq, Show)

instance Arbitrary b => Arbitrary (Flip K a b) where
  arbitrary = do
    b <- arbitrary
    return $ Flip (K b)

instance (Eq b, Eq (K a b)) => EqProp (Flip K a b) where
  (=-=) = eq

instance Functor (Flip K a) where
  fmap f (Flip (K b)) = Flip (K (f b))

data EvilGoateeConst a b =
  GoatyConst b
  deriving (Eq, Show)

instance Arbitrary b => Arbitrary (EvilGoateeConst a b) where
  arbitrary = do
    b <- arbitrary
    return $ GoatyConst b

instance Eq b => EqProp (EvilGoateeConst a b) where
  (=-=) = eq

instance Functor (EvilGoateeConst a) where
  fmap f (GoatyConst b) = GoatyConst $ f b

data LiftItOut f a =
  LiftItOut (f a)
  deriving (Eq, Show)

instance Arbitrary (f a) => Arbitrary (LiftItOut f a) where
  arbitrary = do
    fa <- arbitrary
    return $ LiftItOut fa

instance Eq (f a) => EqProp (LiftItOut f a) where
  (=-=) = eq

instance Functor f => Functor (LiftItOut f) where
  fmap f (LiftItOut fa) = LiftItOut $ f <$> fa

data Parappa f g a =
  DaWrappa (f a)
           (g a)
  deriving (Eq, Show)

instance (Arbitrary (f a), Arbitrary (g a)) => Arbitrary (Parappa f g a) where
  arbitrary = do
    fa <- arbitrary
    ga <- arbitrary
    return $ DaWrappa fa ga

instance (Eq (f a), Eq (g a)) => EqProp (Parappa f g a) where
  (=-=) = eq

instance (Functor f, Functor g) => Functor (Parappa f g) where
  fmap f (DaWrappa fa ga) = DaWrappa (f <$> fa) (f <$> ga)

data IgnoreOne f g a b =
  IgnoringSometing (f a)
                   (g b)
  deriving (Eq, Show)

instance (Arbitrary (f a), Arbitrary (g b)) =>
         Arbitrary (IgnoreOne f g a b) where
  arbitrary = do
    fa <- arbitrary
    gb <- arbitrary
    return $ IgnoringSometing fa gb

instance (Eq (f a), Eq (g b)) => EqProp (IgnoreOne f g a b) where
  (=-=) = eq

instance (Functor g) => Functor (IgnoreOne f g a) where
  fmap f (IgnoringSometing fa gb) = IgnoringSometing fa (f <$> gb)

data Notorious g o a t =
  Notorious (g o)
            (g a)
            (g t)
  deriving (Eq, Show)

instance (Arbitrary (g o), Arbitrary (g a), Arbitrary (g t)) =>
         Arbitrary (Notorious g o a t) where
  arbitrary = do
    go <- arbitrary
    ga <- arbitrary
    gt <- arbitrary
    return $ Notorious go ga gt

instance (Eq (g o), Eq (g a), Eq (g t)) => EqProp (Notorious g o a t) where
  (=-=) = eq

instance (Functor g) => Functor (Notorious g o a) where
  fmap f (Notorious go ga gt) = Notorious go ga (f <$> gt)

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

fromBaseList :: [a] -> List a
fromBaseList [] = Nil
fromBaseList (a:as) = Cons a (fromBaseList as)

instance Arbitrary a => Arbitrary (List a) where
  arbitrary = do
    as <- arbitrary
    return $ fromBaseList as

instance Eq a => EqProp (List a) where
  (=-=) = eq

instance Functor List where
  fmap _ Nil = Nil
  fmap f (Cons a as) = Cons (f a) (fmap f as)

data GoatLord a
  = NoGoat
  | OneGoat a
  | MoreGoats (GoatLord a)
              (GoatLord a)
              (GoatLord a)
  deriving (Eq, Show)

instance Arbitrary a => Arbitrary (GoatLord a) where
  arbitrary = do
    a <- arbitrary
    goatLord1 <- arbitrary
    goatLord2 <- arbitrary
    goatLord3 <- arbitrary
    frequency
      [ (5, return NoGoat)
      , (5, return $ OneGoat a)
      , (1, return $ MoreGoats goatLord1 goatLord2 goatLord3)
      ]

instance Eq a => EqProp (GoatLord a) where
  (=-=) = eq

instance Functor GoatLord where
  fmap _ NoGoat = NoGoat
  fmap f (OneGoat a) = OneGoat $ f a
  fmap f (MoreGoats gl1 gl2 gl3) = MoreGoats (f <$> gl1) (f <$> gl2) (f <$> gl3)

data TalkToMe a
  = Halt
  | Print String
          a
  | Read (String -> a)

instance Show a => Show (TalkToMe a) where
  show Halt = "Halt"
  show (Print s a) = "Print " ++ s ++ " " ++ show a
  show (Read f) = "Read " ++ show (f "TEST")

instance Eq a => Eq (TalkToMe a) where
  Halt == Halt = True
  Print s a == Print s' a' = s == s' && a == a'
  _ == _ = False

instance Functor TalkToMe where
  fmap _ Halt = Halt
  fmap f (Print s a) = Print s (f a)
  fmap f (Read r) = Read (f . r)

spec :: SpecWith ()
spec = do
  describe "Identity" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Identity Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Identity Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Pair" $ do
    it "functor obeys identity" $ property (functorIdentity :: Pair Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Pair Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Two" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Two String Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Two String Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Three" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Three String String Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Three String String Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Three'" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Three' String Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Three' String Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Four" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Four String String String Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Four String String String Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Four'" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Four' String Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Four' String Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Possibly" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Possibly Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Possibly Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Sum" $ do
    it "functor obeys identity" $
      property (functorIdentity :: Sum String Int -> Bool)
    it "functor obeys composition" $
      property
        (functorCompose :: Sum String Int -> (Fun Int Int) -> (Fun Int Int) -> Bool)
  describe "Quant" $ do
    testBatch $ functor (undefined :: Quant String (Int, Int, Int))
  describe "K" $ do testBatch $ functor (undefined :: K String (Int, Int, Int))
  describe "Flip K" $ do
    testBatch $ functor (undefined :: Flip K String (Int, Int, Int))
  describe "EvilGoateeConst" $ do
    testBatch $ functor (undefined :: EvilGoateeConst String (Int, Int, Int))
  describe "LiftItOut" $ do
    testBatch $ functor (undefined :: LiftItOut Maybe (Int, Int, Int))
  describe "Parappa" $ do
    testBatch $ functor (undefined :: Parappa [] Maybe (Int, Int, Int))
  describe "IgnoreOne" $ do
    testBatch $ functor (undefined :: IgnoreOne [] Maybe String (Int, Int, Int))
  describe "Notorious" $ do
    testBatch $
      functor (undefined :: Notorious [] String String (Int, Int, Int))
  describe "List" $ do testBatch $ functor (undefined :: List (Int, Int, Int))
  describe "GoatLord" $ do
    testBatch $ functor (undefined :: GoatLord (Int, Int, Int))
  describe "TalkToMe" $ do
    it "fmap for Halt" $ (+ 1) <$> Halt `shouldBe` (Halt :: TalkToMe Int)
    it "fmap for Print" $
      (+ 1) <$> (Print "str" 1) `shouldBe` (Print "str" 2 :: TalkToMe Int)
    it "fmap for Read" $
      case (+ 1) <$> (Read read) of
        Read f -> f "1" `shouldBe` (2 :: Int)
        _ -> expectationFailure "wrong type of TalkToMe"