gongju-0.1.0.0: src/Gongju/Profunctor.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 TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
-- | Module: Gongju.Profunctor
-- Description: Profunctors, Indexed Profunctors, and Effect Handler Permutations for Gongju
--
-- Plugs canonical 'Profunctor' from 'Data.Profunctor' and 'IxProfunctor' from
-- 'Data.Profunctor.Indexed' into Gongju's open tape composition engine, and provides
-- zero-cost argument permutations for binary profunctor strands and curried effect handlers
-- (such as in @kernmantle-polytropos@).
--
-- Supports:
-- * Contravariant input pre-processing via 'P1' / 'lmap'.
-- * Covariant output post-processing via 'P2' / 'rmap'.
-- * Simultaneous bidirectional mapping via 'dimap'.
-- * Curried category spine generalization and point-free permutations ('CurriedCat', 'ComposeCatN', 'YankCat', 'PermuteCat').
-- * Tuple-spine arrow input/output permutations ('YankTuple', 'PermuteTuple', 'permuteInputs', 'permuteOutputs', 'dimapPermute').
-- * Curried effect handler environment reordering ('permuteHandler', 'compHandlerN').
module Gongju.Profunctor
( -- * Re-exported Canonical Profunctor Classes
Profunctor(..)
, dimap'
, lmap'
, rmap'
-- * Re-exported Indexed Profunctors
, IxProfunctor
, ixcontramap
-- * Open Profunctor Tags
, P1
, P2
-- * Ergonomic Aliases
, PP1
, PP2
-- * Curried Category Permutations
, CurriedCat (..)
, ComposeCatN (..)
, compCatN
, YankCat (..)
, yankCat
, PermuteCat (..)
, permuteCat
-- * Tuple-Spine Arrow Permutations
, YankTuple (..)
, yankTuple
, PermuteTuple (..)
, permuteTuple
-- * Profunctor Strand Permutations
, permuteInputs
, permuteOutputs
, dimapPermute
, swapStrand
-- * Curried Effect Handler Permutations
, permuteHandler
, compHandlerN
) where
import Control.Arrow (Arrow (arr, second))
import Control.Category (Category)
import qualified Control.Category as Cat
import Data.Kind (Type)
import qualified Data.Profunctor as P
import Data.Profunctor
( Profunctor (..)
)
import qualified Data.Profunctor.Indexed as IxP
import GHC.TypeLits (Nat)
import Gongju.Composition
( ComposeN (..)
, InOrder
, MapPath (..)
, MapStep (..)
, PL
, compN
)
import Gongju.Peano (Peano (..), ToPeano)
import Gongju.Permute
( MapAdjust
, MapToPeano
, Permute (..)
, permute
)
import Prelude hiding (id)
{- Note [Profunctor Effect Handler Permutations]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-profunctor-handler-permutations]
In arrow-based architectures and profunctor effect frameworks (such as 'kernmantle'
and 'kernmantle-polytropos'), effect strands are modeled as binary profunctors:
@strand :: Type -> Type -> Type@
while effect handlers are curried functions taking environment configuration parameters
before producing or interpreting a strand:
@handler :: env1 -> env2 -> ... -> p a b@
Permuting profunctor handlers requires addressing two distinct structural axes:
1. Curried environment parameter permutations:
Reordering @env1 -> env2 -> ... -> envk@ via 'permuteHandler @ns' (which delegates
to Gongju's Peano-indexed 'Permute' engine).
2. Curried category spine generalization:
Generalizing from @(->)@ to an arbitrary Cartesian Closed Category ('CurriedCat cat')
via 'YankCat' and 'PermuteCat'. Here, arguments are permuted point-free using only
'catFlip' and category composition '(.)', never inspecting or allocating Haskell values.
-}
{- Note [Tuple-Spine Arrow Permutations]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-tuple-spine-permutations]
When binary profunctors or arrows consume multiple arguments simultaneously, they package
their input domain as right-nested tuples:
@(a0, (a1, (a2, ())))@
Permuting slots in an arrow input spine requires structural induction dual to curried 'Yank':
1. 'YankTuple (n :: Peano) arr s t':
Pulls the @n@-th slot of the nested tuple to position 0.
- Base case (@'Z@): The 0-th element is already at the head; returns 'Cat.id'.
- Step case (@'S n@): Recursively pulls the @n@-th slot in the tail @r@ via
'second (yankTuple @n)', then swaps the head and extracted slot using
'arr (\(a, (target, rest)) -> (target, (a, rest)))'.
2. 'PermuteTuple (ps :: [Peano]) arr s t':
Iterates 'YankTuple' across a permutation index list, updating residual indices
at each step with 'MapAdjust'.
3. 'permuteInputs @ns' and 'permuteOutputs @ns':
For any 'Profunctor p', pre-composing 'lmap (permuteTuple @ns)' or post-composing
'rmap (permuteTuple @ns)' reorders the strand's input or output domain directly
with zero runtime tuple packaging overhead.
-}
-- ---------------------------------------------------------------------------
-- 1. Open Profunctor Tags & Re-exports
-- ---------------------------------------------------------------------------
-- | Profunctor first slot tag (contravariant).
data P1
-- | Profunctor second slot tag (covariant).
data P2
-- | In-order profunctor first-slot (contravariant) mapping alias.
type PP1 = InOrder (PL '[P1])
-- | In-order profunctor second-slot (covariant) mapping alias.
type PP2 = InOrder (PL '[P2])
-- | Type alias specializing 'Data.Profunctor.Indexed.Profunctor' as 'IxProfunctor'.
type IxProfunctor = IxP.Profunctor
-- | Re-export indexed profunctor index contramap.
ixcontramap :: IxProfunctor p => (j -> i) -> p i a b -> p j a b
ixcontramap = IxP.ixcontramap
{-# INLINE ixcontramap #-}
-- | Strict variant of 'dimap'.
dimap' :: Profunctor p => (a -> b) -> (c -> d) -> p b c -> p a d
dimap' = P.dimap
{-# INLINE dimap' #-}
-- | Strict variant of 'lmap'.
lmap' :: Profunctor p => (a -> b) -> p b c -> p a c
lmap' = P.lmap
{-# INLINE lmap' #-}
-- | Strict variant of 'rmap'.
rmap' :: Profunctor p => (b -> c) -> p a b -> p a c
rmap' = P.rmap
{-# INLINE rmap' #-}
-- ---------------------------------------------------------------------------
-- 2. MapStep Instances for Profunctor Layers
-- ---------------------------------------------------------------------------
-- | Pre-compose argument 1 of a Profunctor contravariantly via 'lmap'.
instance (Profunctor p, s ~ p b c, t ~ p a c, MapPath ls a b a b)
=> MapStep P1 ls a b s t where
mapStep f = P.lmap (mapPath @ls f)
{-# INLINE mapStep #-}
-- | Post-compose argument 2 of a Profunctor covariantly via 'rmap'.
instance (Profunctor p, s ~ p a b, t ~ p a c, MapPath ls b c b c)
=> MapStep P2 ls b c s t where
mapStep f = P.rmap (mapPath @ls f)
{-# INLINE mapStep #-}
-- ---------------------------------------------------------------------------
-- 3. Curried Category Permutations (CurriedCat)
-- ---------------------------------------------------------------------------
-- | Cartesian Closed Category / Curried Category with morphism flipping and application.
--
-- See Note [Profunctor Effect Handler Permutations] [ref:note-profunctor-handler-permutations].
class Category cat => CurriedCat (cat :: Type -> Type -> Type) where
-- | Post-compose a transformation onto the return slot of a morphism in @cat@.
catMap :: (b -> c) -> cat a b -> cat a c
-- | Swap the first two curried inputs of a morphism in @cat@.
catFlip :: cat a (cat b c) -> cat b (cat a c)
-- | Bridge morphism execution from Haskell values.
catApply :: cat a b -> a -> b
instance CurriedCat (->) where
catMap f g = f . g
catFlip f b a = f a b
catApply f x = f x
{-# INLINE catMap #-}
{-# INLINE catFlip #-}
{-# INLINE catApply #-}
-- | Generalized arity composition over a 'Category' @cat@ at the leaf.
class Category cat => ComposeCatN (n :: Peano) (cat :: Type -> Type -> Type) x y z g out | n g -> cat x y, n cat y z g -> out where
-- | Internal inductive worker for category composition.
composeCatN' :: cat y z -> g -> out
instance (Category cat, g ~ cat x y, out ~ cat x z) => ComposeCatN 'Z cat x y z g out where
composeCatN' f g = f Cat.. g
{-# INLINE composeCatN' #-}
instance ( Category cat
, ComposeCatN n cat x y z r r'
, g ~ (a -> r)
, out ~ (a -> r')
) => ComposeCatN ('S n) cat x y z g out where
composeCatN' f g a = composeCatN' @n @cat @x @y @z f (g a)
{-# INLINE composeCatN' #-}
-- | User-facing arity composition over any 'Category' @cat@.
compCatN :: forall (n :: Nat) cat x y z g out.
ComposeCatN (ToPeano n) cat x y z g out => cat y z -> g -> out
compCatN = composeCatN' @(ToPeano n) @cat @x @y @z
{-# INLINE compCatN #-}
-- | Point-free bubbling of the @n@-th curried category input using 'catFlip' and 'catMap'.
--
-- See Note [Profunctor Effect Handler Permutations] [ref:note-profunctor-handler-permutations].
class CurriedCat cat => YankCat (n :: Peano) (cat :: Type -> Type -> Type) f g | n f -> g where
-- | Internal inductive worker pulling the @n@-th input to position 0.
yankCat' :: f -> g
instance (CurriedCat cat, f ~ cat a r, g ~ cat a r) => YankCat 'Z cat f g where
yankCat' = Cat.id
{-# INLINE yankCat' #-}
instance ( CurriedCat cat
, f ~ cat a r
, r' ~ cat target rest
, g ~ cat target (cat a rest)
, YankCat n cat r r'
) => YankCat ('S n) cat f g where
yankCat' f = catFlip (catMap (yankCat' @n @cat) f)
{-# INLINE yankCat' #-}
-- | User-facing positional yank over any 'CurriedCat'.
yankCat :: forall (n :: Nat) cat f g.
YankCat (ToPeano n) cat f g => f -> g
yankCat = yankCat' @(ToPeano n) @cat
{-# INLINE yankCat #-}
-- | Arbitrary permutation of curried category inputs over any 'CurriedCat'.
--
-- See Note [Profunctor Effect Handler Permutations] [ref:note-profunctor-handler-permutations].
class CurriedCat cat => PermuteCat (ps :: [Peano]) (cat :: Type -> Type -> Type) f g | ps f -> g where
-- | Internal inductive worker executing iterated point-free permutation.
permuteCat' :: f -> g
instance (CurriedCat cat, f ~ g) => PermuteCat '[] cat f g where
permuteCat' = Cat.id
{-# INLINE permuteCat' #-}
instance ( CurriedCat cat
, mid ~ cat arg rest
, g ~ cat arg rest'
, YankCat p cat f mid
, PermuteCat (MapAdjust p ps) cat rest rest'
) => PermuteCat (p ': ps) cat f g where
permuteCat' f = catMap (permuteCat' @(MapAdjust p ps) @cat) (yankCat' @p @cat f)
{-# INLINE permuteCat' #-}
-- | User-facing permutation of curried category inputs.
permuteCat :: forall (ns :: [Nat]) cat f g.
PermuteCat (MapToPeano ns) cat f g => f -> g
permuteCat = permuteCat' @(MapToPeano ns) @cat
{-# INLINE permuteCat #-}
-- ---------------------------------------------------------------------------
-- 4. Tuple-Spine Arrow Permutations (YankTuple & PermuteTuple)
-- ---------------------------------------------------------------------------
-- | Pull the @n@-th element of a right-nested tuple @s@ to the front.
--
-- See Note [Tuple-Spine Arrow Permutations] [ref:note-tuple-spine-permutations].
class Arrow ar => YankTuple (n :: Peano) (ar :: Type -> Type -> Type) s t | n s -> t where
-- | Internal inductive worker pulling the @n@-th tuple slot to position 0.
yankTuple' :: ar s t
instance Arrow ar => YankTuple 'Z ar s s where
yankTuple' = Cat.id
{-# INLINE yankTuple' #-}
instance ( Arrow ar
, YankTuple n ar r (target, rest)
) => YankTuple ('S n) ar (a, r) (target, (a, rest)) where
yankTuple' = arr (\(a, (target, rest)) -> (target, (a, rest))) Cat.. second (yankTuple' @n @ar)
{-# INLINE yankTuple' #-}
-- | User-facing tuple slot yank for any 'Arrow'.
yankTuple :: forall (n :: Nat) ar s t.
YankTuple (ToPeano n) ar s t => ar s t
yankTuple = yankTuple' @(ToPeano n) @ar
{-# INLINE yankTuple #-}
-- | Arbitrary permutation of slots in a right-nested tuple spine for an 'Arrow'.
--
-- See Note [Tuple-Spine Arrow Permutations] [ref:note-tuple-spine-permutations].
class Arrow ar => PermuteTuple (ps :: [Peano]) (ar :: Type -> Type -> Type) s t | ps s -> t where
-- | Internal inductive worker executing iterated tuple slot permutation.
permuteTuple' :: ar s t
instance Arrow ar => PermuteTuple '[] ar s s where
permuteTuple' = Cat.id
{-# INLINE permuteTuple' #-}
instance ( Arrow ar
, mid ~ (arg, rest)
, t ~ (arg, rest')
, YankTuple p ar s mid
, PermuteTuple (MapAdjust p ps) ar rest rest'
) => PermuteTuple (p ': ps) ar s t where
permuteTuple' = second (permuteTuple' @(MapAdjust p ps) @ar) Cat.. yankTuple' @p @ar
{-# INLINE permuteTuple' #-}
-- | User-facing tuple spine permutation for any 'Arrow'.
--
-- Examples:
--
-- >>> let t = (10 :: Int, (20 :: Int, (30 :: Int, ())))
-- >>> permuteTuple @'[2, 0, 1] t
-- (30,(10,(20,())))
permuteTuple :: forall (ns :: [Nat]) ar s t.
PermuteTuple (MapToPeano ns) ar s t => ar s t
permuteTuple = permuteTuple' @(MapToPeano ns) @ar
{-# INLINE permuteTuple #-}
-- ---------------------------------------------------------------------------
-- 5. Profunctor Strand Permutations
-- ---------------------------------------------------------------------------
-- | Permute the right-nested tuple input spine of a 'Profunctor' according to visible 'Nat' list @ns@.
--
-- See Note [Tuple-Spine Arrow Permutations] [ref:note-tuple-spine-permutations].
permuteInputs :: forall (ns :: [Nat]) p s t b.
(Profunctor p, PermuteTuple (MapToPeano ns) (->) s t) => p t b -> p s b
permuteInputs = P.lmap (permuteTuple @ns)
{-# INLINE permuteInputs #-}
-- | Permute the right-nested tuple output spine of a 'Profunctor' according to visible 'Nat' list @ns@.
--
-- See Note [Tuple-Spine Arrow Permutations] [ref:note-tuple-spine-permutations].
permuteOutputs :: forall (ns :: [Nat]) p a s t.
(Profunctor p, PermuteTuple (MapToPeano ns) (->) s t) => p a s -> p a t
permuteOutputs = P.rmap (permuteTuple @ns)
{-# INLINE permuteOutputs #-}
-- | Simultaneously permute both the input and output tuple spines of a 'Profunctor'.
dimapPermute :: forall (nsIn :: [Nat]) (nsOut :: [Nat]) p sIn tIn sOut tOut.
( Profunctor p
, PermuteTuple (MapToPeano nsIn) (->) sIn tIn
, PermuteTuple (MapToPeano nsOut) (->) sOut tOut
) => p tIn sOut -> p sIn tOut
dimapPermute = P.dimap (permuteTuple @nsIn) (permuteTuple @nsOut)
{-# INLINE dimapPermute #-}
-- | Swap the binary pair inputs of a 'Profunctor' strand: @p (b, a) c -> p (a, b) c@.
swapStrand :: Profunctor p => p (b, a) c -> p (a, b) c
swapStrand = P.lmap (\(a, b) -> (b, a))
{-# INLINE swapStrand #-}
-- ---------------------------------------------------------------------------
-- 6. Curried Effect Handler Permutations
-- ---------------------------------------------------------------------------
-- | Permute the leading curried environment/configuration arguments of an effect handler.
--
-- See Note [Profunctor Effect Handler Permutations] [ref:note-profunctor-handler-permutations].
--
-- For example, given @handler :: Env1 -> Env2 -> Env3 -> p a b@:
--
-- @
-- let handler' = permuteHandler @'[2, 0, 1] handler
-- -- handler' now expects: Env3 -> Env1 -> Env2 -> p a b
-- @
permuteHandler :: forall (ns :: [Nat]) f g.
Permute (MapToPeano ns) f g => f -> g
permuteHandler = permute @ns
{-# INLINE permuteHandler #-}
-- | Post-compose an arrow/profunctor transformation onto an @n@-ary curried effect handler.
--
-- For example, applying a strand adapter @(p b c -> q a d)@ to @handler :: env1 -> ... -> envn -> p b c@.
compHandlerN :: forall (n :: Nat) p q a b c d f g.
ComposeN (ToPeano n) (p b c) (q a d) f g => (p b c -> q a d) -> f -> g
compHandlerN = compN @n
{-# INLINE compHandlerN #-}