penelope-effects-0.1.0.0: src/Penelope/Effects/Binary.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{- |
Module : Penelope.Effects.Binary
Description : Kernmantle binary arrow facade over Penelope (m,n,k,r)-ary core
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 a binary arrow interface ('Rope') and static jump-table routing ('deriveRouter')
over Penelope's polycategorical core.
See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
module Penelope.Effects.Binary
( -- * Binary Arrow Facade
Rope (..)
-- * Jump-Table Router
, deriveRouter
-- * Runners
, runRopePure
, runRopeM
) where
import Control.Arrow (Arrow (..), ArrowChoice (..))
import Control.Category (Category (..), (>>>))
import Data.Functor.Identity (Identity (..))
import Data.Primitive.SmallArray (indexSmallArray)
import Data.Profunctor (Choice (..), Profunctor (..), Strong (..))
import GHC.Exts (Any, unsafeCoerce#)
import Penelope.Core.Engine.Elaborate (MapRow)
import Penelope.Core.Engine.Interpret
( FrameKleisli (..)
, Handler (..)
, interpretAll
, runPenelopePure
)
import Penelope.Core.Syntax
( BranchCont (..)
, Penelope (..)
, PolyEffect
, PolyHEffect
)
import Penelope.Core.Wiring (seqPenelope)
import Polytropos (Field (..), Record, RowOf, Variant, emptyRecord, insertField)
import Polytropos.Core (Pair (..), Rep (..), RowRep (..), SumRow)
import Polytropos.RowMorph (unsafeMkRowMorph)
import Prelude hiding (id, (.))
{- |
Zero-cost binary arrow projection over Penelope's underlying polycategorical engine.
A computation of type @Rope eh ef a b@ is isomorphic to a pipeline with a single input register
@'["in" ':= a]@ and a single output register @'["out" ':= b]@.
See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
newtype Rope (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect) a b = Rope
{ unRope :: Penelope eh ef '["in" ':= a] '["out" ':= b]
}
instance Category (Rope eh ef) where
{-# INLINE id #-}
id = Rope (Route (unsafeMkRowMorph [0]))
{-# INLINE (.) #-}
Rope g . Rope f =
let rename = Route (unsafeMkRowMorph [0])
in Rope (seqPenelope f (seqPenelope rename g))
instance Arrow (Rope eh ef) where
{-# INLINE arr #-}
arr f = Rope $
LiftPure (unsafeMkRowMorph [0])
(\rec ->
let !(Identity aVal) = indexSmallArray (let RowRep (Rep _ sArr) = rec in sArr) 0
!b = f (unsafeCoerce# aVal)
in insertField (Field @"out") b emptyRecord
)
(BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
{-# INLINE first #-}
first = arrowFirst
{-# INLINE second #-}
second = arrowSecond
instance ArrowChoice (Rope eh ef) where
{-# INLINE left #-}
left = arrowLeft
{-# INLINE right #-}
right = arrowRight
instance Profunctor (Rope eh ef) where
{-# INLINE dimap #-}
dimap f g r = arr f >>> r >>> arr g
{-# INLINE lmap #-}
lmap f r = arr f >>> r
{-# INLINE rmap #-}
rmap g r = r >>> arr g
instance Strong (Rope eh ef) where
{-# INLINE first' #-}
first' = first
{-# INLINE second' #-}
second' = second
instance Choice (Rope eh ef) where
{-# INLINE left' #-}
left' = left
{-# INLINE right' #-}
right' = right
{-# INLINE arrowFirst #-}
arrowFirst ::
forall eh ef b c d.
Rope eh ef b c
-> Rope eh ef (b, d) (c, d)
arrowFirst (Rope p) = Rope $
LiftPure (unsafeMkRowMorph [0]) id
(BranchCont $ \rec ->
let !(Identity pairVal) = indexSmallArray (let RowRep (Rep _ sArr) = rec in sArr) 0
!(b, d) = unsafeCoerce# pairVal :: (b, d)
!bIn = insertField (Field @"in") b emptyRecord
stageOut :: Penelope eh ef '["out" ':= c] '["out" ':= (c, d)]
stageOut = LiftPure (unsafeMkRowMorph [0])
(\cRec ->
let !(Identity cVal) = indexSmallArray (let RowRep (Rep _ sArr) = cRec in sArr) 0
!c = unsafeCoerce# cVal :: c
in insertField (Field @"out") (c, d) emptyRecord
)
(BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
in LiftPure (unsafeMkRowMorph []) (\_ -> bIn)
(BranchCont (\_ -> unsafeCoerce# (seqPenelope p stageOut)))
)
{-# INLINE arrowSecond #-}
arrowSecond ::
forall eh ef b c d.
Rope eh ef b c
-> Rope eh ef (d, b) (d, c)
arrowSecond (Rope p) = Rope $
LiftPure (unsafeMkRowMorph [0]) id
(BranchCont $ \rec ->
let !(Identity pairVal) = indexSmallArray (let RowRep (Rep _ sArr) = rec in sArr) 0
!(d, b) = unsafeCoerce# pairVal :: (d, b)
!bIn = insertField (Field @"in") b emptyRecord
stageOut :: Penelope eh ef '["out" ':= c] '["out" ':= (d, c)]
stageOut = LiftPure (unsafeMkRowMorph [0])
(\cRec ->
let !(Identity cVal) = indexSmallArray (let RowRep (Rep _ sArr) = cRec in sArr) 0
!c = unsafeCoerce# cVal :: c
in insertField (Field @"out") (d, c) emptyRecord
)
(BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
in LiftPure (unsafeMkRowMorph []) (\_ -> bIn)
(BranchCont (\_ -> unsafeCoerce# (seqPenelope p stageOut)))
)
{-# INLINE arrowLeft #-}
arrowLeft ::
forall eh ef b c d.
Rope eh ef b c
-> Rope eh ef (Either b d) (Either c d)
arrowLeft (Rope p) = Rope $
LiftPure (unsafeMkRowMorph [0]) id
(BranchCont $ \rec ->
let !(Identity eitherVal) = indexSmallArray (let RowRep (Rep _ sArr) = rec in sArr) 0
in case (unsafeCoerce# eitherVal :: Either b d) of
Left b ->
let !bIn = insertField (Field @"in") b emptyRecord
stageOut :: Penelope eh ef '["out" ':= c] '["out" ':= Either c d]
stageOut = LiftPure (unsafeMkRowMorph [0])
(\cRec ->
let !(Identity cVal) = indexSmallArray (let RowRep (Rep _ sArr) = cRec in sArr) 0
!c = unsafeCoerce# cVal :: c
in insertField (Field @"out") (Left c :: Either c d) emptyRecord
)
(BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
in LiftPure (unsafeMkRowMorph []) (\_ -> bIn)
(BranchCont (\_ -> unsafeCoerce# (seqPenelope p stageOut)))
Right d ->
LiftPure (unsafeMkRowMorph [])
(\_ -> insertField (Field @"out") (Right d :: Either c d) emptyRecord)
(BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
)
{-# INLINE arrowRight #-}
arrowRight ::
forall eh ef b c d.
Rope eh ef b c
-> Rope eh ef (Either d b) (Either d c)
arrowRight (Rope p) = Rope $
LiftPure (unsafeMkRowMorph [0]) id
(BranchCont $ \rec ->
let !(Identity eitherVal) = indexSmallArray (let RowRep (Rep _ sArr) = rec in sArr) 0
in case (unsafeCoerce# eitherVal :: Either d b) of
Right b ->
let !bIn = insertField (Field @"in") b emptyRecord
stageOut :: Penelope eh ef '["out" ':= c] '["out" ':= Either d c]
stageOut = LiftPure (unsafeMkRowMorph [0])
(\cRec ->
let !(Identity cVal) = indexSmallArray (let RowRep (Rep _ sArr) = cRec in sArr) 0
!c = unsafeCoerce# cVal :: c
in insertField (Field @"out") (Right c :: Either d c) emptyRecord
)
(BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
in LiftPure (unsafeMkRowMorph []) (\_ -> bIn)
(BranchCont (\_ -> unsafeCoerce# (seqPenelope p stageOut)))
Left d ->
LiftPure (unsafeMkRowMorph [])
(\_ -> insertField (Field @"out") (Left d :: Either d c) emptyRecord)
(BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
)
{- |
Static jump-table router: compiles a 'Record' of branch handlers into an \(O(1)\)
jump table over 'Polytropos.Variant', eliminating linear branching cascades and nested
coproduct shuffles (@Either (Either a b) c@).
See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
{-# INLINE deriveRouter #-}
deriveRouter ::
forall (s :: SumRow) (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect) b handlers.
Record handlers
-> Rope eh ef (Variant s) b
deriveRouter handlers = Rope $
LiftPure (unsafeMkRowMorph [0]) id
(BranchCont $ \rec ->
let !(Identity varVal) = indexSmallArray (let RowRep (Rep _ sArr) = rec in sArr) 0
!(RowRep (Rep tag payloadArr)) = unsafeCoerce# varVal
!(RowRep (Rep _ handlerArr)) = handlers
!(Identity handlerAny) = indexSmallArray handlerArr (fromIntegral tag)
!(Rope (pipe :: Penelope eh ef '["in" ':= Any] '["out" ':= b])) = unsafeCoerce# handlerAny
!payloadRecord = RowRep (Rep 0 payloadArr)
!payloadIn = insertField (Field @"in") (unsafeCoerce# payloadRecord) emptyRecord
in LiftPure (unsafeMkRowMorph []) (\_ -> payloadIn)
(BranchCont (\_ -> unsafeCoerce# pipe))
)
{- |
Execute a pure, closed 'Rope' arrow to its final output value.
See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
{-# INLINE runRopePure #-}
runRopePure :: Rope '[] '[] a b -> a -> b
runRopePure (Rope p) a =
let inFrame = insertField (Field @"in") a emptyRecord
outFrame = runPenelopePure p inFrame
!(Identity bVal) = indexSmallArray (let RowRep (Rep _ sArr) = outFrame in sArr) 0
in unsafeCoerce# bVal
{- |
Execute an effectful 'Rope' arrow into a target 'Monad' using a handler vector.
See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
{-# INLINE runRopeM #-}
runRopeM ::
forall ef m a b.
Monad m =>
Record (MapRow (Handler '[] (FrameKleisli m)) ef)
-> Rope '[] ef a b
-> a
-> m b
runRopeM handlers (Rope p) a = do
let inFrame = insertField (Field @"in") a emptyRecord
kleisli = interpretAll handlers p
outFrame <- runFrameKleisli kleisli inFrame
let !(Identity bVal) = indexSmallArray (let RowRep (Rep _ sArr) = outFrame in sArr) 0
pure (unsafeCoerce# bVal)