packages feed

dtmc-0.2.0.0: src/Dtmc/State.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}

{- |
Module      : Dtmc.State
Description : Canonical indexing for finite named state types.

'FiniteState' identifies a globally finite state type with a canonical total
indexing by @'Finite' ('Cardinality' state)@. The generic implementation
supports enumeration types whose constructors have no fields. Enable
@DeriveAnyClass@ and @DeriveGeneric@, then derive both 'Generic' and
'FiniteState':

@
data Weather = Dry | Wet | Storm
    deriving (Eq, Ord, Show, Generic, FiniteState)
@

After deriving 'Generic', declaring an empty @instance FiniteState Weather@
is a more explicit way to select the same generic defaults.

Constructor declaration order determines vector and matrix order. A stock
derived 'Ord' instance has the same order and is the intended companion;
handwritten 'FiniteState' and 'Ord' instances are trusted to preserve the
documented ordering and bijection laws.
-}
module Dtmc.State (
    type Cardinality,
    type GenericCardinality,
    FiniteState,
    finiteStates,
    stateIndex,
    stateAt,
) where

import Data.Finite (
    Finite,
    finite,
    finites,
    getFinite,
 )
import Data.Kind (
    Type,
 )
import Data.Proxy (
    Proxy (Proxy),
 )
import GHC.Generics (
    C,
    D,
    Generic (Rep, from, to),
    M1 (M1),
    U1 (U1),
    V1,
    type (:+:) (L1, R1),
 )
import GHC.TypeLits (
    ErrorMessage (Text),
    TypeError,
 )
import GHC.TypeNats (
    KnownNat,
    Nat,
    natVal,
    type (+),
 )

{- | The number of inhabitants of a finite state type. For 'Finite', this is
its existing type-level bound; for every other type, it is derived from its
'Generic' representation. Users cannot override this closed family
independently of that representation.

A generic constructor carrying any fields reduces to a custom 'TypeError'.
-}
type family Cardinality (state :: Type) :: Nat where
    Cardinality (Finite n) = n
    Cardinality state = GenericCardinality (Rep state)

{- | A finite state type with a canonical bijection to
@'Finite' ('Cardinality' state)@.

Instances must satisfy:

* @stateAt (stateIndex state) == state@;
* @stateIndex (stateAt index) == index@;
* @finiteStates == map stateAt finites@;
* @finiteStates@ is strictly ascending according to 'Ord'.

The generic defaults satisfy these laws for fieldless enumeration types with
a stock derived 'Ord' instance. Handwritten method implementations are trusted
to satisfy them, but their 'Cardinality' still comes from the supported
'Generic' representation. Empty state types are supported: their state list is
empty and 'stateAt' has an uninhabited 'Finite 0' domain.
-}
class (Ord state, KnownNat (Cardinality state)) => FiniteState state where
    {- | Return every state exactly once, in canonical index order.

    Complexity: implementation-dependent.
    -}
    finiteStates :: [state]

    {- | Convert a state to its total, statically bounded index.

    Complexity: implementation-dependent.
    -}
    stateIndex :: state -> Finite (Cardinality state)

    {- | Recover the state at a statically bounded index.

    Complexity: implementation-dependent.
    -}
    stateAt :: Finite (Cardinality state) -> state

    default finiteStates ::
        ( Generic state
        , GenericFiniteState (Rep state)
        ) =>
        [state]
    finiteStates = map to genericStates

    default stateIndex ::
        ( Generic state
        , GenericFiniteState (Rep state)
        ) =>
        state ->
        Finite (Cardinality state)
    stateIndex = finite . genericIndex . from

    default stateAt ::
        ( Generic state
        , GenericFiniteState (Rep state)
        ) =>
        Finite (Cardinality state) ->
        state
    stateAt = to . genericAt . fromIntegral . getFinite

instance (KnownNat n) => FiniteState (Finite n) where
    finiteStates = finites
    stateIndex = id
    stateAt = id

instance FiniteState ()

instance FiniteState Bool

instance FiniteState Ordering

{- | The type-level cardinality of a 'Generic' representation. This advanced
helper underlies 'Cardinality'; ordinary users should use 'FiniteState'
instead.
-}
type family GenericCardinality (representation :: Type -> Type) :: Nat where
    GenericCardinality (M1 D metadata representation) =
        GenericCardinality representation
    GenericCardinality (left :+: right) =
        GenericCardinality left + GenericCardinality right
    GenericCardinality (M1 C metadata U1) = 1
    GenericCardinality (M1 C metadata fields) =
        TypeError
            ( 'Text
                "FiniteState: constructors with fields are unsupported"
            )
    GenericCardinality V1 = 0

-- Generic machinery implementing the canonical state/index bijection for
-- fieldless enumeration representations.
class GenericFiniteState representation where
    genericStates :: [representation value]
    genericIndex :: representation value -> Integer
    genericAt :: Integer -> representation value

instance
    (GenericFiniteState representation) =>
    GenericFiniteState (M1 D metadata representation)
    where
    genericStates = map M1 genericStates
    genericIndex (M1 value) = genericIndex value
    genericAt = M1 . genericAt

instance
    ( GenericFiniteState left
    , GenericFiniteState right
    , KnownNat (GenericCardinality left)
    ) =>
    GenericFiniteState (left :+: right)
    where
    genericStates =
        map L1 (genericStates @left)
            ++ map R1 (genericStates @right)

    genericIndex (L1 value) = genericIndex value
    genericIndex (R1 value) = genericCardinality @left + genericIndex value

    genericAt index
        | index < genericCardinality @left = L1 (genericAt index)
        | otherwise =
            R1 (genericAt (index - genericCardinality @left))

genericCardinality ::
    forall representation.
    (KnownNat (GenericCardinality representation)) =>
    Integer
genericCardinality =
    fromIntegral (natVal (Proxy @(GenericCardinality representation)))

instance {-# OVERLAPPING #-} GenericFiniteState (M1 C metadata U1) where
    genericStates = [M1 U1]
    genericIndex (M1 U1) = 0
    genericAt _ = M1 U1

instance
    {-# OVERLAPPABLE #-}
    ( TypeError
        ( 'Text
            "FiniteState: constructors with fields are unsupported"
        )
    ) =>
    GenericFiniteState (M1 C metadata fields)
    where
    genericStates = unsupportedConstructorFields
    genericIndex _ = unsupportedConstructorFields
    genericAt _ = unsupportedConstructorFields

-- Required only to complete an instance made unusable by its 'TypeError'.
unsupportedConstructorFields :: value
unsupportedConstructorFields =
    error "Dtmc.State: constructors with fields are unsupported"

instance GenericFiniteState V1 where
    genericStates = []
    genericIndex value = case value of {}
    genericAt _ =
        error "Dtmc.State.stateAt: unreachable Finite 0 index"