{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Test.Validity.Operations.Associativity
    ( associativeOnGens
    , associativeOnValids
    , associative
    , associativeOnArbitrary
    ) where

import Data.GenValidity

import Test.Hspec
import Test.QuickCheck

-- |
--
-- \[
--     Associative(\star)
--     \quad\equiv\quad
--     \forall a, b, c:
--     (a \star b) \star c = a \star (b \star c)
-- \]
associativeOnGens ::
       (Show a, Eq a)
    => (a -> a -> a)
    -> Gen (a, a, a)
    -> ((a, a, a) -> [(a, a, a)])
    -> Property
associativeOnGens op gen s =
    forAllShrink gen s $ \(a, b, c) ->
        ((a `op` b) `op` c) `shouldBe` (a `op` (b `op` c))

associativeOnValids :: (Show a, Eq a, GenValid a) => (a -> a -> a) -> Property
associativeOnValids op = associativeOnGens op genValid shrinkValid

-- |
--
-- prop> associative ((*) :: Int -> Int -> Int)
-- prop> associative ((+) :: Int -> Int -> Int)
associative :: (Show a, Eq a, GenUnchecked a) => (a -> a -> a) -> Property
associative op = associativeOnGens op genUnchecked shrinkUnchecked

-- |
--
-- prop> associativeOnArbitrary ((*) :: Int -> Int -> Int)
-- prop> associativeOnArbitrary ((+) :: Int -> Int -> Int)
associativeOnArbitrary ::
       (Show a, Eq a, Arbitrary a) => (a -> a -> a) -> Property
associativeOnArbitrary op = associativeOnGens op arbitrary shrink
