penelope-core-0.1.0.0: src/Penelope/Core/Wiring.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UnboxedTuples #-}
{- |
Module : Penelope.Core.Wiring
Description : Polycategorical wiring combinators, sequential composition, frame-rule auto-wiring, and row weakening
Copyright : (c) 2026 The Penelope Authors
License : EUPL-1.2 OR Apache-2.0
Maintainer : nadia.yvette.chambers@ik.me
Stability : experimental
Portability : non-portable (GHC extensions)
Provides polycategorical composition primitives for the Penelope effect system.
See Note [Core Architecture] [ref:note-core-architecture],
Note [Unpacked Polycategorical Representation] [ref:note-unpacked-representation],
Note [Reified Row Morphisms and Gather-Scatter] [ref:note-row-morph-routing],
Note [Frame-Rule Automatic Wire Synthesis] [ref:note-frame-rule-wiring],
and Note [Institutional Satisfaction and Zero-Traversal Weakening] [ref:note-satisfaction-weakening].
-}
module Penelope.Core.Wiring
( -- * Sequential Composition
seqPenelope
, (>>>)
-- * Frame-Rule Automatic Port Wiring
, (>&>)
, liftFrame
-- * Disjunctive Multi-Exit Fanout
, (>?<)
-- * Zero-Traversal Row Weakening
, weakenRow
, weakenRowExplicit
, weakenEF
, weakenEH
-- * Route Pre-Composition & Peephole Fusion
, preComposeRoute
-- * Coyoneda Leaf Continuation Record Mapping
, rmapFused
) where
import Data.Functor.Identity (Identity (..))
import Data.Kind (Type)
import Data.Primitive.PrimArray (sizeofPrimArray)
import Data.Primitive.SmallArray
( indexSmallArray
, newSmallArray
, runSmallArray
, sizeofSmallArray
, writeSmallArray
)
import GHC.Exts (Int (..), int2Word#, unsafeCoerce#, word2Int#)
import Penelope.Core.Route (fuseRoute, isIdentityRowMorph)
import Penelope.Core.Syntax
( BranchCont (..)
, Penelope (..)
, PolyEffect
, PolyHEffect
, Region (..)
)
import Polytropos (Record, RowOf)
import Polytropos.Core (Rep (..), RowRep (..))
import Polytropos.RowMorph
( FramePlan (..)
, FrameWire (..)
, KnownRowMorph (..)
, RowMorph (..)
, composeRowMorph
, executeFrameScatter
, gatherRep
, indexRowMorph
, unsafeMkRowMorph
)
import Unsafe.Coerce (unsafeCoerce)
{- |
Map a continuation or region transformation across all slots of an open record in a
single flat SmallArray pass without intermediate container allocations.
See Note [Unpacked Polycategorical Representation] [ref:note-unpacked-representation].
-}
rmapFused :: (forall x. a x -> b x) -> Record r1 -> Record r2
rmapFused f (RowRep (Rep tag arr)) =
let !sz = sizeofSmallArray arr
!arr' = runSmallArray $ do
m <- newSmallArray sz (Identity (unsafeCoerce# ()))
let loop !i
| i < sz = do
let Identity !anyVal = indexSmallArray arr i
let !res = f (unsafeCoerce anyVal)
writeSmallArray m i (Identity (unsafeCoerce res))
loop (i + 1)
| otherwise = pure ()
loop 0
pure m
in RowRep (Rep tag arr')
{- |
Pre-compose a static 'RowMorph' wire routing stage before a downstream 'Penelope' pipeline.
Peephole-fuses into 'fuseRoute' if the downstream stage is also a 'Route', elides identity
morphisms in O(1) time, or distributes into continuation gather maps.
See Note [Reified Row Morphisms and Gather-Scatter] [ref:note-row-morph-routing].
-}
preComposeRoute ::
forall mid ins (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect) (outs :: RowOf Type).
RowMorph mid ins
-> Penelope eh ef mid outs
-> Penelope eh ef ins outs
preComposeRoute m1 (Route m2) = Route (fuseRoute m1 m2)
preComposeRoute m1 next
| isIdentityRowMorph m1 = unsafeCoerce next
preComposeRoute m1 (LiftPure inMap f cont) =
LiftPure
(fuseRoute m1 inMap)
f
(BranchCont (\res -> preComposeRoute m1 (runBranchCont cont res)))
preComposeRoute m1 (OpF tag# inMap payload conts) =
OpF
tag#
(fuseRoute m1 inMap)
payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> preComposeRoute m1 (k res))) conts)
preComposeRoute m1 (OpH tag# inMap regions payload conts) =
OpH
tag#
(fuseRoute m1 inMap)
regions
payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> preComposeRoute m1 (k res))) conts)
preComposeRoute m1 (ScopeRow ehM efM inner) =
ScopeRow ehM efM (preComposeRoute m1 inner)
{- |
Sequential polycategorical composition of Penelope pipelines.
Substitutes downstream pipelines into continuation leaves with Coyoneda fusion.
See Note [Unpacked Polycategorical Representation] [ref:note-unpacked-representation].
-}
seqPenelope ::
forall (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect) (a :: RowOf Type) (b :: RowOf Type) (c :: RowOf Type).
Penelope eh ef a b
-> Penelope eh ef b c
-> Penelope eh ef a c
seqPenelope (Route m) next = preComposeRoute m next
seqPenelope (LiftPure inMap f (BranchCont k)) next =
LiftPure inMap f (BranchCont (\res -> seqPenelope (k res) next))
seqPenelope (OpF tag# inMap payload conts) next =
OpF tag# inMap payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> seqPenelope (k res) next)) conts)
seqPenelope (OpH tag# inMap regions payload conts) next =
OpH tag# inMap regions payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> seqPenelope (k res) next)) conts)
seqPenelope (ScopeRow ehM efM inner) next =
seqScopeRow ehM efM inner next
-- | Infix sequential composition operator.
{-# INLINE (>>>) #-}
infixr 1 >>>
(>>>) ::
forall (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect) (a :: RowOf Type) (b :: RowOf Type) (c :: RowOf Type).
Penelope eh ef a b
-> Penelope eh ef b c
-> Penelope eh ef a c
(>>>) = seqPenelope
{- |
Lifts a local @(opIn -> opOut)@ computation into a wider @(frameIn -> frameOut)@
register frame by composing 'fpGatherIn' at the root and 'executeFrameScatter' at the leaves.
See Note [Frame-Rule Automatic Wire Synthesis] [ref:note-frame-rule-wiring].
-}
liftFrame ::
forall (frameIn :: RowOf Type) (opIn :: RowOf Type) (opOut :: RowOf Type) (frameOut :: RowOf Type)
(eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect).
FramePlan frameIn opIn opOut frameOut
-> Penelope eh ef opIn opOut
-> Penelope eh ef frameIn frameOut
liftFrame plan = go
where
!baseSz = sizeofPrimArray (unRowMorph (fpBaseScatter plan))
!outSz = fpOutSize plan
liftCont ::
forall branchOuts.
BranchCont (Penelope eh ef opIn opOut) branchOuts
-> BranchCont (Penelope eh ef frameIn frameOut) branchOuts
liftCont (BranchCont k) = BranchCont (\branchRec -> go (k branchRec))
go :: Penelope eh ef opIn opOut -> Penelope eh ef frameIn frameOut
go (Route m) =
LiftPure
(fpGatherIn plan)
(\(RowRep inRep) -> RowRep (gatherRep m inRep))
(BranchCont $ \(RowRep outRep) ->
LiftPure
(unsafeMkRowMorph [0 .. baseSz - 1])
(\(RowRep frameRep) -> RowRep (executeFrameScatter plan frameRep outRep))
(BranchCont $ \_ -> Route (unsafeMkRowMorph [0 .. outSz - 1])))
go (LiftPure inMap f cont) =
LiftPure (composeRowMorph inMap (fpGatherIn plan)) f (liftCont cont)
go (OpF tag# inMap payload conts) =
OpF tag# (composeRowMorph inMap (fpGatherIn plan)) payload (rmapFused liftCont conts)
go (OpH tag# inMap regions payload conts) =
OpH tag# (composeRowMorph inMap (fpGatherIn plan)) regions payload (rmapFused liftCont conts)
go (ScopeRow ehM efM inner) =
ScopeRow ehM efM (liftFrame plan inner)
{- |
Frame-Rule Auto-Wiring Composition ('(>&>)'):
Automatically gathers each stage's required input ports from the live register frame
and scatters its outputs back into the frame, preserving untouched context ports.
See Note [Frame-Rule Automatic Wire Synthesis] [ref:note-frame-rule-wiring].
-}
{-# INLINE (>&>) #-}
infixr 1 >&>
(>&>) ::
forall (frameIn :: RowOf Type) (ins1 :: RowOf Type) (outs1 :: RowOf Type)
(frameMid :: RowOf Type) (ins2 :: RowOf Type) (outs2 :: RowOf Type)
(frameOut :: RowOf Type)
(eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect).
( FrameWire frameIn ins1 outs1 frameMid
, FrameWire frameMid ins2 outs2 frameOut
) =>
Penelope eh ef ins1 outs1
-> Penelope eh ef ins2 outs2
-> Penelope eh ef frameIn frameOut
stage1 >&> stage2 =
let plan1 = framePlan @frameIn @ins1 @outs1 @frameMid
plan2 = framePlan @frameMid @ins2 @outs2 @frameOut
in liftFrame plan1 stage1 `seqPenelope` liftFrame plan2 stage2
{- |
Disjunctive multi-exit fanout combinator.
Binds an operation's exit channels to a 'Record' of k downstream branch continuations
in O(1) time without nested binary coproducts ('Either').
See Note [Core Architecture] [ref:note-core-architecture]
and Note [Unpacked Polycategorical Representation] [ref:note-unpacked-representation].
-}
{-# INLINE (>?<) #-}
infixr 1 >?<
(>?<) ::
forall (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect)
(ins :: RowOf Type) (mid :: RowOf Type) (outs :: RowOf Type)
handlers.
Penelope eh ef ins mid
-> Record handlers
-> Penelope eh ef ins outs
(>?<) (OpF tag# inMap payload conts) handlers =
let RowRep (Rep _ arr) = conts
in if sizeofSmallArray arr == 0
then OpF tag# inMap payload (unsafeCoerce handlers)
else OpF tag# inMap payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> k res >?< handlers)) conts)
(>?<) (OpH tag# inMap regions payload conts) handlers =
let RowRep (Rep _ arr) = conts
in if sizeofSmallArray arr == 0
then OpH tag# inMap regions payload (unsafeCoerce handlers)
else OpH tag# inMap regions payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> k res >?< handlers)) conts)
(>?<) (LiftPure inMap f cont) handlers =
LiftPure inMap f (BranchCont (\res -> runBranchCont cont res >?< handlers))
(>?<) (ScopeRow ehM efM inner) handlers =
ScopeRow ehM efM (inner >?< handlers)
(>?<) (Route m) _ =
Route (unsafeCoerce m)
{- |
Embed a computation into wider higher-order and first-order effect rows using compile-time
synthesized 'KnownRowMorph' instances.
Wraps the computation in 'ScopeRow' in O(1) time with zero syntax traversal.
See Note [Institutional Satisfaction and Zero-Traversal Weakening] [ref:note-satisfaction-weakening].
-}
weakenRow ::
forall {ehSub :: RowOf PolyHEffect} (ehFull :: RowOf PolyHEffect)
{efSub :: RowOf PolyEffect} (efFull :: RowOf PolyEffect)
(ins :: RowOf Type) (outs :: RowOf Type).
(KnownRowMorph ehSub ehFull, KnownRowMorph efSub efFull) =>
Penelope ehSub efSub ins outs
-> Penelope ehFull efFull ins outs
weakenRow = ScopeRow (rowMorph @ehSub @ehFull) (rowMorph @efSub @efFull)
{- |
Embed a computation into wider effect rows using explicit 'RowMorph' mappings.
See Note [Institutional Satisfaction and Zero-Traversal Weakening] [ref:note-satisfaction-weakening].
-}
weakenRowExplicit ::
forall (ehSub :: RowOf PolyHEffect) (ehFull :: RowOf PolyHEffect)
(efSub :: RowOf PolyEffect) (efFull :: RowOf PolyEffect)
(ins :: RowOf Type) (outs :: RowOf Type).
RowMorph ehSub ehFull
-> RowMorph efSub efFull
-> Penelope ehSub efSub ins outs
-> Penelope ehFull efFull ins outs
weakenRowExplicit = ScopeRow
{- |
Weaken first-order effect row while preserving higher-order effect row.
See Note [Institutional Satisfaction and Zero-Traversal Weakening] [ref:note-satisfaction-weakening].
-}
weakenEF ::
forall {efSub :: RowOf PolyEffect} (efFull :: RowOf PolyEffect)
{eh :: RowOf PolyHEffect}
(ins :: RowOf Type) (outs :: RowOf Type).
(KnownRowMorph eh eh, KnownRowMorph efSub efFull) =>
Penelope eh efSub ins outs
-> Penelope eh efFull ins outs
weakenEF = ScopeRow (rowMorph @eh @eh) (rowMorph @efSub @efFull)
{- |
Weaken higher-order effect row while preserving first-order effect row.
See Note [Institutional Satisfaction and Zero-Traversal Weakening] [ref:note-satisfaction-weakening].
-}
weakenEH ::
forall {ehSub :: RowOf PolyHEffect} (ehFull :: RowOf PolyHEffect)
{ef :: RowOf PolyEffect}
(ins :: RowOf Type) (outs :: RowOf Type).
(KnownRowMorph ehSub ehFull, KnownRowMorph ef ef) =>
Penelope ehSub ef ins outs
-> Penelope ehFull ef ins outs
weakenEH = ScopeRow (rowMorph @ehSub @ehFull) (rowMorph @ef @ef)
{- |
Helper to sequence through regional 'ScopeRow' boundaries to downstream continuations.
-}
seqScopeRow ::
forall (ehSub :: RowOf PolyHEffect) (eh :: RowOf PolyHEffect)
(efSub :: RowOf PolyEffect) (ef :: RowOf PolyEffect)
(a :: RowOf Type) (b :: RowOf Type) (c :: RowOf Type).
RowMorph ehSub eh
-> RowMorph efSub ef
-> Penelope ehSub efSub a b
-> Penelope eh ef b c
-> Penelope eh ef a c
seqScopeRow ehM efM p next = case p of
Route m -> preComposeRoute m next
LiftPure inMap f (BranchCont k) ->
LiftPure inMap f (BranchCont (\res -> seqScopeRow ehM efM (k res) next))
OpF tag# inMap payload conts ->
let !(I# fullIdx#) = indexRowMorph efM (I# (word2Int# tag#))
!fullTag# = int2Word# fullIdx#
in OpF fullTag# inMap payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> seqScopeRow ehM efM (k res) next)) conts)
OpH tag# inMap regions payload conts ->
let !(I# fullIdx#) = indexRowMorph ehM (I# (word2Int# tag#))
!fullTag# = int2Word# fullIdx#
!regions' = rmapFused (\(Region reg) -> Region (ScopeRow ehM efM reg)) regions
in OpH fullTag# inMap regions' payload
(rmapFused (\(BranchCont k) -> BranchCont (\res -> seqScopeRow ehM efM (k res) next)) conts)
ScopeRow ehM2 efM2 inner ->
seqScopeRow (composeRowMorph ehM2 ehM) (composeRowMorph efM2 efM) inner next