gongju-0.1.0.0: src/Gongju/Composition.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RequiredTypeArguments #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeAbstractions #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
-- | Module: Gongju.Composition
-- Description: The Grand Unified Open-World Tape Engine and Variadic Composition
--
-- Provides the open tag-driven state machine ('StepPathMask', 'StepRole') for multi-stage
-- functor and arrow composition, arbitrary argument permutation ('Permute'), lax monoidal
-- zipping ('ApPath'), distributive laws ('CommutePast', 'PullPath'), and ergonomic
-- visible-forall entry points ('composeUnifiedV', 'withOps', 'infixr 9 .@').
--
-- Also preserves legacy inductive Peano arity composition ('ComposeN', 'compN') and
-- nested functor mapping ('FmapN', 'fmapN') with bidirectional functional dependencies.
module Gongju.Composition
( -- * Open Layer Tags
F
, C
, B1
, B2
, BB
, Fn
-- * Open Mapping Primitives
, MapStep(..)
, MapPath(..)
-- * Open Zipping Primitives
, ApStep(..)
, ApPath(..)
-- * Distributive Laws & Commuting
, CommutePair(..)
, CommutePast(..)
, PullPath(..)
-- * Open State Machine & Spec Tags
, StepRole(..)
, StepPathMask(..)
, type (#)
, InOrder
, PP
, PZ
, PL
, PM
, J
, CS
, SwapAt
, PullRet
-- * Ergonomic Unindexed Aliases
, P
, Z
, M
, L
, LF
, PullF
-- * Type Families for Index & Mode Extraction
, UnwrapMode
, OpModes
, HasExplicitIndex
, OpIndices
, OpIndicesIf
, TakePeano
, AtPeano
, SwapAtPeano
-- * Visible Forall & Infix Entry Points
, composeUnifiedV
, withOps
, (.@)
-- * Backward-Compatible Variadic Combinators
, ComposeN(..)
, compN
, FmapN(..)
, fmapN
) where
import Control.Monad (join)
import Data.Bifunctor (Bifunctor (bimap, first, second))
import Data.Bitraversable (Bitraversable (bitraverse), bisequenceA)
import Data.Functor.Contravariant (Contravariant (contramap))
import Data.Kind (Type)
import GHC.TypeLits (Nat)
import Gongju.Peano
( Append
, Init
, Peano (..)
, Replicate
, ToPeano
)
import Gongju.Permute (Permute (..))
import Gongju.Positional (ExpectArrow)
{- Note [Variadic Composition Bidirectional Fundeps]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-variadic-composition-fundeps]
Template Haskell composition libraries (such as 'composition-extra') generate static
operators for fixed arities ((.:), (..:), (...:)). Beyond bloating code size, Template
Haskell splices cannot be abstracted over dynamically in type-level pipelines.
Gongju replaces these splices with an open tag-driven execution engine ('StepPathMask')
and inductive arity composition ('ComposeN'), indexed by Peano depth or tape masks.
Crucially, bidirectional functional dependencies are maintained across the engine:
- @n b c g -> f@: Propagates input function types forward to determine composite function type.
- @n f -> c@: Propagates expected output type backward into the composite structure.
- @n f b -> g@: Infers intermediate argument shapes under polymorphic unification.
-}
-- ---------------------------------------------------------------------------
-- 1. Open Layer Tags (Extensible by Downstream Modules Without Orphans)
-- ---------------------------------------------------------------------------
-- | Covariant Functor layer tag.
data F
-- | Contravariant Functor layer tag.
data C
-- | Bifunctor first slot tag.
data B1
-- | Bifunctor second slot tag.
data B2
-- | Bifunctor diagonal tag (both slots).
data BB
-- | Replicate @n@ covariant 'F' functor layers.
type Fn (n :: Nat) = Replicate n F
-- ---------------------------------------------------------------------------
-- 2. Open Primitive 1: MapStep & MapPath
-- ---------------------------------------------------------------------------
-- | 1-step mapping class dispatched on the head tag of kind 'Type'.
class MapStep (tag :: Type) (rest :: [Type]) a b s t where
mapStep :: (a -> b) -> s -> t
-- | Sequential path mapping through a type-level stack of functor layers.
class MapPath (ls :: [Type]) a b s t where
mapPath :: (a -> b) -> s -> t
instance (s ~ a, t ~ b) => MapPath '[] a b s t where
mapPath f = f
{-# INLINE mapPath #-}
instance MapStep tag ls a b s t => MapPath (tag ': ls) a b s t where
mapPath = mapStep @tag @ls
{-# INLINE mapPath #-}
instance (Functor φ, s ~ φ s', t ~ φ t', MapPath ls a b s' t')
=> MapStep F ls a b s t where
mapStep f = fmap (mapPath @ls f)
{-# INLINE mapStep #-}
instance (Contravariant φ, s ~ φ s', t ~ φ t', MapPath ls a b t' s')
=> MapStep C ls a b s t where
mapStep f = contramap (mapPath @ls f)
{-# INLINE mapStep #-}
instance (Bifunctor p, s ~ p s' c, t ~ p t' c, MapPath ls a b s' t')
=> MapStep B1 ls a b s t where
mapStep f = first (mapPath @ls f)
{-# INLINE mapStep #-}
instance (Bifunctor p, s ~ p c s', t ~ p c t', MapPath ls a b s' t')
=> MapStep B2 ls a b s t where
mapStep f = second (mapPath @ls f)
{-# INLINE mapStep #-}
instance (Bifunctor p, s ~ p s' s', t ~ p t' t', MapPath ls a b s' t')
=> MapStep BB ls a b s t where
mapStep f = let g = mapPath @ls f in bimap g g
{-# INLINE mapStep #-}
-- ---------------------------------------------------------------------------
-- 3. Open Primitive 2: ApStep & ApPath (Lax Monoidal Zipping)
-- ---------------------------------------------------------------------------
-- | 1-step applicative zipping class dispatched on layer tag.
class ApStep (tag :: Type) (rest :: [Type]) fab fa fb where
apStep :: fab -> fa -> fb
-- | Sequential applicative zipping along a stack of functor layers.
class ApPath (ls :: [Type]) fab fa fb where
apPath :: fab -> fa -> fb
instance (fab ~ (fa -> fb)) => ApPath '[] fab fa fb where
apPath = id
{-# INLINE apPath #-}
instance ApStep tag ls fab fa fb => ApPath (tag ': ls) fab fa fb where
apPath = apStep @tag @ls
{-# INLINE apPath #-}
instance (Applicative φ, fab ~ φ fab', fa ~ φ fa', fb ~ φ fb', ApPath ls fab' fa' fb')
=> ApStep F ls fab fa fb where
apStep = liftA2 (apPath @ls)
{-# INLINE apStep #-}
instance (Applicative (p c), fab ~ p c fab', fa ~ p c fa', fb ~ p c fb', ApPath ls fab' fa' fb')
=> ApStep B2 ls fab fa fb where
apStep = liftA2 (apPath @ls)
{-# INLINE apStep #-}
-- ---------------------------------------------------------------------------
-- 4. Open Primitive 3: CommutePair & PullPath (Distributive Laws)
-- ---------------------------------------------------------------------------
-- | Commute adjacent effect layers @l1@ and @l2@.
class CommutePair (l1 :: Type) (l2 :: Type) s t where
commutePair :: s -> t
instance (Traversable φ, Applicative ψ, s ~ φ (ψ inner), t ~ ψ (φ inner))
=> CommutePair F F s t where
commutePair = sequenceA
{-# INLINE commutePair #-}
instance (Bitraversable p, Applicative ψ, s ~ p c (ψ inner), t ~ ψ (p c inner))
=> CommutePair B2 F s t where
commutePair = bitraverse pure id
{-# INLINE commutePair #-}
instance (Bitraversable p, Applicative ψ, s ~ p (ψ inner) c, t ~ ψ (p inner c))
=> CommutePair B1 F s t where
commutePair = bitraverse id pure
{-# INLINE commutePair #-}
instance (Bitraversable p, Applicative ψ, s ~ p (ψ inner) (ψ inner), t ~ ψ (p inner inner))
=> CommutePair BB F s t where
commutePair = bisequenceA
{-# INLINE commutePair #-}
-- | Bubble an inner layer @r@ outward past an entire @outer@ stack.
class CommutePast (outer :: [Type]) (r :: Type) s t where
commutePast :: s -> t
instance (s ~ t) => CommutePast '[] r s t where
commutePast = id
{-# INLINE commutePast #-}
instance forall l ls r s mid t subS subT.
( CommutePast ls r subS subT
, MapPath '[ l ] subS subT s mid
, CommutePair l r mid t
) => CommutePast (l ': ls) r s t where
commutePast = commutePair @l @r @mid @t . mapPath @'[ l ] @subS @subT @s @mid (commutePast @ls @r @subS @subT)
{-# INLINE commutePast #-}
-- | Pull an entire @retPath@ stack outward past @outer@.
class PullPath (outer :: [Type]) (retPath :: [Type]) s t where
pullPath :: s -> t
instance (s ~ t) => PullPath outer '[] s t where
pullPath = id
{-# INLINE pullPath #-}
instance forall outer r rs s mid t subS subT.
( CommutePast outer r s mid
, PullPath outer rs subS subT
, MapPath '[ r ] subS subT mid t
) => PullPath outer (r ': rs) s t where
pullPath = mapPath @'[ r ] @subS @subT @mid @t (pullPath @outer @rs @subS @subT) . commutePast @outer @r @s @mid
{-# INLINE pullPath #-}
-- ---------------------------------------------------------------------------
-- 5. Open Primitive 4: StepRole & StepPathMask (The Open State Machine)
-- ---------------------------------------------------------------------------
-- | Individual operational step executing a transition on the accumulator.
class StepRole (op :: Type) (base :: [Type]) (active :: [Type]) (ms :: [Type]) m t3 acc out where
stepRole :: (m -> t3) -> acc -> out
-- | Tape machine executing a list of operations @ms@ against the active functor stack.
class StepPathMask (base :: [Type]) (active :: [Type]) (ms :: [Type]) m t3 acc out where
stepPathMask :: (m -> t3) -> acc -> out
instance (acc ~ m, out ~ t3) => StepPathMask base active '[] m t3 acc out where
stepPathMask f acc = f acc
{-# INLINE stepPathMask #-}
instance StepRole op base active ms m t3 acc out
=> StepPathMask base active (op ': ms) m t3 acc out where
stepPathMask = stepRole @op @base @active @ms
{-# INLINE stepPathMask #-}
-- | Open spec tag pairing an explicit argument index with a mode.
infix 6 #
data (i :: Nat) # (mode :: Type)
-- | Open spec tag for unindexed in-order arguments.
data InOrder (mode :: Type)
-- | Pure argument mode tag.
data PP
-- | Applicative zipped argument mode tag.
data PZ
-- | Layered argument mode tag along sub-stack @sub@.
data PL (sub :: [Type])
-- | Monadic bind argument mode tag.
data PM
-- | Monadic return join tag.
data J
-- | Collapse 2 innermost monadic layers.
data CS
-- | Swap active layers at depth @d@ and @d + 1@.
data SwapAt (d :: Nat)
-- | Commute @retPath@ outward past @active@.
data PullRet (retPath :: [Type])
-- Ergonomic unindexed aliases:
type P = InOrder PP
type Z = InOrder PZ
type M = InOrder PM
type L (ls :: [Type]) = InOrder (PL ls)
type LF (n :: Nat) = InOrder (PL (Fn n))
type PullF (n :: Nat) = PullRet (Fn n)
-- | Recursively unwrap indexed or in-order wrapper tags into underlying modes.
type family UnwrapMode (op :: Type) :: Type where
UnwrapMode (_ # mode) = UnwrapMode mode
UnwrapMode (InOrder mode) = UnwrapMode mode
UnwrapMode stackOp = stackOp
-- | Map 'UnwrapMode' across a list of operation tags.
type family OpModes (ops :: [Type]) :: [Type] where
OpModes '[] = '[]
OpModes (op ': ops) = UnwrapMode op ': OpModes ops
-- | Detect whether any operation in the list specifies an explicit numerical index.
type family HasExplicitIndex (ops :: [Type]) :: Bool where
HasExplicitIndex '[] = 'False
HasExplicitIndex ((_ # _) ': _) = 'True
HasExplicitIndex (_ ': ops) = HasExplicitIndex ops
-- | Extract the list of indices from an operation list if explicitly indexed.
type family OpIndices (ops :: [Type]) :: [Peano] where
OpIndices ops = OpIndicesIf (HasExplicitIndex ops) ops
-- | Helper family generating the index list depending on explicit tagging.
type family OpIndicesIf (indexed :: Bool) (ops :: [Type]) :: [Peano] where
OpIndicesIf 'False _ = '[]
OpIndicesIf 'True '[] = '[]
OpIndicesIf 'True ((i # _) ': ops) = ToPeano i ': OpIndicesIf 'True ops
OpIndicesIf 'True (InOrder _ ': ops) = 'Z ': OpIndicesIf 'True ops
OpIndicesIf 'True (_ ': ops) = OpIndicesIf 'True ops
-- | Take @d@ elements from a type-level list.
type family TakePeano (d :: Peano) (xs :: [k]) :: [k] where
TakePeano 'Z _ = '[]
TakePeano ('S d) (x ': xs) = x ': TakePeano d xs
-- | Index into a type-level list at Peano position @d@.
type family AtPeano (d :: Peano) (xs :: [k]) :: k where
AtPeano 'Z (x ': _) = x
AtPeano ('S d) (_ ': xs) = AtPeano d xs
-- | Swap adjacent elements in a type-level list at depth @d@.
type family SwapAtPeano (d :: Peano) (xs :: [k]) :: [k] where
SwapAtPeano 'Z (x ': y ': xs) = y ': x ': xs
SwapAtPeano ('S d) (x ': xs) = x ': SwapAtPeano d xs
-- Core StepRole Instances:
instance (ExpectArrow "StepRole PP" acc a rTail, StepPathMask base '[] ms m t3 rTail out')
=> StepRole PP base '[] ms m t3 acc (a -> out') where
stepRole f acc a = stepPathMask @base @'[] @ms f (acc a)
{-# INLINE stepRole #-}
instance forall base l ls ms m t3 acc acc' out' a rTail.
( MapPath (l ': ls) (a -> rTail) rTail acc acc'
, StepPathMask base (l ': ls) ms m t3 acc' out'
) => StepRole PP base (l ': ls) ms m t3 acc (a -> out') where
stepRole f acc a =
stepPathMask @base @(l ': ls) @ms @m @t3 @acc' @out'
f
(mapPath @(l ': ls) @(a -> rTail) @rTail @acc @acc' ($ a) acc)
{-# INLINE stepRole #-}
instance (ExpectArrow "StepRole PZ" acc a rTail, MapPath base a rTail fa acc', StepPathMask base base ms m t3 acc' out')
=> StepRole PZ base '[] ms m t3 acc (fa -> out') where
stepRole f acc fa =
stepPathMask @base @base @ms @m @t3 @acc' @out' f (mapPath @base acc fa)
{-# INLINE stepRole #-}
instance (ApPath (l ': ls) acc fa acc', StepPathMask base (l ': ls) ms m t3 acc' out')
=> StepRole PZ base (l ': ls) ms m t3 acc (fa -> out') where
stepRole f acc fa =
stepPathMask @base @(l ': ls) @ms @m @t3 @acc' @out' f (apPath @(l ': ls) acc fa)
{-# INLINE stepRole #-}
instance forall base active sub ms m t3 acc acc' out' a rTail fa inner.
( MapPath sub a rTail fa inner
, MapPath active (a -> rTail) inner acc acc'
, StepPathMask base (Append active sub) ms m t3 acc' out'
) => StepRole (PL sub) base active ms m t3 acc (fa -> out') where
stepRole f acc fa =
stepPathMask @base @(Append active sub) @ms @m @t3 @acc' @out' f
(mapPath @active @(a -> rTail) @inner @acc @acc' (\k -> mapPath @sub @a @rTail @fa @inner k fa) acc)
{-# INLINE stepRole #-}
instance (Monad φ, ExpectArrow "StepRole PM" acc a rTail, fa ~ φ a, StepPathMask base '[ F ] ms m t3 (φ rTail) out')
=> StepRole PM base '[] ms m t3 acc (fa -> out') where
stepRole f acc fa =
stepPathMask @base @'[ F ] @ms f (fmap acc fa)
{-# INLINE stepRole #-}
instance forall base l ls ms m t3 acc acc' out' φ a rTail fa.
( Monad φ, fa ~ φ a
, MapPath (Init (l ': ls)) (φ (a -> rTail)) (φ rTail) acc acc'
, StepPathMask base (l ': ls) ms m t3 acc' out'
) => StepRole PM base (l ': ls) ms m t3 acc (fa -> out') where
stepRole f acc fa =
stepPathMask @base @(l ': ls) @ms @m @t3 @acc' @out' f
(mapPath @(Init (l ': ls)) @(φ (a -> rTail)) @(φ rTail) (\mf -> mf >>= (<$> fa)) acc)
{-# INLINE stepRole #-}
instance forall base active ms m t3 acc acc' out φ inner.
( Monad φ
, MapPath (Init active) (φ (φ inner)) (φ inner) acc acc'
, StepPathMask base active ms m t3 acc' out
) => StepRole J base active ms m t3 acc out where
stepRole f acc =
stepPathMask @base @active @ms @m @t3 @acc' @out f
(mapPath @(Init active) @(φ (φ inner)) @(φ inner) join acc)
{-# INLINE stepRole #-}
instance forall base active ms m t3 acc acc' out φ inner.
( Monad φ
, MapPath (Init (Init active)) (φ (φ inner)) (φ inner) acc acc'
, StepPathMask base (Init active) ms m t3 acc' out
) => StepRole CS base active ms m t3 acc out where
stepRole f acc =
stepPathMask @base @(Init active) @ms @m @t3 @acc' @out f
(mapPath @(Init (Init active)) @(φ (φ inner)) @(φ inner) join acc)
{-# INLINE stepRole #-}
instance forall (d :: Nat) base active ms m t3 acc acc' out sMid tMid dp.
( dp ~ ToPeano d
, CommutePair (AtPeano dp active) (AtPeano ('S dp) active) sMid tMid
, MapPath (TakePeano dp active) sMid tMid acc acc'
, StepPathMask base (SwapAtPeano dp active) ms m t3 acc' out
) => StepRole (SwapAt d) base active ms m t3 acc out where
stepRole f acc =
stepPathMask @base @(SwapAtPeano dp active) @ms @m @t3 @acc' @out f
(mapPath @(TakePeano dp active) @sMid @tMid
(commutePair @(AtPeano dp active) @(AtPeano ('S dp) active) @sMid @tMid) acc)
{-# INLINE stepRole #-}
instance forall (retPath :: [Type]) base active ms m t3 acc acc' out.
( PullPath active retPath acc acc'
, StepPathMask base (Append retPath active) ms m t3 acc' out
) => StepRole (PullRet retPath) base active ms m t3 acc out where
stepRole f acc =
stepPathMask @base @(Append retPath active) @ms @m @t3 @acc' @out f
(pullPath @active @retPath @acc @acc' acc)
{-# INLINE stepRole #-}
-- ---------------------------------------------------------------------------
-- 6. Visible-Forall & Infix Entry Points
-- ---------------------------------------------------------------------------
-- | Fully general visible-forall composition engine parameterized by lifting path, base, and op tape.
composeUnifiedV ::
forall (lpath :: [Type]) ->
forall (base :: [Type]) ->
forall (ops :: [Type]) ->
forall a t3 m m' g gPerm out.
( Permute (OpIndices ops) g gPerm
, MapPath lpath a t3 m m'
, StepPathMask base '[] (OpModes ops) m m' gPerm out
) =>
(a -> t3) -> g -> out
composeUnifiedV (type lpath) (type base) (type ops) @a @t3 @m @m' @g @gPerm @out f g =
stepPathMask @base @'[] @(OpModes ops) @m @m' @gPerm @out
(mapPath @lpath @a @t3 @m @m' f)
(permute' @(OpIndices ops) @g @gPerm g)
{-# INLINE composeUnifiedV #-}
-- | Execute an outer function over an inner function with an open operation tape.
withOps ::
forall (ops :: [Type]) ->
forall m t3 g gPerm out.
( Permute (OpIndices ops) g gPerm
, StepPathMask '[] '[] (OpModes ops) m t3 gPerm out
) =>
(m -> t3) -> g -> out
withOps (type ops) @m @t3 @g @gPerm @out f g =
stepPathMask @'[] @'[] @(OpModes ops) @m @t3 @gPerm @out
f
(permute' @(OpIndices ops) @g @gPerm g)
{-# INLINE withOps #-}
-- | Infix tape-driven composition operator.
--
-- Example:
--
-- >>> (show .@ [P, P]) (+) 10 20
-- "30"
infixr 9 .@
(.@) ::
forall m t3 g gPerm out.
(m -> t3) ->
forall (ops :: [Type]) ->
( Permute (OpIndices ops) g gPerm
, StepPathMask '[] '[] (OpModes ops) m t3 gPerm out
) =>
g -> out
((.@) f (type ops)) g = withOps ops f g
{-# INLINE (.@) #-}
-- ---------------------------------------------------------------------------
-- 7. Backward-Compatible Variadic Arity Composition
-- ---------------------------------------------------------------------------
-- | Inductive arity composition class parameterized over Peano depth @n@.
--
-- See Note [Variadic Composition Bidirectional Fundeps] [ref:note-variadic-composition-fundeps].
class ComposeN (n :: Peano) b c g f | n b c g -> f, n f -> c, n f b -> g where
-- | Internal composition worker operating on inductive 'Peano' terms.
composeN' :: (b -> c) -> g -> f
instance ComposeN 'Z b c b c where
composeN' f x = f x
{-# INLINE composeN' #-}
instance ComposeN n b c r r' => ComposeN ('S n) b c (a -> r) (a -> r') where
composeN' f g a = composeN' @n f (g a)
{-# INLINE composeN' #-}
-- | Compose a unary function @(b -> c)@ with a curried function consuming @n@ arguments.
--
-- Examples:
--
-- @
-- compN @1 == (.)
-- compN @2 == (.:)
-- compN @3 == (..:)
-- @
compN :: forall (n :: Nat) b c g f. ComposeN (ToPeano n) b c g f => (b -> c) -> g -> f
compN = composeN' @(ToPeano n)
{-# INLINE compN #-}
-- | Inductive nested functor mapping class parameterized over nesting depth @n@.
class FmapN (n :: Peano) a b s t | n a b s -> t, n s t -> a b where
-- | Internal functor mapping worker operating on inductive 'Peano' terms.
fmapN' :: (a -> b) -> s -> t
instance FmapN 'Z a b a b where
fmapN' f = f
{-# INLINE fmapN' #-}
instance (Functor f, FmapN n a b s t) => FmapN ('S n) a b (f s) (f t) where
fmapN' f = fmap (fmapN' @n f)
{-# INLINE fmapN' #-}
-- | Map a transformation @(a -> b)@ through @n@ nested 'Functor' layers.
fmapN :: forall (n :: Nat) a b s t. FmapN (ToPeano n) a b s t => (a -> b) -> s -> t
fmapN = fmapN' @(ToPeano n)
{-# INLINE fmapN #-}