packages feed

proarrow-0.1.0.0: testing/Proarrow/Testing/Laws/Run.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

-- | Checking laws stated as code ("Proarrow.Tools.Laws") in a 'Testable' category: 'testLaws'
-- runs each law with random objects for its variables and random arrows for the ones it asks for,
-- and compares both sides.
--
-- The endpoints of a law's equation are built inside the law, so their 'TestOb' cannot be listed
-- up front. Instead the law is run in 'TESTED' @k@, whose objects are built from leaves by the
-- structures' object formers; there an object's 'Ob' is 'Tested', which rebuilds the 'TestOb' of
-- the object of @k@ it stands for from the 'Witnesses' passed at run time: one 'Witness' per
-- structure in the law's list, e.g. a 'WithTestOb2' for 'M.Monoidal'.
--
-- A structure this module does not cover can be added from outside it, in the same way as the ones
-- here:
--
-- * a former for each new kind of object (an open data family, or one of the free category's),
--   with a 'Tested' instance giving its 'Untest' and rebuilding its 'Ob' and 'TestOb';
-- * a 'Witness' instance for the structure, holding how 'TestOb' is closed under its formers;
-- * the structure's class instance for 'TESTED', whose arrows describe themselves ('prim' for a
--   named arrow, 'app', 'apps', 'infixlDoc', 'infixrDoc' for operations on arrows).
--
-- 'testProLaws' checks the laws of a profunctor class ("Proarrow.Tools.Laws" 'Laws.ProLaws') the
-- same way, with the profunctor interpreted as 'TestedP', whose elements describe themselves like
-- the arrows of 'TESTED' do ('TestedArr' is 'TestedP' at the hom profunctor). Its domain and
-- codomain each get their own 'Witnesses'. An object former that crosses from one to the other,
-- like 'RepF' for @p '%' b@, carries the witnesses of the side it comes from in its structure's
-- 'Witness' ('RepresentedBy', 'CorepresentedBy'). The laws of 'Adj.Proadjunction' and
-- 'Promonad.Procomonad' build composites whose middle object is only known to be an object; the
-- runner makes it a leaf, which needs 'TestOb' to follow from 'Ob'.
module Proarrow.Testing.Laws.Run
  ( testLaws
  , testLawsWith
  , testProLaws

    -- * Witnesses
  , Witness (..)
  , Witnesses (..)
  , HasWitness (..)

    -- * Interpreting with testable objects
  , TESTED (..)
  , Tested (..)
  , untestOb2
  , untestOb3
  , untestTestOb2
  , TestedArr
  , pattern TestedArr

    -- * Interpreting profunctors
  , TestedP (..)
  , RepF
  , RepresentedBy
  , CorepF
  , CorepresentedBy

    -- * Describing arrows
  , Doc
  , prim
  , atom
  , app
  , apps
  , infixlDoc
  , infixrDoc
  ) where

import Data.Kind (Constraint, Type)
import Test.Falsify (Property)
import Test.Falsify.Generator (Gen)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Falsify (TestOptions, testProperty, testPropertyWith)
import Prelude hiding (fst, id, snd, (.))

import Proarrow.Adjunction qualified as Adj
import Proarrow.Category.Enriched.Dagger (DaggerProfunctor (..))
import Proarrow.Category.Enriched.Finitary (Finitary (..))
import Proarrow.Category.Instance.Free qualified as Free
import Proarrow.Category.Monoidal qualified as M
import Proarrow.Category.Monoidal.Closed qualified as Exponential
import Proarrow.Category.Monoidal.CompactClosed qualified as CC
import Proarrow.Category.Monoidal.CopyDiscard qualified as CopyDiscard
import Proarrow.Category.Monoidal.Distributive qualified as Distributive
import Proarrow.Category.Monoidal.StarAutonomous qualified as SA
import Proarrow.Category.Monoidal.Strength qualified as Strength
import Proarrow.Colimit.BinaryCoproduct qualified as BinaryCoproduct
import Proarrow.Colimit.Initial qualified as Initial
import Proarrow.Core (CAT, CategoryOf (..), Hom, Kind, Profunctor (..), Promonad (..), type (+->))
import Proarrow.Limit.BinaryProduct qualified as BinaryProduct
import Proarrow.Limit.Terminal qualified as Terminal
import Proarrow.Monoid qualified as Monoid
import Proarrow.Profunctor.Corepresentable (Corepresentable (..), withObCorep)
import Proarrow.Profunctor.Instance.Composition ((:.:) (..))
import Proarrow.Profunctor.Representable (Representable (..), withObRep)
import Proarrow.Promonad qualified as Promonad
import Proarrow.Testing
  ( Some (..)
  , SomeProfunctorElt (..)
  , TestObIsOb
  , Testable (..)
  , TestableProfunctor (..)
  , WithTestOb2
  , WithTestObCoprod
  , WithTestObCorep
  , WithTestObDual
  , WithTestObExp
  , WithTestObProd
  , WithTestObRep
  , genNamed
  , genOb
  , genObSuchThatWith
  , isGenNonEmpty
  , obFromTestOb
  , testEq
  )
import Proarrow.Tools.Laws qualified as Laws

-- | Check the laws of @'Laws.Laws' cs@ in @k@, one property per law: run each law in 'TESTED'
-- with random objects for its variables and random arrows for the ones it asks for. The
-- 'Witnesses' say how 'TestOb' is closed under the structures of @cs@ (see 'Tested').
testLaws :: forall cs k. (Laws.Laws cs, Testable k, Free.All cs (TESTED cs k)) => String -> Witnesses cs k -> TestTree
testLaws = testLawsWith @cs (genOb @k)

-- | 'testLaws' with the objects for the variables drawn from the given generator, e.g.
-- 'Proarrow.Testing.genObSmall' where the laws build large objects like exponentials.
testLawsWith
  :: forall cs k
   . (Laws.Laws cs, Testable k, Free.All cs (TESTED cs k))
  => Property (Some k) -> String -> Witnesses cs k -> TestTree
testLawsWith genObject name witnesses =
  testGroup name [testProperty (Laws.lawName law) (checkLaw law) | law <- Laws.laws @cs]
  where
    checkLaw :: Laws.Law cs -> Property ()
    checkLaw (Laws.Law lawName body) = do
      Some @a <- genObject
      Some @b <- genObject
      Some @c <- genObject
      Some @d <- genObject
      Some @e <- genObject
      eq <-
        body @(TLeaf a :: TESTED cs k) @(TLeaf b) @(TLeaf c) @(TLeaf d) @(TLeaf e) (genArr witnesses)
      testEquation witnesses lawName eq

-- | Check the laws of @'Laws.ProLaws' c@ for the profunctor @p@, one property per law: run each
-- law with @p@ interpreted as 'TestedP' @p@, its elements drawn by 'genProfunctorElt' (each picks
-- its two object variables), the other variables of a 'Laws.ProLaw' drawn along the chain
-- @e '~>' c '~>' a@ and @b '~>' d '~>' f@ where those hom-sets are non-empty, and random arrows
-- for the ones it asks for. The two 'Witnesses' are for the domain @j@ and the codomain
-- @k@ of @p@, the 'TestOptions' apply to each law's property, and the other variables are drawn from
-- the given generator of objects, e.g. 'genSomeSmall' where the laws tensor several objects
-- together.
testProLaws
  :: forall {j} {k} csj csk cl (p :: j +-> k)
   . (Laws.ProLaws cl, TestableProfunctor p, cl (TestedP p :: TESTED csj j +-> TESTED csk k))
  => TestOptions
  -> (forall i. (Testable i) => Gen (Some i))
  -> String
  -> Witnesses csj j
  -> Witnesses csk k
  -> TestTree
testProLaws opts genObjects name wsj wsk =
  testGroup name [testPropertyWith opts (Laws.proLawName law) (checkLaw law) | law <- Laws.proLaws @cl]
  where
    checkLaw :: Laws.ProLaw cl -> Property ()
    checkLaw (Laws.ProLaw lawName body) = do
      SomeP @a @b p0 <- genProfunctorElt @p "p"
      Some @c <- genObSuchThatWith (genObjects @k) \(Some @c') -> isGenNonEmpty @(c' ~> a)
      Some @d <- genObSuchThatWith (genObjects @j) \(Some @d') -> isGenNonEmpty @(b ~> d')
      Some @e <- genObSuchThatWith (genObjects @k) \(Some @e') -> isGenNonEmpty @(e' ~> c)
      Some @f <- genObSuchThatWith (genObjects @j) \(Some @f') -> isGenNonEmpty @(d ~> f')
      eq <-
        body @(TestedP p) @(TLeaf a :: TESTED csk k) @(TLeaf b :: TESTED csj j) @(TLeaf c) @(TLeaf d) @(TLeaf e) @(TLeaf f)
          (prim "p" p0)
          (genArr wsk)
          (genArr wsj)
      testProEquation lawName eq
    checkLaw (Laws.ProLaw3 lawName body) = do
      SomeP @a @b p0 <- genProfunctorElt @p "p"
      SomeP @c @d p1 <- genProfunctorElt @p "p'"
      SomeP @e @f p2 <- genProfunctorElt @p "p''"
      eq <-
        body @(TestedP p) @(TLeaf a :: TESTED csk k) @(TLeaf b :: TESTED csj j) @(TLeaf c) @(TLeaf d) @(TLeaf e) @(TLeaf f)
          (prim "p" p0)
          (prim "p'" p1)
          (prim "p''" p2)
          (genArr wsk)
          (genArr wsj)
      testProEquation lawName eq
    testProEquation :: String -> Laws.ProEquation (TestedP p :: TESTED csj j +-> TESTED csk k) -> Property ()
    testProEquation lawName = \case
      l Laws.:=: r -> testTested wsj wsk lawName l r
      Laws.InK e -> testEquation wsk lawName e
      Laws.InJ e -> testEquation wsj lawName e

-- | Compare the two sides of an equation between arrows of 'TESTED', printing them on failure.
testEquation :: forall cs k. (Testable k) => Witnesses cs k -> String -> Laws.Equation (TESTED cs k) -> Property ()
testEquation ws lawName eq = Laws.withSides eq (testTested ws ws lawName)

-- | A named arbitrary arrow between the objects the endpoints stand for.
genArr
  :: forall cs k (x :: TESTED cs k) y. (Testable k, Tested x, Tested y) => Witnesses cs k -> String -> Property (x ~> y)
genArr ws s = untestTestOb2 @x @y ws (prim s <$> genNamed @(Untest x ~> Untest y) s)

-- | Compare two elements of @p@, printing their descriptions on failure.
testTested
  :: forall {csj} {csk} {j} {k} (p :: j +-> k) (x :: TESTED csk k) (y :: TESTED csj j)
   . (TestableProfunctor p)
  => Witnesses csj j -> Witnesses csk k -> String -> TestedP p x y -> TestedP p x y -> Property ()
testTested wsj wsk lawName (TestedP dl l) (TestedP dr r) =
  untestTestOb @x wsk $ untestTestOb @y @(Property ()) wsj $ testEq lawName (dl 0 "") l (dr 0 "") r

-- | Objects of @k@ built from leaves by the structures' object formers. Checking a law
-- interprets it here rather than in @k@ itself: an object's 'Ob' is then 'Tested', which
-- recovers the 'TestOb' of the object of @k@ it stands for ('Untest') from the 'Witnesses' for
-- the structures @cs@, supplied at run time.
--
-- The only constructor is the leaf. Compound objects use the free category's formers, which are
-- open data families of any kind ('M.**!', 'BinaryProduct.*!', 'SA.DualF', ...), so a new
-- structure brings its own former and its own 'Tested' instance.
type TESTED :: [Kind -> Constraint] -> Kind -> Kind
type data TESTED cs k = TLeaf k

-- * Witnesses

-- | How 'TestOb' is closed under the object formers of the structure @c@, for the category @k@.
-- Structures without formers of their own have a witness that holds nothing.
type Witness :: (Kind -> Constraint) -> Kind -> Type
data family Witness c k

data instance Witness CategoryOf k = CategoryW
newtype instance Witness M.Monoidal k = MonoidalW (WithTestOb2 k)
data instance Witness M.SymMonoidal k = SymMonoidalW
newtype instance Witness BinaryProduct.HasBinaryProducts k = ProductsW (WithTestObProd k)
newtype instance Witness BinaryCoproduct.HasBinaryCoproducts k = CoproductsW (WithTestObCoprod k)
data instance Witness Terminal.HasTerminalObject k = TerminalW
data instance Witness Initial.HasInitialObject k = InitialW
data instance Witness Distributive.Distributive k = DistributiveW
newtype instance Witness Exponential.Closed k = ClosedW (WithTestObExp k)
newtype instance Witness SA.StarAutonomous k = StarAutonomousW (WithTestObDual k)
data instance Witness CC.CompactClosed k = CompactClosedW
data instance Witness Strength.TracedMonoidal k = TracedW
data instance Witness CopyDiscard.CopyDiscard k = CopyDiscardW
data instance Witness (Monoid.Supplies Monoid.Monoid) k = MonoidSupplyW
data instance Witness (Monoid.Supplies Monoid.Comonoid) k = ComonoidSupplyW
data instance Witness (Monoid.Supplies Monoid.CommutativeMonoid) k = CommutativeMonoidSupplyW
data instance Witness (Monoid.Supplies Monoid.CocommutativeComonoid) k = CocommutativeComonoidSupplyW

infixr 5 :&

-- | One 'Witness' for each structure in @cs@, in the same order.
type Witnesses :: [Kind -> Constraint] -> Kind -> Type
data Witnesses cs k where
  WNil :: Witnesses '[] k
  (:&) :: Witness c k -> Witnesses cs k -> Witnesses (c ': cs) k

-- | Look up the witness for the structure @c@.
type HasWitness :: (Kind -> Constraint) -> [Kind -> Constraint] -> Constraint
class HasWitness c cs where
  -- | The witness for @c@ in the list.
  witness :: Witnesses cs k -> Witness c k

instance {-# OVERLAPPABLE #-} (HasWitness c cs) => HasWitness c (d ': cs) where
  witness (_ :& ws) = witness @c ws
instance HasWitness c (c ': cs) where
  witness (w :& _) = w

-- * The testable-objects category

-- | The objects of 'TESTED': those that stand for an object of @k@ ('Untest'), with how to
-- rebuild that object's 'Ob' and 'TestOb' from the ones of its parts. This is 'Ob' for 'TESTED'.
type Tested :: forall {cs} {k}. TESTED cs k -> Constraint
class Tested (a :: TESTED cs k) where
  -- | The object of @k@ that @a@ stands for. (The class variable is re-annotated so that @k@ is
  -- in scope in the result kind.)
  type Untest (a :: TESTED cs k) :: k

  -- | The 'Ob' of the object @a@ stands for, which the structures of @k@ provide.
  untestOb :: ((Ob (Untest a)) => r) -> r

  -- | The 'TestOb' of the object @a@ stands for, which the witnesses provide.
  untestTestOb :: Witnesses cs k -> ((TestOb (Untest a)) => r) -> r

instance (Testable k, TestOb (a :: k)) => Tested (TLeaf a :: TESTED cs k) where
  type Untest (TLeaf a) = a
  untestOb r = obFromTestOb @a r
  untestTestOb _ r = r
instance (Testable k, M.Monoidal k, TestOb (M.Unit :: k)) => Tested (M.UnitF :: TESTED cs k) where
  type Untest M.UnitF = M.Unit
  untestOb r = r
  untestTestOb _ r = r
instance (HasWitness M.Monoidal cs, M.Monoidal k, Tested (a :: TESTED cs k), Tested b) => Tested (a M.**! b) where
  type Untest (a M.**! b) = Untest a M.** Untest b
  untestOb r = untestOb2 @a @b (M.withOb2 @k @(Untest a) @(Untest b) r)
  untestTestOb ws r = untestTestOb2 @a @b ws (case witness @M.Monoidal ws of MonoidalW f -> f @(Untest a) @(Untest b) r)
instance
  (HasWitness BinaryProduct.HasBinaryProducts cs, BinaryProduct.HasBinaryProducts k, Tested (a :: TESTED cs k), Tested b)
  => Tested (a BinaryProduct.*! b)
  where
  type Untest (a BinaryProduct.*! b) = Untest a BinaryProduct.&& Untest b
  untestOb r = untestOb2 @a @b (BinaryProduct.withObProd @k @(Untest a) @(Untest b) r)
  untestTestOb ws r =
    untestTestOb2 @a @b ws (case witness @BinaryProduct.HasBinaryProducts ws of ProductsW f -> f @(Untest a) @(Untest b) r)
instance (Testable k, Initial.HasInitialObject k, TestOb (Initial.InitialObject :: k)) => Tested (Initial.InitF :: TESTED cs k) where
  type Untest Initial.InitF = Initial.InitialObject
  untestOb r = r
  untestTestOb _ r = r
instance
  ( HasWitness BinaryCoproduct.HasBinaryCoproducts cs
  , BinaryCoproduct.HasBinaryCoproducts k
  , Tested (a :: TESTED cs k)
  , Tested b
  )
  => Tested (a BinaryCoproduct.+ b)
  where
  type Untest (a BinaryCoproduct.+ b) = Untest a BinaryCoproduct.|| Untest b
  untestOb r = untestOb2 @a @b (BinaryCoproduct.withObCoprod @k @(Untest a) @(Untest b) r)
  untestTestOb ws r =
    untestTestOb2 @a @b
      ws
      (case witness @BinaryCoproduct.HasBinaryCoproducts ws of CoproductsW f -> f @(Untest a) @(Untest b) r)

instance
  (Testable k, Terminal.HasTerminalObject k, TestOb (Terminal.TerminalObject :: k))
  => Tested (Terminal.TermF :: TESTED cs k)
  where
  type Untest Terminal.TermF = Terminal.TerminalObject
  untestOb r = r
  untestTestOb _ r = r
instance
  (HasWitness Exponential.Closed cs, Exponential.Closed k, Tested (a :: TESTED cs k), Tested b)
  => Tested (a Exponential.--> b)
  where
  type Untest (a Exponential.--> b) = Untest a Exponential.~~> Untest b
  untestOb r = untestOb2 @a @b (Exponential.withObExp @k @(Untest a) @(Untest b) r)
  untestTestOb ws r =
    untestTestOb2 @a @b ws (case witness @Exponential.Closed ws of ClosedW f -> f @(Untest a) @(Untest b) r)

instance (HasWitness SA.StarAutonomous cs, SA.StarAutonomous k, Tested (a :: TESTED cs k)) => Tested (SA.DualF a) where
  type Untest (SA.DualF a) = SA.Dual (Untest a)
  untestOb r = untestOb @a (SA.withObDual @k @(Untest a) r)
  untestTestOb ws r = untestTestOb @a ws (case witness @SA.StarAutonomous ws of StarAutonomousW f -> f @(Untest a) r)

-- | 'untestOb' of two objects at once.
untestOb2 :: forall {cs} {k} (a :: TESTED cs k) b r. (Tested a, Tested b) => ((Ob (Untest a), Ob (Untest b)) => r) -> r
untestOb2 r = untestOb @a (untestOb @b r)

-- | 'untestTestOb' of two objects at once.
untestTestOb2
  :: forall {cs} {k} (a :: TESTED cs k) (b :: TESTED cs k) r
   . (Tested a, Tested b) => Witnesses cs k -> ((TestOb (Untest a), TestOb (Untest b)) => r) -> r
untestTestOb2 ws r = untestTestOb @a ws (untestTestOb @b ws r)

-- | 'untestOb' of three objects at once.
untestOb3
  :: forall {cs} {k} (a :: TESTED cs k) b c r
   . (Tested a, Tested b, Tested c) => ((Ob (Untest a), Ob (Untest b), Ob (Untest c)) => r) -> r
untestOb3 r = untestOb @a (untestOb @b (untestOb @c r))

-- | An arrow of @k@ between the objects the endpoints stand for, with a description of how it was
-- built: an element of the hom profunctor. These are the arrows of 'TESTED'.
type TestedArr :: forall cs k. CAT (TESTED cs k)
type TestedArr @cs @k = TestedP (Hom k)

-- | 'TestedP' at the hom profunctor.
pattern TestedArr
  :: forall cs k (a :: TESTED cs k) (b :: TESTED cs k)
   . () => (Tested a, Tested b) => Doc -> Untest a ~> Untest b -> TestedArr a b
pattern TestedArr d f = TestedP d f

{-# COMPLETE TestedArr #-}

-- | A description that can be shown at a precedence, like 'showsPrec'.
type Doc = Int -> ShowS

-- | A name, which never needs parentheses.
atom :: String -> Doc
atom s _ = showString s

-- | An element or arrow described by its name.
prim :: (Tested a, Tested b) => String -> p (Untest a) (Untest b) -> TestedP p a b
prim s = TestedP (atom s)

-- | A function applied to one argument.
app :: String -> Doc -> Doc
app f x = apps f [x]

-- | A function applied to several arguments.
apps :: String -> [Doc] -> Doc
apps f xs d = showParen (d > 10) (showString f . foldr (\x r -> showChar ' ' . x 11 . r) (\r -> r) xs)

-- | A left or right associative infix operator at the given precedence, like @infixl@ and
-- @infixr@. The operator string includes its surrounding spaces, e.g. @" . "@.
infixlDoc, infixrDoc :: Int -> String -> Doc -> Doc -> Doc
infixlDoc p op x y d = showParen (d > p) (x p . showString op . y (p + 1))
infixrDoc p op x y d = showParen (d > p) (x (p + 1) . showString op . y p)

instance (CategoryOf k) => CategoryOf (TESTED cs k) where
  type (~>) = TestedArr
  type Ob a = Tested a

instance
  ( HasWitness M.Monoidal csj
  , HasWitness M.Monoidal csk
  , Testable j
  , Testable k
  , M.MonoidalProfunctor p
  , TestOb (M.Unit :: j)
  , TestOb (M.Unit :: k)
  )
  => M.MonoidalProfunctor (TestedP p :: TESTED csj j +-> TESTED csk k)
  where
  one = prim "one" M.one
  TestedP df f ** TestedP dg g = TestedP (infixlDoc 8 " ** " df dg) (f M.** g)
instance (HasWitness M.Monoidal cs, Testable k, M.Monoidal k, TestOb (M.Unit :: k)) => M.Monoidal (TESTED cs k) where
  type Unit = M.UnitF
  type a ** b = a M.**! b
  withOb2 r = r
  leftUnitor @a = untestOb @a (prim "leftUnitor" M.leftUnitor)
  leftUnitorInv @a = untestOb @a (prim "leftUnitorInv" M.leftUnitorInv)
  rightUnitor @a = untestOb @a (prim "rightUnitor" M.rightUnitor)
  rightUnitorInv @a = untestOb @a (prim "rightUnitorInv" M.rightUnitorInv)
  associator @a @b @c = untestOb3 @a @b @c (prim "associator" (M.associator @k @(Untest a) @(Untest b) @(Untest c)))
  associatorInv @a @b @c = untestOb3 @a @b @c (prim "associatorInv" (M.associatorInv @k @(Untest a) @(Untest b) @(Untest c)))
instance (HasWitness M.Monoidal cs, Testable k, M.SymMonoidal k, TestOb (M.Unit :: k)) => M.SymMonoidal (TESTED cs k) where
  swap @a @b = untestOb2 @a @b (prim "swap" (M.swap @k @(Untest a) @(Untest b)))

instance
  (HasWitness BinaryProduct.HasBinaryProducts cs, BinaryProduct.HasBinaryProducts k)
  => BinaryProduct.HasBinaryProducts (TESTED cs k)
  where
  type a && b = a BinaryProduct.*! b
  withObProd r = r
  fst @a @b = untestOb2 @a @b (prim "fst" (BinaryProduct.fst @k @(Untest a) @(Untest b)))
  snd @a @b = untestOb2 @a @b (prim "snd" (BinaryProduct.snd @k @(Untest a) @(Untest b)))
  TestedArr df f &&& TestedArr dg g = TestedArr (infixlDoc 5 " &&& " df dg) (f BinaryProduct.&&& g)

instance (Testable k, Initial.HasInitialObject k, TestOb (Initial.InitialObject :: k)) => Initial.HasInitialObject (TESTED cs k) where
  type InitialObject = Initial.InitF
  initiate @a = untestOb @a (prim "initiate" Initial.initiate)

instance
  (HasWitness BinaryCoproduct.HasBinaryCoproducts cs, BinaryCoproduct.HasBinaryCoproducts k)
  => BinaryCoproduct.HasBinaryCoproducts (TESTED cs k)
  where
  type a || b = a BinaryCoproduct.+ b
  withObCoprod r = r
  lft @a @b = untestOb2 @a @b (prim "lft" (BinaryCoproduct.lft @k @(Untest a) @(Untest b)))
  rgt @a @b = untestOb2 @a @b (prim "rgt" (BinaryCoproduct.rgt @k @(Untest a) @(Untest b)))
  TestedArr df f ||| TestedArr dg g = TestedArr (infixlDoc 4 " ||| " df dg) (f BinaryCoproduct.||| g)

instance
  ( HasWitness M.Monoidal cs
  , HasWitness BinaryCoproduct.HasBinaryCoproducts cs
  , Testable k
  , Distributive.Distributive k
  , TestOb (M.Unit :: k)
  , TestOb (Initial.InitialObject :: k)
  )
  => Distributive.Distributive (TESTED cs k)
  where
  distL @a @b @c = untestOb3 @a @b @c (prim "distL" (Distributive.distL @k @(Untest a) @(Untest b) @(Untest c)))
  distR @a @b @c = untestOb3 @a @b @c (prim "distR" (Distributive.distR @k @(Untest a) @(Untest b) @(Untest c)))
  absorbL @a = untestOb @a (prim "absorbL" (Distributive.absorbL @k @(Untest a)))
  absorbR @a = untestOb @a (prim "absorbR" (Distributive.absorbR @k @(Untest a)))

instance
  (Testable k, Terminal.HasTerminalObject k, TestOb (Terminal.TerminalObject :: k))
  => Terminal.HasTerminalObject (TESTED cs k)
  where
  type TerminalObject = Terminal.TermF
  terminate @a = untestOb @a (prim "terminate" Terminal.terminate)

instance
  (HasWitness M.Monoidal cs, HasWitness Exponential.Closed cs, Testable k, Exponential.Closed k, TestOb (M.Unit :: k))
  => Exponential.Closed (TESTED cs k)
  where
  type a ~~> b = a Exponential.--> b
  withObExp r = r
  curry @a @b (TestedArr df f) = untestOb2 @a @b (TestedArr (app "curry" df) (Exponential.curry @k @(Untest a) @(Untest b) f))
  apply @a @b = untestOb2 @a @b (prim "apply" (Exponential.apply @k @(Untest a) @(Untest b)))

  -- '^^^' is infixl 9 and '.' infixr 9, so '^^^' is parenthesized under either side of '.'.
  TestedArr df f ^^^ TestedArr dg g =
    TestedArr (\d -> showParen (d >= 9) (df 9 . showString " ^^^ " . dg 10)) (f Exponential.^^^ g)

instance
  ( HasWitness M.Monoidal cs
  , HasWitness Exponential.Closed cs
  , HasWitness SA.StarAutonomous cs
  , Testable k
  , SA.StarAutonomous k
  , TestOb (M.Unit :: k)
  )
  => SA.StarAutonomous (TESTED cs k)
  where
  type Dual a = SA.DualF a
  withObDual r = r
  dual (TestedArr df f) = TestedArr (app "dual" df) (SA.dual f)
  dualInv @a @b (TestedArr df f) = untestOb2 @a @b (TestedArr (app "dualInv" df) (SA.dualInv @k @(Untest a) @(Untest b) f))
  linDist @a @b @c (TestedArr df f) =
    untestOb3 @a @b @c (TestedArr (app "linDist" df) (SA.linDist @k @(Untest a) @(Untest b) @(Untest c) f))
  linDistInv @a @b @c (TestedArr df f) =
    untestOb3 @a @b @c (TestedArr (app "linDistInv" df) (SA.linDistInv @k @(Untest a) @(Untest b) @(Untest c) f))
  doubleNeg @a = untestOb @a (prim "doubleNeg" (SA.doubleNeg @k @(Untest a)))
  doubleNegInv @a = untestOb @a (prim "doubleNegInv" (SA.doubleNegInv @k @(Untest a)))

instance
  ( HasWitness M.Monoidal cs
  , HasWitness Exponential.Closed cs
  , HasWitness SA.StarAutonomous cs
  , Testable k
  , CC.CompactClosed k
  , TestOb (M.Unit :: k)
  )
  => CC.CompactClosed (TESTED cs k)
  where
  distribDual @a @b = untestOb2 @a @b (prim "distribDual" (CC.distribDual @k @(Untest a) @(Untest b)))
  dualUnit = prim "dualUnit" CC.dualUnit
  dualityUnit @a = untestOb @a (prim "dualityUnit" (CC.dualityUnit @k @(Untest a)))
  dualityCounit @a = untestOb @a (prim "dualityCounit" (CC.dualityCounit @k @(Untest a)))

-- | Every object is a monoid when the category supplies them, with the monoid of the object it
-- stands for.
instance
  (HasWitness M.Monoidal cs, Testable k, M.Monoidal k, Monoid.Supplies Monoid.Monoid k, TestOb (M.Unit :: k), Tested a)
  => Monoid.Monoid (a :: TESTED cs k)
  where
  mempty = untestOb @a (prim "mempty" (Monoid.mempty @(Untest a)))
  mappend = untestOb @a (prim "mappend" (Monoid.mappend @(Untest a)))

-- | Every object is a comonoid when the category supplies them.
instance
  (HasWitness M.Monoidal cs, Testable k, M.Monoidal k, Monoid.Supplies Monoid.Comonoid k, TestOb (M.Unit :: k), Tested a)
  => Monoid.Comonoid (a :: TESTED cs k)
  where
  counit = untestOb @a (prim "counit" (Monoid.counit @(Untest a)))
  comult = untestOb @a (prim "comult" (Monoid.comult @(Untest a)))

-- | The monoids of a category that supplies commutative ones are commutative.
instance
  ( HasWitness M.Monoidal cs
  , Testable k
  , M.SymMonoidal k
  , Monoid.Supplies Monoid.CommutativeMonoid k
  , TestOb (M.Unit :: k)
  , Tested a
  )
  => Monoid.CommutativeMonoid (a :: TESTED cs k)

-- | The comonoids of a category that supplies cocommutative ones are cocommutative.
instance
  ( HasWitness M.Monoidal cs
  , Testable k
  , M.SymMonoidal k
  , Monoid.Supplies Monoid.CocommutativeComonoid k
  , TestOb (M.Unit :: k)
  , Tested a
  )
  => Monoid.CocommutativeComonoid (a :: TESTED cs k)

-- | 'Strength.act' of the tensor of @p@.
instance
  (HasWitness M.Monoidal cs, Testable k, M.Monoidal k, Strength.Strong M.Tensor p, TestOb (M.Unit :: k))
  => Strength.Strong M.Tensor (TestedP p :: CAT (TESTED cs k))
  where
  act @a (TestedP dx x) = untestOb @a (TestedP (app "act" dx) (Strength.act @M.Tensor @p @(Untest a) x))

-- | 'Strength.coact' over the tensor of @p@, e.g. the trace of the category the objects stand for.
instance
  (HasWitness M.Monoidal cs, Testable k, M.Monoidal k, Strength.Costrong M.Tensor p, TestOb (M.Unit :: k))
  => Strength.Costrong M.Tensor (TestedP p :: CAT (TESTED cs k))
  where
  coact @a @x @y (TestedP df f) =
    untestOb3 @a @x @y (TestedP (app "coact" df) (Strength.coact @M.Tensor @p @(Untest a) @(Untest x) @(Untest y) f))

-- | Copying and discarding in the category the objects stand for.
instance
  (HasWitness M.Monoidal cs, Testable k, CopyDiscard.CopyDiscard k, TestOb (M.Unit :: k))
  => CopyDiscard.CopyDiscard (TESTED cs k)
  where
  copy @a = untestOb @a (prim "copy" (CopyDiscard.copy @k @(Untest a)))
  discard @a = untestOb @a (prim "discard" (CopyDiscard.discard @k @(Untest a)))

instance (CategoryOf k) => Laws.Labelled (TESTED cs k) where
  label s (TestedArr _ f) = prim s f

-- * The testable-objects profunctor

-- | An element of @p@ between the objects the endpoints stand for, with a description of how it
-- was built, for printing a failing law.
type TestedP :: forall {csj} {csk} {j} {k}. (j +-> k) -> TESTED csj j +-> TESTED csk k
data TestedP p a b where
  TestedP :: (Tested a, Tested b) => Doc -> p (Untest a) (Untest b) -> TestedP p a b

instance (Profunctor p) => Profunctor (TestedP p :: TESTED csj j +-> TESTED csk k) where
  dimap (TestedArr df f) (TestedArr dg g) (TestedP dx x) = TestedP (apps "dimap" [df, dg, dx]) (dimap f g x)
  lmap (TestedArr df f) (TestedP dx x) = TestedP (apps "lmap" [df, dx]) (lmap f x)
  rmap (TestedArr dg g) (TestedP dx x) = TestedP (apps "rmap" [dg, dx]) (rmap g x)
  r \\ TestedP{} = r

instance (Promonad p) => Promonad (TestedP p :: CAT (TESTED cs k)) where
  id @a = untestOb @a (prim "id" id)
  TestedP dy y . TestedP dx x = TestedP (infixrDoc 9 " . " dy dx) (y . x)

-- | The object @p '%' b@, for the interpretation of a 'Representable' @p@.
type RepF :: forall {csj} {j} {k} {o}. (j +-> k) -> TESTED csj j -> o
data family RepF p b

-- | The structure of being closed under the representing functor of @p@, whose objects in 'TESTED'
-- are formed by 'RepF'. Its witness needs the witnesses of the domain @j@ of @p@, @csj@.
type RepresentedBy :: forall {j} {k}. [Kind -> Constraint] -> (j +-> k) -> Kind -> Constraint
class RepresentedBy csj p k'

instance RepresentedBy csj p k'

data instance Witness (RepresentedBy csj (p :: j +-> k)) k' = RepresentedW (Witnesses csj j) (WithTestObRep j p)

instance
  (HasWitness (RepresentedBy csj p) csk, Representable p, Tested (b :: TESTED csj j))
  => Tested (RepF (p :: j +-> k) b :: TESTED csk k)
  where
  type Untest (RepF p b) = p % Untest b
  untestOb r = untestOb @b (withObRep @p @(Untest b) r)
  untestTestOb ws r = case witness @(RepresentedBy csj p) ws of
    RepresentedW wsj f -> untestTestOb @b wsj (f @(Untest b) r)

instance
  (HasWitness (RepresentedBy csj p) csk, Representable p)
  => Representable (TestedP p :: TESTED csj j +-> TESTED csk k)
  where
  type TestedP p % b = RepF p b
  index (TestedP dx x) = TestedArr (app "index" dx) (index x)
  tabulate @b (TestedArr df f) = untestOb @b (TestedP (app "tabulate" df) (tabulate @p @(Untest b) f))
  repMap (TestedArr df f) = TestedArr (app "repMap" df) (repMap @p f)
  repUniv @b = untestOb @b (prim "repUniv" (repUniv @p @(Untest b)))

-- | The object @p '%%' a@, for the interpretation of a 'Corepresentable' @p@.
type CorepF :: forall {csk} {j} {k} {o}. (j +-> k) -> TESTED csk k -> o
data family CorepF p a

-- | The structure of being closed under the corepresenting functor of @p@, whose objects in
-- 'TESTED' are formed by 'CorepF'. Its witness needs the witnesses of the codomain @k@ of @p@,
-- @csk@.
type CorepresentedBy :: forall {j} {k}. [Kind -> Constraint] -> (j +-> k) -> Kind -> Constraint
class CorepresentedBy csk p j'

instance CorepresentedBy csk p j'

data instance Witness (CorepresentedBy csk (p :: j +-> k)) j' = CorepresentedW (Witnesses csk k) (WithTestObCorep k p)

instance
  (HasWitness (CorepresentedBy csk p) csj, Corepresentable p, Tested (a :: TESTED csk k))
  => Tested (CorepF (p :: j +-> k) a :: TESTED csj j)
  where
  type Untest (CorepF p a) = p %% Untest a
  untestOb r = untestOb @a (withObCorep @p @(Untest a) r)
  untestTestOb ws r = case witness @(CorepresentedBy csk p) ws of
    CorepresentedW wsk f -> untestTestOb @a wsk (f @(Untest a) r)

instance
  (HasWitness (CorepresentedBy csk p) csj, Corepresentable p)
  => Corepresentable (TestedP p :: TESTED csj j +-> TESTED csk k)
  where
  type TestedP p %% a = CorepF p a
  coindex (TestedP dx x) = TestedArr (app "coindex" dx) (coindex x)
  cotabulate @a (TestedArr df f) = untestOb @a (TestedP (app "cotabulate" df) (cotabulate @p @(Untest a) f))
  corepMap (TestedArr df f) = TestedArr (app "corepMap" df) (corepMap @p f)
  corepUniv @a = untestOb @a (prim "corepUniv" (corepUniv @p @(Untest a)))

instance (DaggerProfunctor p) => DaggerProfunctor (TestedP p :: CAT (TESTED cs k)) where
  dagger (TestedP d x) = TestedP (app "dagger" d) (dagger x)

instance (Finitary p) => Finitary (TestedP p :: TESTED csj j +-> TESTED csk k) where
  size @a @b = untestOb2 @a @b (size @p @(Untest a) @(Untest b))
  toIndex @a @b (TestedP _ x) = untestOb2 @a @b (toIndex @p @(Untest a) @(Untest b) x)
  fromIndex @a @b i = untestOb2 @a @b (TestedP (app "fromIndex" (\_ -> shows i)) (fromIndex @p @(Untest a) @(Untest b) i))

-- | The adjunction of the profunctors the objects stand for. The middle object of the 'Adj.unit'
-- is only known to be an object, so it becomes a leaf, which needs 'TestOb' to follow from 'Ob'.
instance
  (Adj.Proadjunction p q, Testable j, Testable k, TestObIsOb j, TestObIsOb k)
  => Adj.Proadjunction (TestedP p :: TESTED csj j +-> TESTED csk k) (TestedP q :: TESTED csk k +-> TESTED csj j)
  where
  unit @a = untestOb @a case Adj.unit @p @q @(Untest a) of
    (:.:) @m l r -> (:.:) @(TLeaf m :: TESTED csk k) (TestedP (atom "unitQ") l) (TestedP (atom "unitP") r) \\ l
  counit (TestedP dp x :.: TestedP dq y) = TestedArr (app "counit" (infixlDoc 9 " :.: " dp dq)) (Adj.counit (x :.: y))

-- | The procomonad the objects stand for. The middle object of 'Promonad.produplicate' is only
-- known to be an object, so it becomes a leaf, which needs 'TestOb' to follow from 'Ob'.
instance (Promonad.Procomonad p, Testable k, TestObIsOb k) => Promonad.Procomonad (TestedP p :: CAT (TESTED cs k)) where
  proextract (TestedP d x) = TestedArr (app "proextract" d) (Promonad.proextract x)
  produplicate (TestedP d x) = case Promonad.produplicate x of
    (:.:) @m l r -> (:.:) @(TLeaf m :: TESTED cs k) (TestedP (app "produplicate1" d) l) (TestedP (app "produplicate2" d) r) \\ l