packages feed

monoidmap-0.0.1.7: src/test/Data/MonoidMap/DistributivitySpec.hs

{-# LANGUAGE RankNTypes #-}
{- HLINT ignore "Redundant bracket" -}
{- HLINT ignore "Use camelCase" -}
{- HLINT ignore "Use null" -}

-- |
-- Copyright: © 2022–2025 Jonathan Knowles
-- License: Apache-2.0
--
module Data.MonoidMap.DistributivitySpec
    ( spec
    ) where

import Prelude

import Control.Monad
    ( forM_ )
import Data.Data
    ( typeRep )
import Data.Function
    ( (&) )
import Data.Maybe
    ( isJust )
import Data.MonoidMap
    ( MonoidMap, get )
import Data.Proxy
    ( Proxy (..) )
import Test.Common
    ( Key
    , Test
    , TestType (..)
    , TestValue
    , property
    , testTypesGCDMonoid
    , testTypesGroup
    , testTypesLCMMonoid
    , testTypesLeftGCDMonoid
    , testTypesLeftReductive
    , testTypesMonoidNull
    , testTypesMonus
    , testTypesOverlappingGCDMonoid
    , testTypesReductive
    , testTypesRightGCDMonoid
    , testTypesRightReductive
    )
import Test.Hspec
    ( Spec, describe, it )
import Test.QuickCheck
    ( Property, cover, (===) )

import qualified Data.Group as Group
    ( Group (..) )
import qualified Data.Monoid.GCD as LeftGCDMonoid
    ( LeftGCDMonoid (..) )
import qualified Data.Monoid.GCD as RightGCDMonoid
    ( RightGCDMonoid (..) )
import qualified Data.Monoid.GCD as OverlappingGCDMonoid
    ( OverlappingGCDMonoid (..) )
import qualified Data.Monoid.GCD as GCDMonoid
    ( GCDMonoid (..) )
import qualified Data.Monoid.LCM as LCMMonoid
    ( LCMMonoid (..) )
import qualified Data.Monoid.Monus as Monus
    ( Monus (..) )
import qualified Data.Semigroup as Semigroup
    ( Semigroup (..) )
import qualified Data.Semigroup.Cancellative as LeftReductive
    ( LeftReductive (..) )
import qualified Data.Semigroup.Cancellative as RightReductive
    ( RightReductive (..) )
import qualified Data.Semigroup.Cancellative as Reductive
    ( Reductive (..) )

spec :: Spec
spec = do
    specDistributiveGet
    specDistributiveGetMaybe

specDistributiveGet :: Spec
specDistributiveGet = do
    specForAll
        testTypesMonoidNull
        "Semigroup.<>"
        (Semigroup.<>)
        (Semigroup.<>)
    specForAll
        testTypesLeftGCDMonoid
        "LeftGCDMonoid.commonPrefix"
        (LeftGCDMonoid.commonPrefix)
        (LeftGCDMonoid.commonPrefix)
    specForAll
        testTypesRightGCDMonoid
        "RightGCDMonoid.commonSuffix"
        (RightGCDMonoid.commonSuffix)
        (RightGCDMonoid.commonSuffix)
    specForAll
        testTypesOverlappingGCDMonoid
        "OverlappingGCDMonoid.overlap"
        (OverlappingGCDMonoid.overlap)
        (OverlappingGCDMonoid.overlap)
    specForAll
        testTypesGCDMonoid
        "GCDMonoid.gcd"
        (GCDMonoid.gcd)
        (GCDMonoid.gcd)
    specForAll
        testTypesLCMMonoid
        "LCMMonoid.lcm"
        (LCMMonoid.lcm)
        (LCMMonoid.lcm)
    specForAll
        testTypesGroup
        "Group.minus"
        (Group.~~)
        (Group.~~)
    specForAll
        testTypesMonus
        "Monus.monus"
        (Monus.<\>)
        (Monus.<\>)
  where
    specForAll
        :: [TestType c]
        -> String
        -> (forall k v m. (Test k v, c v, m ~ MonoidMap k v) => (m -> m -> m))
        -> (forall v. (TestValue v, c v) => (v -> v -> v))
        -> Spec
    specForAll testTypes funName f g =
        describe description $ forM_ testTypes $ specFor f g
      where
        description = "Distributivity of 'get' with '" <> funName <> "'"

    specFor
        :: (forall k v m. (Test k v, c v, m ~ MonoidMap k v) => (m -> m -> m))
        -> (forall v. (TestValue v, c v) => (v -> v -> v))
        -> TestType c
        -> Spec
    specFor f g (TestType (_ :: Proxy v)) =
        it description $ property $ propDistributiveGet @Key @v f g
      where
        description = show $ typeRep $ Proxy @(MonoidMap Key v)

specDistributiveGetMaybe :: Spec
specDistributiveGetMaybe = do
    specForAll
        testTypesLeftReductive
        "LeftReductive.stripPrefix"
        (LeftReductive.stripPrefix)
        (LeftReductive.stripPrefix)
    specForAll
        testTypesRightReductive
        "RightReductive.stripSuffix"
        (RightReductive.stripSuffix)
        (RightReductive.stripSuffix)
    specForAll
        testTypesReductive
        "Reductive.minusMaybe"
        (Reductive.</>)
        (Reductive.</>)
  where
    specForAll
        :: [TestType c]
        -> String
        -> (forall k v m. (Test k v, c v, m ~ MonoidMap k v)
            => (m -> m -> Maybe m))
        -> (forall v. (TestValue v, c v)
            => (v -> v -> Maybe v))
        -> Spec
    specForAll testTypes funName f g =
        describe description $ forM_ testTypes $ specFor f g
      where
        description = "Distributivity of 'get' with '" <> funName <> "'"

    specFor
        :: (forall k v m. (Test k v, c v, m ~ MonoidMap k v)
            => (m -> m -> Maybe m))
        -> (forall v. (TestValue v, c v)
            => (v -> v -> Maybe v))
        -> TestType c
        -> Spec
    specFor f g (TestType (_ :: Proxy v)) =
        it description $ property $ propDistributiveGetMaybe @Key @v f g
      where
        description = show $ typeRep $ Proxy @(MonoidMap Key v)

propDistributiveGet
    :: Test k v
    => (MonoidMap k v -> MonoidMap k v -> MonoidMap k v)
    -> (v -> v -> v)
    -> k
    -> MonoidMap k v
    -> MonoidMap k v
    -> Property
propDistributiveGet f g k m1 m2 =
    get k (f m1 m2) === g (get k m1) (get k m2)
    & cover 2
        (get k (f m1 m2) == mempty)
        "get k (f m1 m2) == mempty"
    & cover 2
        (get k (f m1 m2) /= mempty)
        "get k (f m1 m2) /= mempty"
    & cover 2
        (get k m1 == mempty && get k m2 == mempty)
        "get k m1 == mempty && get k m2 == mempty"
    & cover 2
        (get k m1 == mempty && get k m2 /= mempty)
        "get k m1 == mempty && get k m2 /= mempty"
    & cover 2
        (get k m1 /= mempty && get k m2 == mempty)
        "get k m1 /= mempty && get k m2 == mempty"
    & cover 2
        (get k m1 /= mempty && get k m2 /= mempty)
        "get k m1 /= mempty && get k m2 /= mempty"

propDistributiveGetMaybe
    :: Test k v
    => (MonoidMap k v -> MonoidMap k v -> Maybe (MonoidMap k v))
    -> (v -> v -> Maybe v)
    -> k
    -> MonoidMap k v
    -> MonoidMap k v
    -> Property
propDistributiveGetMaybe f g k m1 m2 = property $
    all (\m -> g (get k m1) (get k m2) == Just (get k m)) (f m1 m2)
    & cover 2
        (isJust (f m1 m2) && g (get k m1) (get k m2) == Just mempty)
        "isJust (f m1 m2) && g (get k m1) (get k m2) == Just mempty"
    & cover 2
        (isJust (f m1 m2) && g (get k m1) (get k m2) /= Just mempty)
        "isJust (f m1 m2) && g (get k m1) (get k m2) /= Just mempty"