packages feed

proarrow-0.1.0.0: test/Props/Discrete.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Props.Discrete where

import Data.Type.Equality (type (:~:))
import Data.Type.Equality qualified as Eq
import Data.Type.Nat (SNat (..), snat)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Falsify (testProperty)
import Prelude

import Proarrow.Category.Enriched.Thin
  ( DecidableProfunctor (..)
  , Decision (..)
  , Holds
  , Indexed (..)
  , KnownIndex
  , Objects
  , ThinProfunctor (..)
  )
import Proarrow.Category.Enriched.Thin.Composition (Closure)
import Proarrow.Category.Instance.Bool (BOOL (..), Booleans (..))
import Proarrow.Category.Instance.Discrete (CODISCRETE (..), Codiscrete, DISCRETE (..), Discrete (..))
import Proarrow.Core (CAT, Profunctor (..), UN)

test :: TestTree
test =
  testGroup
    "Discrete"
    [ testProperty "reachability over a bare set finds the edge" $ withArr reachEdge (pure ())
    , testProperty "distinct points are decided apart" $ case decide @Discrete @(D FLS) @(D TRU) of
        No -> pure ()
    ]

-- | A point of the bare set @DISCRETE BOOL@ is recovered from its index alone.
pointOf :: forall (a :: DISCRETE BOOL). (KnownIndex a) => Booleans (UN D a) (UN D a)
pointOf = case snat @(Index a) of
  SZ -> Fls
  SS @i -> case snat @i of SZ -> Tru

-- | The graph with the single edge @FLS -> TRU@ on the bare two-point set: unlike over the walking
-- arrow, there is no base arrow to fall back on.
type Edge :: CAT (DISCRETE BOOL)
data Edge a b where
  FT :: Edge (D FLS) (D TRU)

instance Profunctor Edge where
  dimap Refl Refl e = e
  r \\ FT = r

type family EdgeHolds (a :: BOOL) (b :: BOOL) :: BOOL where
  EdgeHolds FLS TRU = TRU
  EdgeHolds a b = FLS

instance ThinProfunctor Edge

instance DecidableProfunctor Edge where
  type Holds Edge a b = EdgeHolds (UN D a) (UN D b)
  decide @a @b = case (pointOf @a, pointOf @b) of
    (Fls, Fls) -> No
    (Fls, Tru) -> Yes FT
    (Tru, Fls) -> No
    (Tru, Tru) -> No
  toHolds FT r = r

-- | The closure over the bare set: the edge is found, its reverse is not, and points reach themselves.
reachEdge :: Closure Edge (D FLS) (D TRU)
reachEdge = arr

noWayBack :: Holds (Closure Edge) (D TRU) (D FLS) :~: FLS
noWayBack = Eq.Refl

reachSelf :: Holds (Closure Edge) (D TRU) (D TRU) :~: TRU
reachSelf = Eq.Refl

-- | The discrete category itself is decided by comparing indices.
samePoint :: Holds (Discrete :: CAT (DISCRETE BOOL)) (D FLS) (D FLS) :~: TRU
samePoint = Eq.Refl

otherPoint :: Holds (Discrete :: CAT (DISCRETE BOOL)) (D FLS) (D TRU) :~: FLS
otherPoint = Eq.Refl

-- * The codiscrete category on the same points

-- | Its objects are the points of @k@, in the same order.
objectsCodiscrete :: Objects (CODISCRETE BOOL) :~: '[CD FLS, CD TRU]
objectsCodiscrete = Eq.Refl

-- | Every point reaches every other, and the closure computes that by searching the points. This
-- only typechecks because the codiscrete category is enumerable.
codiscreteReaches :: Holds (Closure (Codiscrete :: CAT (CODISCRETE BOOL))) (CD TRU) (CD FLS) :~: TRU
codiscreteReaches = Eq.Refl