{-# LANGUAGE CPP #-}
module Test.SmallCheck.Laws.Monoid
  (
  -- * Identity
    leftIdentity
  , rightIdentity
  -- * Associativity
  , associativity
  , associativitySum
  -- * mconcat
  , mconcatProp
  ) where

#if MIN_VERSION_base(4,8,0)
import Prelude hiding (mconcat)
#else
import Data.Monoid (Monoid, mappend, mempty)
import Data.Traversable (sequenceA)
#endif
import Data.Monoid ((<>))
import qualified Data.Monoid as Monoid (mconcat)
import Test.SmallCheck (Property, over)
import Test.SmallCheck.Series (Series)
import Test.SmallCheck.Series.Utils (zipLogic3)

-- * Identity

-- | Check the /left identity/ law holds for the given 'Monoid' 'Series':
--
-- @
-- 'mempty' '<>' x ≡ x
-- @
leftIdentity
  :: (Eq a, Monad m, Show a, Monoid a)
  => Series m a -> Property m
leftIdentity s = over s $ \x -> mempty <> x == x

-- | Check the /right identity/ law holds for the given 'Monoid' 'Series':
--
-- @
-- x '<>' 'mempty' ≡ x
-- @
rightIdentity
  :: (Eq a, Monad m, Show a, Monoid a)
  => Series m a -> Property m
rightIdentity s = over s $ \x -> x <> mempty == x

-- * Associativity

-- | Check the /associativity/ law holds for the given 'Monoid' 'Series':
--
-- @
-- x '<>' (y '<>' z) ≡ (x '<>' y) '<>' z
-- @
--
-- This uses the product of the 3 'Series', be aware of combinatorial explosion.
associativity
  :: (Eq a, Monad m, Show a, Monoid a)
  => Series m a -> Series m a -> Series m a -> Property m
associativity xs ys zs =
    over xs $ \x ->
        over ys $ \y ->
            over zs $ \z ->
    x <> (y <> z) == (x <> y) <> z

-- | Check the /associativity/ law hold for the given 'Monoid' 'Series':
--
-- @
-- x '<>' (y '<>' z) ≡ (x '<>' y) '<>' z
-- @
--
-- This uses the sum of the 3 'Series'.
associativitySum
  :: (Eq a, Monad m, Show a, Monoid a)
  => Series m a -> Series m a -> Series m a -> Property m
associativitySum xs ys zs =
    over (zipLogic3 xs ys zs) $ \(x,y,z) ->
        x <> (y <> z) == (x <> y) <> z

-- * mconcat

-- | When implementing 'mconcat' yourself this must hold:
--
-- @
-- 'mconcat' ≡ 'foldr' 'mappend' 'mempty'
-- @
mconcatProp
  :: (Eq a, Monad m, Show a, Monoid a)
  => Series m a -> Property m
mconcatProp s = over (sequenceA $ replicate 3 s) $ \l ->
    Monoid.mconcat l == foldr mappend mempty l
