packages feed

gongju-0.1.0.0: test/Main.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RequiredTypeArguments #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

-- | Test Suite for Gongju: Grand Unified Open-World Tape Engine and Permutation Library
--
-- Validates:
-- * Peano natural transformations and bidirectional fundep inference.
-- * Variadic composition and open-world tape execution via visible-forall (.@).
-- * Positional combinators (slipr, slipl, twist) and arbitrary permutation (permute).
-- * Indexed monad and comonad transitions (IxFunctor, IxPointed, IxApplicative, IxMonad).
-- * Canonical comonads, distributive functors, and representable commute laws.
-- * Canonical adjunctions with dual annihilation (zapLR, zapRL, unit, counit).
-- * Canonical Kan extensions (Ran, Lan), Yoneda fusion, and Codensity CPS optimizations.
-- * Canonical profunctors with contravariant/covariant slot mappings.
module Main (main) where

import Test.Hspec
import Data.Functor.Identity (Identity (..))
import Gongju.Core
import CoreInspection

-- | Minimal Comonad fixture for verifying cokleisli extension via 'W'.
-- Implements canonical 'Control.Comonad.Comonad'.
newtype IdW a = IdW a
  deriving (Eq, Show)

instance Functor IdW where
  fmap f (IdW a) = IdW (f a)

instance Comonad IdW where
  extract (IdW a) = a
  duplicate (IdW a) = IdW (IdW a)

-- | Minimal Distributive and Representable Functor fixture (Reader functor).
-- Validates effect commutation outside a comonad via 'Dist'.
newtype ReaderR r a = ReaderR { runReaderR :: r -> a }

instance Functor (ReaderR r) where
  fmap f (ReaderR g) = ReaderR (f . g)

instance Distributive (ReaderR r) where
  distribute f = ReaderR \r -> fmap (\(ReaderR g) -> g r) f

instance Representable (ReaderR r) where
  type Rep (ReaderR r) = r
  tabulate = ReaderR
  index (ReaderR f) = f

-- | Minimal Indexed State Monad fixture.
-- Implements canonical 'Data.Functor.Indexed' and 'Control.Monad.Indexed'.
newtype IxState i j a = IxState { runIxState :: i -> (a, j) }

instance IxFunctor IxState where
  imap f (IxState g) = IxState \i ->
    let (a, j) = g i in (f a, j)

instance IxPointed IxState where
  ireturn a = IxState (a,)

instance IxApplicative IxState where
  iap (IxState gf) (IxState ga) = IxState \i ->
    let (f, j) = gf i
        (a, l) = ga j
    in (f a, l)

instance IxMonad IxState where
  ibind k (IxState g) = IxState \i ->
    let (a, j) = g i
    in runIxState (k a) j

main :: IO ()
main = hspec do
  -- Tested against Note [Peano TypeLits Bridge] [ref:note-peano-typelits-bridge]
  describe "Gongju.Peano Naturals & List Type Families" do
    it "converts numeric TypeLits to Peano and verifies term equality" do
      show (Z :: Peano) `shouldBe` "Z"
      show (S Z :: Peano) `shouldBe` "S Z"

  -- Tested against Note [Variadic Composition Bidirectional Fundeps] [ref:note-variadic-composition-fundeps]
  describe "Gongju.Composition Variadic Composition & Tape Engine" do
    it "compN @1 behaves identically to standard function composition (.)" do
      let f = (+ 1) :: Int -> Int
          g = (* 2) :: Int -> Int
      compN @1 f g 5 `shouldBe` f (g 5)

    it "compN @2 composes over a 2-argument curried function" do
      let f = show :: Int -> String
          add2 x y = (x + y :: Int)
      compN @2 f add2 10 20 `shouldBe` "30"

    it "compN @3 composes over a 3-argument curried function" do
      let f = (* 2) :: Int -> Int
          add3 x y z = (x + y + z :: Int)
      compN @3 f add3 1 2 3 `shouldBe` 12

    it "fmapN @2 maps through a nested functor layer" do
      let nested = Just [1, 2, 3 :: Int]
      fmapN @2 (+ 10) nested `shouldBe` Just [11, 12, 13]

    it "executes tape-driven composition via infix (.@) on pure arguments" do
      let add3 x y z = (x + y + z :: Int)
          h = ((+ 100) .@ [P, P, P]) add3
      h 10 20 30 `shouldBe` 160

    it "executes tape-driven composition with explicit argument indices" do
      let tuple3 x y z = (x, y, z) :: (Int, String, Bool)
          p = (id .@ [2 # P, 0 # P, 1 # P]) tuple3
      p True (42 :: Int) "gongju" `shouldBe` (42, "gongju", True)

    it "executes tape-driven monadic binding" do
      let mAction :: Int -> Int -> [Int]
          mAction x y = [x + y, x * y]
          mComp = (fmap show .@ [P, M]) mAction
      mComp 3 [10, 20 :: Int] `shouldBe` ["[13,30]", "[23,60]"]

  -- Tested against Note [Positional Curried Arrow Manipulation] [ref:note-positional-arrow-manipulation]
  describe "Gongju.Positional Combinators" do
    it "slipr @1 pulls the 1st argument (0-indexed) to position 0" do
      let sub x y = (x - y :: Int)
      slipr @1 sub 5 20 `shouldBe` 15

    it "slipr @2 pulls the 2nd argument (0-indexed) to position 0" do
      let f x y z = (x, y, z) :: (Int, String, Bool)
      slipr @2 f True 42 "hello" `shouldBe` (42, "hello", True)

    it "slipl @1 pushes the 0th argument to position 1" do
      let f x y = (x - y :: Int)
      slipl @1 f 5 20 `shouldBe` 15

    it "twist @0 swaps adjacent arguments at position 0 and 1" do
      let sub x y = (x - y :: Int)
      twist @0 sub 5 20 `shouldBe` 15

  -- Tested against Note [Iterated Yank Index Adjustment] [ref:note-iterated-yank-adjustment]
  describe "Gongju.Permute Arbitrary Argument Permutation" do
    it "permute @'[2, 0, 1] permutes a 3-argument function" do
      let f x y z = (x, y, z) :: (Int, String, Bool)
      permute @'[2, 0, 1] f True 10 "gongju" `shouldBe` (10, "gongju", True)

    it "permutes functions with identical argument types without ambiguity" do
      let f x y z = (x - y + z :: Int)
      permute @'[1, 2, 0] f 20 30 10 `shouldBe` (10 - 20 + 30)

  describe "Gongju.Indexed Transitions" do
    it "composes transitions over indexed state monad" do
      let step1 :: Int -> Int -> String
          step1 x y = show (x + y)
          comp = (imap (++ "!") .@ [P, IxM]) step1
          res = comp 10 (IxState \s -> (5 :: Int, s + 1))
      runIxState res (0 :: Int) `shouldBe` ("15!", 1 :: Int)

  describe "Gongju.Comonad & Distributive Functors" do
    it "extends cokleisli functions using W tag" do
      let wAction :: IdW Int -> Int -> String
          wAction (IdW x) y = show (x + y)
          wComp = (fmap (++ "!") .@ [W, P]) wAction
      wComp (IdW 5) (10 :: Int) `shouldBe` IdW "15!"

    it "commutes a Representable effect outside a Comonad via PullRet '[Dist]" do
      let g :: IdW Int -> Int -> ReaderR String Int
          g (IdW baseVal) multiplier = ReaderR \env -> (baseVal + multiplier) * length env
          comp = withOps [W, P, PullDist] id g
          readerRes = comp (IdW 10) (5 :: Int)
      runReaderR readerRes "abc" `shouldBe` IdW 45

  -- Tested against Data.Functor.Adjunction canonical instances
  describe "Gongju.Adjunction Dual Annihilation & Adjoints" do
    -- Dual pairing annihilation: zapLR pairs left adjoint (,) with right adjoint (->)
    it "performs dual annihilation via zapLR on ((,) e) and ((->) e)" do
      let pair = (10 :: Int, length :: String -> Int)
          f e = replicate e 'x'
      zapLR pair f `shouldBe` 10

    -- Dual pairing annihilation: zapRL pairs right adjoint (->) with left adjoint (,)
    it "performs dual annihilation via zapRL on ((->) e) and ((,) e)" do
      let pair = (10 :: Int, "hello")
          f :: Int -> String -> Int
          f e str = length str + e
      zapRL f pair `shouldBe` 15

    -- Adjunction counit: collapses pair of environment and accessor (e, e -> a) to a
    it "applies adjunction counit via withOps [AdjC]" do
      let counitTarget = (5 :: Int, (+ 10) :: Int -> Int)
          comp = withOps [AdjC] id counitTarget
      comp `shouldBe` 15

  -- Tested against kan-extensions canonical Ran, Lan, Yoneda, Coyoneda, Codensity
  describe "Gongju.Kan Right/Left Kan Extensions, Yoneda & Codensity" do
    -- Universal counit of Right Kan extension collapses Ran g h (g a) to h a
    it "counit collapses Ran to underlying functor via ranCounit" do
      let ranVal = ranUnit (Identity (42 :: Int))
      runIdentity (ranCounit ranVal) `shouldBe` 42

    -- Universal unit of Left Kan extension embeds h a into Lan g h (g a)
    it "unit embeds functor into Lan via lanUnit" do
      let lanVal = lanUnit (Identity (99 :: Int)) :: Lan Identity Identity (Identity Int)
      runIdentity (lanCounit lanVal) `shouldBe` 99

    -- Coyoneda allows fmap fusion without requiring Functor constraints until lowering
    it "fuses mapping via Coyoneda lifting and lowering" do
      let co = liftCoyoneda [1, 2, 3 :: Int]
          coMapped = fmap (+ 10) co
      lowerCoyoneda coMapped `shouldBe` [11, 12, 13]

    -- Yoneda provides O(1) CPS composition for arbitrary covariant functors
    it "fuses mapping via Yoneda lifting and lowering" do
      let yon = liftYoneda [1, 2, 3 :: Int]
          yonMapped = fmap (* 2) yon
      lowerYoneda yonMapped `shouldBe` [2, 4, 6]

    -- Codensity optimizes right-associated monadic bind chains into O(1) continuations
    it "optimizes monad bind CPS via Codensity" do
      let cod = liftCodensity (Just (100 :: Int))
          codBound = cod >>= \x -> pure (x + 23)
      lowerCodensity codBound `shouldBe` Just 123

  -- Tested against profunctors canonical Profunctor and dimap
  describe "Gongju.Profunctor Contravariant/Covariant Mapping" do
    -- Contravariant pre-processing of input arguments via P1 (lmap)
    it "maps profunctor first slot contravariantly via mapPath @'[P1]" do
      let f = (+ 10) :: Int -> Int
          g = mapPath @'[P1] (* 2) f
      g 5 `shouldBe` 20

    -- Covariant post-processing of return values via P2 (rmap)
    it "maps profunctor second slot covariantly via mapPath @'[P2]" do
      let f = (+ 10) :: Int -> Int
          g = mapPath @'[P2] (* 2) f
      g 5 `shouldBe` 30

    -- Simultaneous bidirectional transformation via dimap
    it "dimaps both profunctor slots simultaneously via dimap" do
      let f = (+ 10) :: Int -> Int
          g = dimap (* 2) show f
      g 5 `shouldBe` "20"

  -- Tested against Note [Contravariant Positional Pre-Composition] [ref:note-contravariant-precomposition]
  describe "Gongju.Contravariant Positional Pre-Composition & 2D Splicing" do
    it "precomposeAt @0 and (-.) pre-compose argument 0" do
      let f x y = (x - y :: Int)
          g = precomposeAt @0 (* 2) f
          h = (* 2) -. f
      g 10 3 `shouldBe` 17
      h 10 3 `shouldBe` 17

    it "precomposeAt @1 and (-.*) pre-compose argument 1" do
      let f x y z = (x + y * z :: Int)
          g = precomposeAt @1 (+ 10) f
          h = (+ 10) -.* f
      g 5 2 3 `shouldBe` 41
      h 5 2 3 `shouldBe` 41

    it "precomposeAt @2 and (-.**) pre-compose argument 2" do
      let tuple3 x y z = (x, y, z) :: (Int, String, Bool)
          g = precomposeAt @2 not tuple3
          h = not -.** tuple3
      g 10 "abc" False `shouldBe` (10, "abc", True)
      h 10 "abc" False `shouldBe` (10, "abc", True)

    it "precomposeAt @3 and (-.***) pre-compose argument 3" do
      let tuple4 w x y z = (w, x, y, z) :: (Int, Int, Int, Int)
          g = precomposeAt @3 (* 100) tuple4
          h = (* 100) -.*** tuple4
      g 1 2 3 4 `shouldBe` (1, 2, 3, 400)
      h 1 2 3 4 `shouldBe` (1, 2, 3, 400)

    it "spliceAt @1 @2 splices a binary function into slot 1 of a ternary function" do
      let add3 x y z = (x + y + z :: Int)
          mult2 u v = (u * v :: Int)
          spliced = spliceAt @1 @2 mult2 add3
      spliced 10 3 4 5 `shouldBe` 27

    it "spliceAt @0 @3 splices a ternary function into slot 0" do
      let add2 a b = (a + b :: Int)
          prod3 x y z = (x * y * z :: Int)
          spliced = spliceAt @0 @3 prod3 add2
      spliced 2 3 4 10 `shouldBe` 34

  -- Tested against Note [Contravariant Functor Alternating Parity] [ref:note-contravariant-alternating-parity]
  describe "Gongju.Contravariant Alternating Parity Stacks" do
    it "contramapN @1 and (<-$>) invert variance on 1-layer Predicate" do
      let isPositive = Predicate (> (0 :: Int))
          p1 = contramapN @1 (* 2) isPositive
          p2 = (* 2) <-$> isPositive
      getPredicate p1 5 `shouldBe` True
      getPredicate p1 (-1) `shouldBe` False
      getPredicate p2 5 `shouldBe` True
      getPredicate p2 (-1) `shouldBe` False

    it "contramapN @2 and (<-$$>) cancel variance inversion on 2-layer Predicate (covariant)" do
      let pp = Predicate (\(Predicate p) -> p (10 :: Int)) :: Predicate (Predicate Int)
          pp1 = contramapN @2 (* 2) pp
          pp2 = (* 2) <-$$> pp
      getPredicate pp1 (Predicate (> 15)) `shouldBe` True
      getPredicate pp1 (Predicate (> 25)) `shouldBe` False
      getPredicate pp2 (Predicate (> 15)) `shouldBe` True
      getPredicate pp2 (Predicate (> 25)) `shouldBe` False

    it "contramapN @3 and (<-$$$>) invert variance on 3-layer Predicate (contravariant)" do
      let ppp = Predicate (\(Predicate pp) -> pp (Predicate (> (15 :: Int)))) :: Predicate (Predicate (Predicate Int))
          ppp1 = contramapN @3 (* 2) ppp
          ppp2 = (* 2) <-$$$> ppp
          evalTest val = Predicate (\(Predicate p) -> p (val :: Int))
      getPredicate ppp1 (evalTest 10) `shouldBe` True
      getPredicate ppp1 (evalTest 5) `shouldBe` False
      getPredicate ppp2 (evalTest 10) `shouldBe` True
      getPredicate ppp2 (evalTest 5) `shouldBe` False

    it "supports Divisible divide combining two predicates" do
      let pEven = Predicate even :: Predicate Int
          pPositive = Predicate (> 0) :: Predicate Int
          pCombined = divide (\x -> (x, x)) pEven pPositive
      getPredicate pCombined 4 `shouldBe` True
      getPredicate pCombined 3 `shouldBe` False
      getPredicate pCombined (-2) `shouldBe` False

  -- Tested against Note [Positional Monadic Curried Binding] [ref:note-positional-monadic-binding]
  describe "Gongju.Monad Positional Monadic Binding" do
    it "bindAt @1 and (==<<), (>>==) bind trailing argument slot 1" do
      let f x y = pure (x * y) :: Maybe Int
      bindAt @1 f (Just 7) 6 `shouldBe` Just 42
      (f ==<< Just 7) 6 `shouldBe` Just 42
      (Just 7 >>== f) 6 `shouldBe` Just 42
      (f ==<< Nothing) 6 `shouldBe` Nothing

    it "bindAt @2 and (===<<), (>>===) bind trailing argument slot 2" do
      let add3 x y z = pure (x + y + z) :: Maybe Int
      bindAt @2 add3 (Just 10) 1 2 `shouldBe` Just 13
      (add3 ===<< Just 10) 1 2 `shouldBe` Just 13
      (Just 10 >>=== add3) 1 2 `shouldBe` Just 13

    it "bindAt @3 and (====<<), (>>====) bind trailing argument slot 3" do
      let add4 w x y z = pure (w + x + y + z) :: Maybe Int
      bindAt @3 add4 (Just 100) 1 2 3 `shouldBe` Just 106
      (add4 ====<< Just 100) 1 2 3 `shouldBe` Just 106
      (Just 100 >>==== add4) 1 2 3 `shouldBe` Just 106

    it "bindPos @1 @1 and (=.=<<), (>>=.=) bind interior slot 1 of 3" do
      let add3 x y z = pure (x + y + z) :: Maybe Int
      bindPos @1 @1 add3 (Just 20) 1 3 `shouldBe` Just 24
      (add3 =.=<< Just 20) 1 3 `shouldBe` Just 24
      (Just 20 >>=.= add3) 1 3 `shouldBe` Just 24
      (add3 =.=<< Nothing) 1 3 `shouldBe` Nothing

    it "bindPos @0 @2 and (==.<<), (=..<<), (>>.==), (>>..=) bind interior slot 0 of 3" do
      let add3 x y z = pure (x + y + z) :: Maybe Int
      bindPos @0 @2 add3 (Just 10) 2 3 `shouldBe` Just 15
      (add3 ==.<< Just 10) 2 3 `shouldBe` Just 15
      (add3 =..<< Just 10) 2 3 `shouldBe` Just 15
      (Just 10 >>.== add3) 2 3 `shouldBe` Just 15
      (Just 10 >>..= add3) 2 3 `shouldBe` Just 15
      (add3 ==.<< Nothing) 2 3 `shouldBe` Nothing

  -- Tested against Note [Multi-Slot Monadic Lifting Masks] [ref:note-multislot-monadic-masks]
  describe "Gongju.Monad Multi-Slot Lifting Masks" do
    it "liftMask @'[MArg, PArg, MArg] sequences multiple monadic slots while preserving pure arguments" do
      let f a b c = pure (a ++ ":" ++ show b ++ ":" ++ c) :: [String]
          g = liftMask @'[MArg, PArg, MArg] f
      g ["a", "b"] (42 :: Int) ["x", "y"] `shouldBe` ["a:42:x", "a:42:y", "b:42:x", "b:42:y"]

    it "liftMask @'[PArg, MArg] preserves pure arg 0 and binds monadic arg 1" do
      let g x y = if y > 0 then Just (x * y) else Nothing :: Maybe Int
          h = liftMask @'[PArg, MArg] g
      h (5 :: Int) (Just 3) `shouldBe` Just 15
      h (5 :: Int) Nothing `shouldBe` Nothing

    it "liftMask @'[MArg, MArg] lifts both arguments into a monad" do
      let g x y = if y > 0 then Just (x * y) else Nothing :: Maybe Int
          h = liftMask @'[MArg, MArg] g
      h (Just 5) (Just 3) `shouldBe` Just 15
      h Nothing (Just 3) `shouldBe` Nothing
      h (Just 5) Nothing `shouldBe` Nothing

  describe "Gongju.Monad Arity-Indexed Kleisli Composition & Pure Lifting" do
    it "liftPureN lifts pure functions to return monadic results" do
      liftPureN @1 (+ (1 :: Int)) 5 `shouldBe` (Just 6 :: Maybe Int)
      liftPureN @2 (+) (3 :: Int) 4 `shouldBe` (Just 7 :: Maybe Int)
      liftPureN @3 (\x y z -> (x + y + z :: Int)) 1 2 3 `shouldBe` (Just 6 :: Maybe Int)

    it "bindKleisliN @2 and (<=<.), (.>=>) post-compose Kleisli arrows after binary functions" do
      let f x y = Just (x + y :: Int)
          checkEven z = if even z then Just z else Nothing
      bindKleisliN @2 checkEven f 2 4 `shouldBe` Just 6
      bindKleisliN @2 checkEven f 2 3 `shouldBe` Nothing
      (checkEven <=<. f) 2 4 `shouldBe` Just 6
      (checkEven <=<. f) 2 3 `shouldBe` Nothing
      (f .>=> checkEven) 2 4 `shouldBe` Just 6
      (f .>=> checkEven) 2 3 `shouldBe` Nothing

    it "bindKleisliN @3 and (<=<..), (..>=>) post-compose Kleisli arrows after ternary functions" do
      let add3M x y z = Just (x + y + z :: Int)
          checkEven z = if even z then Just z else Nothing
      bindKleisliN @3 checkEven add3M 1 2 3 `shouldBe` Just 6
      bindKleisliN @3 checkEven add3M 1 2 4 `shouldBe` Nothing
      (checkEven <=<.. add3M) 1 2 3 `shouldBe` Just 6
      (checkEven <=<.. add3M) 1 2 4 `shouldBe` Nothing
      (add3M ..>=> checkEven) 1 2 3 `shouldBe` Just 6
      (add3M ..>=> checkEven) 1 2 4 `shouldBe` Nothing

  -- Tested against Note [Profunctor Effect Handler Permutations] [ref:note-profunctor-handler-permutations]
  describe "Gongju.Profunctor Curried Category & Handler Permutations" do
    it "catFlip and catApply work over CurriedCat (->)" do
      let f x y = (x - y :: Int)
      catFlip f 5 20 `shouldBe` 15
      catApply f 20 5 `shouldBe` 15

    it "compCatN composes category morphisms at the leaf" do
      let f x y = \z -> (x + y + z :: Int)
          g z = show (z * 2)
      compCatN @2 g f 10 20 5 `shouldBe` "70"

    it "yankCat pulls curried category inputs to position 0" do
      let sub3 x y z = (x - y - z :: Int)
      yankCat @1 sub3 10 100 5 `shouldBe` (100 - 10 - 5)
      yankCat @2 sub3 5 100 10 `shouldBe` (100 - 10 - 5)

    it "permuteCat permutes curried category inputs arbitrarily" do
      let tuple3 x y z = (x, y, z) :: (Int, String, Bool)
          perm = permuteCat @'[2, 0, 1] tuple3
      perm False 100 "hello" `shouldBe` (100, "hello", False)

    it "permuteHandler reorders environment parameters of an effect handler" do
      let handler env1 env2 env3 = \(x :: Int) -> env1 ++ ":" ++ show env2 ++ ":" ++ env3 ++ ":" ++ show x
          handler' = permuteHandler @'[2, 0, 1] handler
      handler' "c" "a" (42 :: Int) 10 `shouldBe` "a:42:c:10"

    it "compHandlerN adapts profunctor strands produced by an effect handler" do
      let h e1 e2 = \(x :: Int) -> show (x + e1 * e2)
          adapted = compHandlerN @2 (lmap (* 2)) h
      adapted 3 4 5 `shouldBe` "22"

  -- Tested against Note [Tuple-Spine Arrow Permutations] [ref:note-tuple-spine-permutations]
  describe "Gongju.Profunctor Tuple-Spine Arrow & Strand Permutations" do
    it "yankTuple pulls tuple slots to the head" do
      let t3 = (10 :: Int, (20 :: Int, (30 :: Int, ())))
      yankTuple @0 t3 `shouldBe` t3
      yankTuple @1 t3 `shouldBe` (20, (10, (30, ())))
      yankTuple @2 t3 `shouldBe` (30, (10, (20, ())))

    it "permuteTuple permutes right-nested tuple slots arbitrarily" do
      let t3 = (10 :: Int, (20 :: Int, (30 :: Int, ())))
      permuteTuple @'[2, 0, 1] t3 `shouldBe` (30, (10, (20, ())))
      permuteTuple @'[1, 2, 0] t3 `shouldBe` (20, (30, (10, ())))

    it "permuteInputs reorders profunctor strand input tuple spines" do
      let strand = \(z, (x, (y, ()))) -> show (x * 100 + y * 10 + z :: Int)
          strand' = permuteInputs @'[2, 0, 1] strand
      strand' (1, (2, (3, ()))) `shouldBe` "123"

    it "permuteOutputs reorders profunctor strand output tuple spines" do
      let gen = \() -> (10 :: Int, (20 :: Int, (30 :: Int, ())))
          gen' = permuteOutputs @'[2, 0, 1] gen
      gen' () `shouldBe` (30, (10, (20, ())))

    it "dimapPermute simultaneously permutes input and output tuple spines" do
      let biStrand = \(y, (x, ())) -> (x ++ "!", (y + 1 :: Int, ()))
          d = dimapPermute @'[1, 0] @'[1, 0] biStrand
      d ("first", (100 :: Int, ())) `shouldBe` (101, ("first!", ()))

    it "swapStrand swaps binary pair inputs of a profunctor strand" do
      let p = \(b, a) -> (a - b :: Int)
      p (5, 20) `shouldBe` 15
      swapStrand p (5, 20) `shouldBe` (-15)

  -- Tested against Note [Zero-Cost Inlining and Core Simplification] [ref:note-zero-cost-core-simplification]
  describe "Gongju.Core Zero-Cost Inspection Fixtures" do
    it "testComp2 and testComp3 evaluate arity composition" do
      testComp2 (* 2) (+) 10 20 `shouldBe` 60
      testComp3 (* 2) (\x y z -> x + y + z) 1 2 3 `shouldBe` 12

    it "testSlipr1, testSlipl2, testTwist1, testYank1, testShove2 manipulate positional arguments" do
      let f3 x y z = (x * 100 + y * 10 + z :: Int)
      testSlipr1 (-) 5 20 `shouldBe` 15
      testYank1 (-) 5 20 `shouldBe` 15
      testSlipl2 f3 1 2 3 `shouldBe` 312
      testShove2 f3 1 2 3 `shouldBe` 312
      testTwist1 f3 1 3 2 `shouldBe` 123

    it "testPermute3 permutes arguments [2, 0, 1]" do
      let f3 x y z = (x * 100 + y * 10 + z :: Int)
      testPermute3 f3 3 1 2 `shouldBe` 123

    it "testPrecompose1 and testSplice12 perform contravariant positional pre-composition" do
      testPrecompose1 (* 10) (-) 100 5 `shouldBe` 50
      testSplice12 (*) (\x y z -> x + y + z) 10 3 4 5 `shouldBe` 27

    it "testBind1, testBindPos11, testLiftPure2 perform monadic binding and lifting" do
      testBind1 (\x y -> Just (x + y)) (Just 20) 10 `shouldBe` Just 30
      testBind1 (\x y -> Just (x + y)) Nothing 10 `shouldBe` Nothing
      testBindPos11 (\x y z -> Just (x + y + z)) (Just 20) 10 5 `shouldBe` Just 35
      testBindPos11 (\x y z -> Just (x + y + z)) Nothing 10 5 `shouldBe` Nothing
      testLiftPure2 (+) 10 20 `shouldBe` Just 30

    it "testPermuteTuple3, testPermuteInputs, testSwapStrand permute tuple spines and strands" do
      testPermuteTuple3 (10, (20, (30, ()))) `shouldBe` (30, (10, (20, ())))
      testPermuteInputs (\(z, (x, (y, ()))) -> x * 100 + y * 10 + z) (1, (2, (3, ()))) `shouldBe` 123
      testSwapStrand (\(b, a) -> a - b) (5, 20) `shouldBe` (-15)

    it "testCatFlip, testCatApply, testCompCat2, testYankCat1, testPermuteCat3, testPermuteHandler3 manipulate category morphisms" do
      let f3 x y z = (x * 100 + y * 10 + z :: Int)
          f4 x y z env = (x * 1000 + y * 100 + z * 10 + env :: Int)
      testCatFlip (-) 5 20 `shouldBe` 15
      testCatApply (-) 20 5 `shouldBe` 15
      testCompCat2 (* 2) (\x y z -> x + y + z) 1 2 3 `shouldBe` 12
      testYankCat1 (-) 5 20 `shouldBe` 15
      testPermuteCat3 f3 3 1 2 `shouldBe` 123
      testPermuteHandler3 f4 3 1 2 4 `shouldBe` 1234