gongju-0.1.0.0: src/Gongju/Contravariant.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RequiredTypeArguments #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Module: Gongju.Contravariant
-- Description: Contravariant Syntax Grids, Positional Pre-Composition, and Alternating Parity Stacks
--
-- Replaces Template Haskell contravariant splices (such as 'composition-extra') with pure
-- type-level Peano induction, bidirectional functional dependencies, and type-safe argument pre-processing.
--
-- Provides:
-- * Positional curried pre-composition ('PrecomposeAt', 'precomposeAt @n', '(-.)', '(-.*)', '(-.**)', '(-.***)').
-- * 2D function splicing grid ('SpliceAt', 'spliceAt @n @m').
-- * Alternating parity contravariant functor mapping ('ContramapN', 'contramapN @n', '(<-$>)', '(<-$$>)', '(<-$$$>)').
-- * Canonical re-exports of 'Contravariant', 'Divisible', and 'Decidable' from @base@.
module Gongju.Contravariant
( -- * Positional Pre-Composition
PrecomposeAt (..)
, precomposeAt
, (-.)
, (-.*)
, (-.**)
, (-.***)
-- * 2D Function Splicing Grid
, SpliceAt (..)
, spliceAt
-- * Nested Contravariant Mapping (Alternating Parity)
, ContramapN (..)
, ContravariantN
, contramapN
, (<-$>)
, (<-$$>)
, (<-$$$>)
, (<-$$$$>)
-- * Re-exported Canonical Classes & Types
, Contravariant (..)
, (>$<)
, Predicate (..)
, Comparison (..)
, Equivalence (..)
, Op (..)
, Divisible (..)
, Decidable (..)
) where
import Data.Functor.Contravariant
( Comparison (..)
, Contravariant (..)
, Equivalence (..)
, Op (..)
, Predicate (..)
, (>$<)
)
import Data.Functor.Contravariant.Divisible
( Decidable (..)
, Divisible (..)
)
import GHC.TypeLits (Nat)
import Gongju.Composition (ComposeN (..))
import Gongju.Peano (Peano (..), ToPeano)
{- Note [Contravariant Positional Pre-Composition]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-contravariant-precomposition]
Because the function arrow @(->)@ is contravariant in its argument position,
"contravariant function composition" corresponds to pre-composing a transformation
function onto an argument consumed by another function.
In libraries relying on Template Haskell splices (such as 'composition-extra'),
operators proliferate along two distinct axes:
1. The target argument slot index @n@ of @f@ being pre-composed:
@(-.)@ at slot 0, @(-.*)@ at slot 1, @(-.**)@ at slot 2, etc.
2. The arity @m@ of arguments consumed by the pre-composed function @h@:
@(-.)@ for unary, @(-.:)@ for binary, @(-..:)@ for ternary, etc.
Gongju collapses both axes into two inductively resolved typeclasses:
* 'PrecomposeAt': Unary pre-composition at positional depth @n@.
* 'SpliceAt': Full 2D generalization splicing an @m@-ary function into slot @n@.
Full bidirectional functional dependencies are maintained across the grid:
- @n f -> b@: Determines the target argument type @b@ from slot @n@ and @f@.
- @n g -> a@: Determines the new argument type @a@ from slot @n@ and @g@.
- @n a f -> g@: Infers the transformed signature @g@ from input @a@ and @f@.
- @n b g -> f@: Recovers the original signature @f@ from target @b@ and @g@.
-}
-- ---------------------------------------------------------------------------
-- 1. Positional Pre-Composition (PrecomposeAt)
-- ---------------------------------------------------------------------------
-- | Pre-compose a unary function @(a -> b)@ onto the @n@-th argument (0-indexed) of @f@.
--
-- See Note [Contravariant Positional Pre-Composition] [ref:note-contravariant-precomposition].
class PrecomposeAt (n :: Peano) a b f g | n f -> b, n g -> a, n a f -> g, n b g -> f where
-- | Internal inductive worker operating on 'Peano' terms.
precomposeAt' :: (a -> b) -> f -> g
instance PrecomposeAt 'Z a b (b -> r) (a -> r) where
precomposeAt' h f a = f (h a)
{-# INLINE precomposeAt' #-}
instance PrecomposeAt n a b r r' => PrecomposeAt ('S n) a b (x -> r) (x -> r') where
precomposeAt' h f x = precomposeAt' @n h (f x)
{-# INLINE precomposeAt' #-}
-- | User-facing positional pre-composition parameterized by visible 'Nat' literal @n@.
--
-- Example:
--
-- >>> let add3 x y z = (x + y + z :: Int)
-- >>> precomposeAt @1 (* 10) add3 1 2 3
-- 24
precomposeAt :: forall (n :: Nat) a b f g. PrecomposeAt (ToPeano n) a b f g => (a -> b) -> f -> g
precomposeAt = precomposeAt' @(ToPeano n)
{-# INLINE precomposeAt #-}
-- | Infix pre-composition at argument slot 0: @(h -. f) a = f (h a)@.
infixr 9 -.
(-.) :: PrecomposeAt 'Z a b f g => (a -> b) -> f -> g
(-.) = precomposeAt' @'Z
{-# INLINE (-.) #-}
-- | Infix pre-composition at argument slot 1: @(h -.* f) x a = f x (h a)@.
infixr 9 -.*
(-.*) :: PrecomposeAt ('S 'Z) a b f g => (a -> b) -> f -> g
(-.*) = precomposeAt' @('S 'Z)
{-# INLINE (-.*) #-}
-- | Infix pre-composition at argument slot 2: @(h -.** f) x y a = f x y (h a)@.
infixr 9 -.**
(-.**) :: PrecomposeAt ('S ('S 'Z)) a b f g => (a -> b) -> f -> g
(-.**) = precomposeAt' @('S ('S 'Z))
{-# INLINE (-.**) #-}
-- | Infix pre-composition at argument slot 3: @(h -.*** f) x y z a = f x y z (h a)@.
infixr 9 -.***
(-.***) :: PrecomposeAt ('S ('S ('S 'Z))) a b f g => (a -> b) -> f -> g
(-.***) = precomposeAt' @('S ('S ('S 'Z)))
{-# INLINE (-.***) #-}
-- ---------------------------------------------------------------------------
-- 2. 2D Function Splicing Grid (SpliceAt)
-- ---------------------------------------------------------------------------
-- | Splice an @m@-ary function @h@ directly into the @n@-th argument slot of @f@.
--
-- The resulting function @g@ consumes @n@ arguments, then the @m@ arguments required by @h@,
-- followed by the remaining arguments of @f@.
--
-- See Note [Contravariant Positional Pre-Composition] [ref:note-contravariant-precomposition].
class SpliceAt (n :: Peano) (m :: Peano) h f g | n m h f -> g where
-- | Internal inductive worker operating on 'Peano' depths.
spliceAt' :: h -> f -> g
instance ComposeN m d r h g => SpliceAt 'Z m h (d -> r) g where
spliceAt' h f = composeN' @m f h
{-# INLINE spliceAt' #-}
instance SpliceAt n m h r r' => SpliceAt ('S n) m h (a -> r) (a -> r') where
spliceAt' h f a = spliceAt' @n @m h (f a)
{-# INLINE spliceAt' #-}
-- | User-facing 2D function splicing parameterized by visible 'Nat' literals @n@ (target slot)
-- and @m@ (spliced function arity).
--
-- Example:
--
-- >>> let add3 x y z = (x + y + z :: Int)
-- >>> let mult2 u v = (u * v :: Int)
-- >>> spliceAt @1 @2 mult2 add3 10 3 4 5
-- 27
spliceAt :: forall (n :: Nat) (m :: Nat) h f g. SpliceAt (ToPeano n) (ToPeano m) h f g => h -> f -> g
spliceAt = spliceAt' @(ToPeano n) @(ToPeano m)
{-# INLINE spliceAt #-}
{- Note [Contravariant Functor Alternating Parity]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-contravariant-alternating-parity]
In 'Data.Functor.Contravariant.Syntax' (operators @<-$@, @<-$$@, @<-$$$@, ...),
mapping a transformation function @(a -> b)@ through nested 'Contravariant' functor layers
introduces an alternating parity flip:
- 1 layer (@1): @(a -> b) -> f b -> f a@ (contravariant: reverses arrow)
- 2 layers (@2): @(a -> b) -> f (g a) -> f (g b)@ (covariant: two reversals cancel!)
- 3 layers (@3): @(a -> b) -> f (g (h b)) -> f (g (h a))@ (contravariant again)
With Peano induction, swapping @s@ and @t@ in the recursive constraint:
@instance (Contravariant f, ContramapN n a b t s) => ContramapN ('S n) a b (f s) (f t)@
tracks this variance reversal automatically.
Furthermore, the pair of bidirectional functional dependencies:
@n a b s -> t, n a b t -> s@
mutually inductive-check each other across the parity swap, preserving full type inference.
-}
-- ---------------------------------------------------------------------------
-- 3. Nested Contravariant Functor Mapping (Alternating Parity)
-- ---------------------------------------------------------------------------
-- | Map a transformation through @n@ nested 'Contravariant' functor layers with alternating parity.
--
-- See Note [Contravariant Functor Alternating Parity] [ref:note-contravariant-alternating-parity].
class ContramapN (n :: Peano) a b s t | n s t -> a b, n a b s -> t, n a b t -> s where
-- | Internal inductive worker operating on 'Peano' depth.
contramapN' :: (a -> b) -> s -> t
instance ContramapN 'Z a b a b where
contramapN' f = f
{-# INLINE contramapN' #-}
instance (Contravariant f, ContramapN n a b t s) => ContramapN ('S n) a b (f s) (f t) where
contramapN' f = contramap (contramapN' @n f)
{-# INLINE contramapN' #-}
-- | User-facing constraint synonym parameterized by visible 'Nat' depth @n@.
type ContravariantN (n :: Nat) a b s t = ContramapN (ToPeano n) a b s t
-- | Map a transformation through @n@ nested 'Contravariant' functor layers.
--
-- Examples:
--
-- >>> let p = Predicate (> 0) :: Predicate Int
-- >>> let p' = contramapN @1 (* 2) p
-- >>> getPredicate p' 5
-- True
contramapN :: forall (n :: Nat) a b s t. ContramapN (ToPeano n) a b s t => (a -> b) -> s -> t
contramapN = contramapN' @(ToPeano n)
{-# INLINE contramapN #-}
-- | Infix contravariant mapping at depth 1 (equivalent to 'contramap' / '(>$<)').
infixr 4 <-$>
(<-$>) :: ContramapN ('S 'Z) a b s t => (a -> b) -> s -> t
(<-$>) = contramapN' @('S 'Z)
{-# INLINE (<-$>) #-}
-- | Infix contravariant mapping at depth 2 (covariant due to parity cancellation).
infixr 4 <-$$>
(<-$$>) :: ContramapN ('S ('S 'Z)) a b s t => (a -> b) -> s -> t
(<-$$>) = contramapN' @('S ('S 'Z))
{-# INLINE (<-$$>) #-}
-- | Infix contravariant mapping at depth 3 (contravariant).
infixr 4 <-$$$>
(<-$$$>) :: ContramapN ('S ('S ('S 'Z))) a b s t => (a -> b) -> s -> t
(<-$$$>) = contramapN' @('S ('S ('S 'Z)))
{-# INLINE (<-$$$>) #-}
-- | Infix contravariant mapping at depth 4 (covariant).
infixr 4 <-$$$$>
(<-$$$$>) :: ContramapN ('S ('S ('S ('S 'Z)))) a b s t => (a -> b) -> s -> t
(<-$$$$>) = contramapN' @('S ('S ('S ('S 'Z))))
{-# INLINE (<-$$$$>) #-}