module Chapter15 where
import Data.Monoid
import Test.Hspec
import Test.QuickCheck hiding (Failure, Success)
data Optional a
= Nada
| Only a
deriving (Eq, Show)
instance Semigroup a => Semigroup (Optional a) where
Nada <> only = only
only <> Nada = only
Only a <> Only b = Only $ a <> b
instance Monoid a => Monoid (Optional a) where
mempty = Nada
type S = String
type B = Bool
semigroupAssoc :: (Eq m, Semigroup m) => m -> m -> m -> Bool
semigroupAssoc a b c = a <> (b <> c) == (a <> b) <> c
monoidLeftIdentity :: (Eq m, Monoid m) => m -> Bool
monoidLeftIdentity a = (a <> mempty) == a
monoidRightIdentity :: (Eq m, Monoid m) => m -> Bool
monoidRightIdentity a = (mempty <> a) == a
newtype First' a = First'
{ getFirst' :: Optional a
} deriving (Eq, Show)
instance Arbitrary a => Arbitrary (First' a) where
arbitrary = do
a <- arbitrary
frequency $ [(1, return $ First' Nada), (3, return $ First' $ Only a)]
instance Semigroup (First' a) where
first@(First' (Only a)) <> _ = first
_ <> first@(First' (Only a)) = first
_ <> _ = First' Nada
instance Monoid (First' a) where
mempty = First' Nada
data Trivial =
Trivial
deriving (Eq, Show)
instance Arbitrary Trivial where
arbitrary = return Trivial
instance Semigroup Trivial where
_ <> _ = Trivial
instance Monoid Trivial where
mempty = Trivial
data Identity a =
Identity a
deriving (Eq, Show)
instance Arbitrary a => Arbitrary (Identity a) where
arbitrary = do
a <- arbitrary
return $ Identity a
instance Semigroup a => Semigroup (Identity a) where
Identity a <> Identity b = Identity $ a <> b
instance Monoid a => Monoid (Identity a) where
mempty = Identity mempty
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 (Semigroup a, Semigroup b) => Semigroup (Two a b) where
Two a b <> Two a' b' = Two (a <> a') (b <> b')
instance (Monoid a, Monoid b) => Monoid (Two a b) where
mempty = Two mempty mempty
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 (Semigroup a, Semigroup b, Semigroup c) =>
Semigroup (Three a b c) where
Three a b c <> Three a' b' c' = Three (a <> a') (b <> b') (c <> c')
instance (Monoid a, Monoid b, Monoid c) => Monoid (Three a b c) where
mempty = Three mempty mempty mempty
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 (Semigroup a, Semigroup b, Semigroup c, Semigroup d) =>
Semigroup (Four a b c d) where
Four a b c d <> Four a' b' c' d' =
Four (a <> a') (b <> b') (c <> c') (d <> d')
instance (Monoid a, Monoid b, Monoid c, Monoid d) => Monoid (Four a b c d) where
mempty = Four mempty mempty mempty mempty
data BoolConj =
BoolConj Bool
deriving (Eq, Show)
instance Arbitrary BoolConj where
arbitrary = do
a <- arbitrary
return $ BoolConj a
instance Semigroup BoolConj where
BoolConj True <> BoolConj True = BoolConj True
_ <> _ = BoolConj False
instance Monoid BoolConj where
mempty = BoolConj True
data BoolDisj =
BoolDisj Bool
deriving (Eq, Show)
instance Arbitrary BoolDisj where
arbitrary = do
a <- arbitrary
return $ BoolDisj a
instance Semigroup BoolDisj where
BoolDisj False <> BoolDisj False = BoolDisj False
_ <> _ = BoolDisj True
instance Monoid BoolDisj where
mempty = BoolDisj False
data Or a b
= Fst a
| Snd b
deriving (Eq, Show)
instance (Arbitrary a, Arbitrary b) => Arbitrary (Or a b) where
arbitrary = do
a <- arbitrary
b <- arbitrary
oneof [return $ Fst a, return $ Snd b]
instance Semigroup (Or a b) where
Snd a <> _ = Snd a
_ <> Snd a = Snd a
_ <> a = a
newtype Combine a b = Combine
{ unCombine :: (a -> b)
}
combineInc :: Combine Int (Sum Int)
combineInc = Combine $ \n -> Sum (n + 1)
combineSub :: Combine Int (Sum Int)
combineSub = Combine $ \n -> Sum (n - 1)
instance Semigroup b => Semigroup (Combine a b) where
Combine f <> Combine g = Combine $ \a -> f a <> g a
instance Monoid b => Monoid (Combine a b) where
mempty = Combine $ \_ -> mempty
newtype Comp a = Comp
{ unComp :: (a -> a)
}
compInc :: Comp Int
compInc = Comp $ \n -> n + 1
compSub :: Comp Int
compSub = Comp $ \n -> n - 1
instance Semigroup (Comp a) where
Comp f <> Comp g = Comp $ f . g
instance Monoid (Comp a) where
mempty = Comp id
data Validation a b
= Failure a
| Success b
deriving (Eq, Show)
instance (Arbitrary a, Arbitrary b) => Arbitrary (Validation a b) where
arbitrary = do
a <- arbitrary
b <- arbitrary
oneof [return $ Failure a, return $ Success b]
instance Semigroup a => Semigroup (Validation a b) where
Success a <> _ = Success a
_ <> Success a = Success a
Failure a <> Failure b = Failure $ a <> b
newtype Mem s a = Mem
{ runMem :: s -> (a, s)
}
instance Semigroup a => Semigroup (Mem s a) where
Mem f <> Mem f' =
Mem $ \s ->
let (a1, s1) = f s
(a2, s2) = f' s1
in (a1 <> a2, s2)
instance Monoid a => Monoid (Mem s a) where
mempty = Mem $ \s -> (mempty, s)
testMem = Mem $ \s -> ("hi", s + 1)
spec :: SpecWith ()
spec = do
describe "Optional monoid" $ do
it "with a Sum" $
(Only $ Sum 1) `mappend` (Only $ Sum 1) `shouldBe` (Only $ Sum (2 :: Int))
it "with a Sum and Nada" $
(Only $ Sum 1) `mappend` Nada `shouldBe` (Only $ Sum (1 :: Int))
it "with a Nada and Sum" $
Nada `mappend` (Only $ Sum 1) `shouldBe` (Only $ Sum (1 :: Int))
it "with a Product" $
(Only $ Product 4) `mappend` (Only $ Product 2) `shouldBe`
(Only $ Product (8 :: Int))
it "with a List" $
(Only $ [1]) `mappend` (Only $ [1]) `shouldBe` (Only $ ([1, 1] :: [Int]))
it "with a List and Nada" $
(Only $ [1]) `mappend` Nada `shouldBe` (Only $ ([1] :: [Int]))
describe "First'" $ do
it "obeys associativity" $
property (semigroupAssoc :: First' S -> First' S -> First' S -> B)
it "obeys right identity" $ property (monoidRightIdentity :: First' S -> B)
it "obeys left identity" $ property (monoidLeftIdentity :: First' S -> B)
it "for an Only and Nada" $
(First' $ Only [1]) `mappend` First' Nada `shouldBe`
(First' $ Only ([1] :: [Int]))
it "for a Nada and Only" $
First' Nada `mappend` (First' $ Only [1]) `shouldBe`
(First' $ Only ([1] :: [Int]))
it "for both Nada" $
First' Nada `mappend` First' Nada `shouldBe`
(First' $ (Nada :: Optional [Int]))
it "for both Only" $
(First' $ Only [1]) `mappend` (First' $ Only [2]) `shouldBe`
(First' $ Only ([1] :: [Int]))
describe "Trivial" $ do
it "obeys associativity" $
property (semigroupAssoc :: Trivial -> Trivial -> Trivial -> B)
it "obeys right identity" $ property (monoidRightIdentity :: Trivial -> B)
it "obeys left identity" $ property (monoidLeftIdentity :: Trivial -> B)
describe "Identity" $ do
it "obeys associativity" $
property (semigroupAssoc :: Identity S -> Identity S -> Identity S -> B)
it "obeys right identity" $
property (monoidRightIdentity :: Identity S -> B)
it "obeys left identity" $ property (monoidLeftIdentity :: Identity S -> B)
describe "Two" $ do
it "obeys associativity" $
property (semigroupAssoc :: Two S S -> Two S S -> Two S S -> B)
it "obeys right identity" $ property (monoidRightIdentity :: Two S S -> B)
it "obeys left identity" $ property (monoidLeftIdentity :: Two S S -> B)
describe "Three" $ do
it "obeys associativity" $
property
(semigroupAssoc :: Three S S S -> Three S S S -> Three S S S -> B)
it "obeys right identity" $
property (monoidRightIdentity :: Three S S S -> B)
it "obeys left identity" $ property (monoidLeftIdentity :: Three S S S -> B)
describe "Four" $ do
it "obeys associativity" $
property
(semigroupAssoc :: Four S S S S -> Four S S S S -> Four S S S S -> B)
it "obeys right identity" $
property (monoidRightIdentity :: Four S S S S -> B)
it "obeys left identity" $
property (monoidLeftIdentity :: Four S S S S -> B)
describe "BoolConj" $ do
it "obeys associativity" $
property (semigroupAssoc :: BoolConj -> BoolConj -> BoolConj -> B)
it "obeys right identity" $ property (monoidRightIdentity :: BoolConj -> B)
it "obeys left identity" $ property (monoidLeftIdentity :: BoolConj -> B)
it "for True/True" $ BoolConj True <> BoolConj True `shouldBe` BoolConj True
it "for True/False" $
BoolConj True <> BoolConj False `shouldBe` BoolConj False
it "for False/True" $
BoolConj False <> BoolConj True `shouldBe` BoolConj False
it "for False/False" $
BoolConj False <> BoolConj False `shouldBe` BoolConj False
it "for right mempty" $ BoolConj True <> mempty `shouldBe` BoolConj True
it "for left mempty" $ mempty <> BoolConj False `shouldBe` BoolConj False
describe "BoolDisj" $ do
it "obeys associativity" $
property (semigroupAssoc :: BoolDisj -> BoolDisj -> BoolDisj -> B)
it "obeys right identity" $ property (monoidRightIdentity :: BoolDisj -> B)
it "obeys left identity" $ property (monoidLeftIdentity :: BoolDisj -> B)
it "for True/True" $ BoolDisj True <> BoolDisj True `shouldBe` BoolDisj True
it "for True/False" $
BoolDisj True <> BoolDisj False `shouldBe` BoolDisj True
it "for False/True" $
BoolDisj False <> BoolDisj True `shouldBe` BoolDisj True
it "for False/False" $
BoolDisj False <> BoolDisj False `shouldBe` BoolDisj False
it "for right mempty" $ BoolDisj True <> mempty `shouldBe` BoolDisj True
it "for left mempty" $ mempty <> BoolDisj False `shouldBe` BoolDisj False
describe "Or" $ do
it "obeys associativity" $
property (semigroupAssoc :: Or S S -> Or S S -> Or S S -> B)
it "for Fst/Fst" $ Fst 1 <> Fst 2 `shouldBe` (Fst 2 :: Or Int Int)
it "for Fst/Snd" $ Fst 1 <> Snd 2 `shouldBe` (Snd 2 :: Or Int Int)
it "for Snd/Fst" $ Snd 1 <> Fst 2 `shouldBe` (Snd 1 :: Or Int Int)
it "for Snd/Snd" $ Snd 1 <> Snd 2 `shouldBe` (Snd 1 :: Or Int Int)
describe "Combine" $ do
it "for inc <> sub with 0" $
(unCombine (combineInc <> combineSub) $ 0) `shouldBe` Sum 0
it "for inc <> sub with 1" $
(unCombine (combineInc <> combineSub) $ 1) `shouldBe` Sum 2
it "for inc <> inc with 1" $
(unCombine (combineInc <> combineInc) $ 1) `shouldBe` Sum 4
it "for sub <> inc with 1" $
(unCombine (combineSub <> combineInc) $ 1) `shouldBe` Sum 2
it "for inc <> mempty with 1" $
(unCombine (combineInc <> mempty) $ 1) `shouldBe` Sum 2
describe "Comp" $ do
it "for inc <> sub with 0" $ (unComp (compInc <> compSub) $ 0) `shouldBe` 0
it "for inc <> sub with 1" $ (unComp (compInc <> compSub) $ 1) `shouldBe` 1
it "for inc <> inc with 1" $ (unComp (compInc <> compInc) $ 1) `shouldBe` 3
it "for sub <> inc with 1" $ (unComp (compSub <> compInc) $ 1) `shouldBe` 1
it "for inc <> mempty with 1" $
(unComp (compInc <> mempty) $ 1) `shouldBe` 2
describe "Validation" $ do
it "obeys associativity" $
property
(semigroupAssoc :: Validation S S -> Validation S S -> Validation S S -> B)
it "for Failure/Failure" $
Failure "woot" <> Failure "blah" `shouldBe`
(Failure "wootblah" :: Validation String Int)
it "for Failure/Success" $
Failure "woot" <> Success 2 `shouldBe`
(Success 2 :: Validation String Int)
it "for Success/Failure" $
Success 1 <> Failure "woot" `shouldBe`
(Success 1 :: Validation String Int)
it "for Success/Success" $
Success 1 <> Success 2 `shouldBe` (Success 1 :: Validation String Int)
describe "Mem" $ do
it "rmzero" $ runMem mempty 0 `shouldBe` ("", 0)
it "testMem" $ runMem testMem 0 `shouldBe` ("hi", 1)
it "rmleft" $ runMem (testMem <> mempty) 0 `shouldBe` ("hi", 1)
it "rmright" $ runMem (mempty <> testMem) 0 `shouldBe` ("hi", 1)