packages feed

moonlight-core-0.1.0.0: test/basis/OrderSpec.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE TypeApplications #-}

module OrderSpec
  ( tests,
  )
where

import Control.Monad
  ( when,
  )
import Data.Foldable
  ( traverse_,
  )
import Data.Set qualified as Set
import Moonlight.Core
  ( FiniteUniverse (..),
    boundedEnumUniverse,
    finiteUniverseList,
    finiteUniverseSet,
  )
import Moonlight.Core
  ( PartialOrder (..),
    comparable,
    finitePointwiseLeq,
    incomparable,
    pointwiseLeqOver,
  )
import Test.Tasty
  ( TestTree,
    testGroup,
  )
import Test.Tasty.HUnit
  ( Assertion,
    assertBool,
    testCase,
    (@?=),
  )

data TinyDomain
  = TinyA
  | TinyB
  | TinyC
  deriving stock (Eq, Ord, Show, Enum, Bounded)

instance FiniteUniverse TinyDomain where
  finiteUniverse =
    boundedEnumUniverse

tests :: TestTree
tests =
  testGroup
    "partial order"
    [ testCase "Bool satisfies the partial-order laws" $
        assertPartialOrderLaws "Bool" [False, True],
      testCase "product order satisfies the partial-order laws" $
        assertPartialOrderLaws "Bool product" boolProductSamples,
      testCase "set inclusion satisfies the partial-order laws" $
        assertPartialOrderLaws "Set Int" setSamples,
      testCase "strict order is non-equal less-or-equal" testStrictOrder,
      testCase "product order is pointwise and admits incomparable points" testProductOrder,
      testCase "finite pointwise order quantifies over the finite universe" testFinitePointwiseOrder
    ]

assertPartialOrderLaws ::
  (PartialOrder order, Show order) =>
  String ->
  [order] ->
  Assertion
assertPartialOrderLaws label values = do
  traverse_ (assertReflexive label) values
  traverse_ (uncurry (assertAntisymmetric label)) (pairs values)
  traverse_ (assertTransitive label) (triples values)

assertReflexive ::
  (PartialOrder order, Show order) =>
  String ->
  order ->
  Assertion
assertReflexive label value =
  assertBool
    (label <> " is not reflexive at " <> show value)
    (leq value value)

assertAntisymmetric ::
  (PartialOrder order, Show order) =>
  String ->
  order ->
  order ->
  Assertion
assertAntisymmetric label left right =
  when (leq left right && leq right left) $
    assertBool
      ( label
          <> " violates antisymmetry at "
          <> show (left, right)
      )
      (left == right)

assertTransitive ::
  (PartialOrder order, Show order) =>
  String ->
  (order, order, order) ->
  Assertion
assertTransitive label (left, middle, right) =
  when (leq left middle && leq middle right) $
    assertBool
      ( label
          <> " is not transitive at "
          <> show (left, middle, right)
      )
      (leq left right)

testStrictOrder :: Assertion
testStrictOrder = do
  lt False True @?= True
  lt True True @?= False
  lt True False @?= False

testProductOrder :: Assertion
testProductOrder = do
  leq (False, True) (True, True) @?= True
  leq (False, True) (True, False) @?= False
  comparable (False, True) (True, True) @?= True
  incomparable (False, True) (True, False) @?= True

testFinitePointwiseOrder :: Assertion
testFinitePointwiseOrder = do
  finiteUniverseList @TinyDomain @?= [TinyA, TinyB, TinyC]
  finiteUniverseSet @TinyDomain @?= Set.fromList [TinyA, TinyB, TinyC]
  pointwiseLeqOver [TinyA, TinyC] leftProjection rightProjection @?= True
  finitePointwiseLeq leftProjection rightProjection @?= False
  finitePointwiseLeq (const False) rightProjection @?= True

leftProjection :: TinyDomain -> Bool
leftProjection TinyA =
  True
leftProjection TinyB =
  True
leftProjection TinyC =
  False

rightProjection :: TinyDomain -> Bool
rightProjection TinyA =
  True
rightProjection TinyB =
  False
rightProjection TinyC =
  False

boolProductSamples :: [(Bool, Bool)]
boolProductSamples =
  (,) <$> [False, True] <*> [False, True]

setSamples :: [Set.Set Int]
setSamples =
  Set.fromList
    <$> [ [],
          [1],
          [2],
          [1, 2],
          [1, 2, 3]
        ]

pairs :: [a] -> [(a, a)]
pairs values =
  (,) <$> values <*> values

triples :: [a] -> [(a, a, a)]
triples values =
  (,,) <$> values <*> values <*> values