dtmc-0.2.0.0: test/Dtmc/StateSpec.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE EmptyDataDeriving #-}
{-# LANGUAGE TypeApplications #-}
module Dtmc.StateSpec (
spec,
) where
import Data.Finite (
Finite,
finites,
getFinite,
)
import Data.List (
sort,
)
import Data.Proxy (
Proxy (Proxy),
)
import Dtmc.State (
Cardinality,
FiniteState,
finiteStates,
stateAt,
stateIndex,
)
import GHC.Generics (
Generic,
)
import GHC.TypeNats (
natVal,
)
import Test.Hspec (
Spec,
describe,
it,
shouldBe,
)
data Empty
deriving (Eq, Ord, Show, Generic)
data One = One
deriving (Eq, Ord, Show, Generic)
data Three = A | B | C
deriving (Eq, Ord, Show, Generic, FiniteState)
instance FiniteState Empty
instance FiniteState One
spec :: Spec
spec = do
describe "generic FiniteState" $ do
it "can be derived directly in the state declaration" $
finiteStates @Three `shouldBe` [A, B, C]
it "derives cardinalities for empty, singleton, and sum types" $ do
natVal (Proxy @(Cardinality Empty)) `shouldBe` 0
natVal (Proxy @(Cardinality One)) `shouldBe` 1
natVal (Proxy @(Cardinality Three)) `shouldBe` 3
it "enumerates states in constructor order" $
finiteStates @Three `shouldBe` [A, B, C]
it "uses the same order as a stock Ord instance" $
finiteStates @Three `shouldBe` sort (finiteStates @Three)
it "round-trips every state through its finite index" $
map (stateAt . stateIndex) (finiteStates @Three)
`shouldBe` finiteStates @Three
it "round-trips every finite index through its state" $
map (stateIndex . stateAt @Three) finites `shouldBe` finites
it "assigns consecutive zero-based indices" $
map (getFinite . stateIndex) (finiteStates @Three)
`shouldBe` [0, 1, 2]
it "supports empty and singleton state types" $ do
finiteStates @Empty `shouldBe` []
finiteStates @One `shouldBe` [One]
stateAt (stateIndex One) `shouldBe` One
describe "Empty laws" (finiteStateLaws @Empty)
describe "One laws" (finiteStateLaws @One)
describe "Three laws" (finiteStateLaws @Three)
describe "Finite identity instance" $ do
it "preserves enumeration and both conversions" $ do
finiteStates @(Finite 3) `shouldBe` finites
map stateIndex (finites @3) `shouldBe` finites
map stateAt (finites @3) `shouldBe` finites
it "supports Finite 0" $
finiteStates @(Finite 0) `shouldBe` []
describe "Finite 0 laws" (finiteStateLaws @(Finite 0))
describe "Finite 3 laws" (finiteStateLaws @(Finite 3))
describe "base instances" $ do
it "use their standard constructor order" $ do
finiteStates @() `shouldBe` [()]
finiteStates @Bool `shouldBe` [False, True]
finiteStates @Ordering `shouldBe` [LT, EQ, GT]
describe "() laws" (finiteStateLaws @())
describe "Bool laws" (finiteStateLaws @Bool)
describe "Ordering laws" (finiteStateLaws @Ordering)
finiteStateLaws ::
forall state.
(FiniteState state, Show state) =>
Spec
finiteStateLaws = do
it "enumerates every finite index in canonical order" $
finiteStates @state
`shouldBe` map (stateAt @state) (finites @(Cardinality state))
it "round-trips every state through its index" $
map (stateAt . stateIndex) (finiteStates @state)
`shouldBe` finiteStates @state
it "round-trips every index through its state" $
map (stateIndex . stateAt @state) (finites @(Cardinality state))
`shouldBe` finites @(Cardinality state)
it "enumerates states in strictly ascending order" $
and (zipWith (<) states (drop 1 states)) `shouldBe` True
where
states = finiteStates @state