packages feed

penelope-core-0.1.0.0: src/Penelope/Core/Engine/Interpret.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

{- |
Module      : Penelope.Core.Engine.Interpret
Description : Phase 2 bulk vector-dispatched algebraic interpretation
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 Phase 2 bulk vector-dispatched algebraic interpretation over first-order operations.

See Note [Two-Phase Weaving and Unweaving] [ref:note-two-phase-engine] and
Note [Institutional Satisfaction and Zero-Traversal Weakening] [ref:note-satisfaction-weakening].
-}
module Penelope.Core.Engine.Interpret
  ( -- * Carrier Abstraction
    PolyCarrier (..)
  , FrameKleisli (..)

    -- * Handler Abstraction
  , Handler (..)
  , Handlers

    -- * Phase 2 Interpretation Engine
  , interpretAll
  , runPenelopePure
  ) where

import Control.Category (Category (..))
import Data.Functor.Identity (Identity (..))
import Data.Kind (Type)
import Data.Primitive.SmallArray (indexSmallArray)
import GHC.Exts (Any, Int (..), unsafeCoerce#, word2Int#)
import Penelope.Core.Engine.Elaborate (MapRow)
import Penelope.Core.Syntax
  ( BranchCont (..)
  , Penelope (..)
  , PolyEffect
  )
import Penelope.Core.Wiring (rmapFused)
import Polytropos (Record, RowOf, emptyRecord, gatherRecord)
import Polytropos.Core (Rep (..), RowRep (..))
import Polytropos.RowMorph (RowMorph, gatherRep)
import Prelude hiding (id, (.))

{- |
Typeclass for target semantic carriers \(m :: RowOf Type -> RowOf Type -> Type\)
that can execute compiled first-order 'Penelope' graphs.

See Note [Two-Phase Weaving and Unweaving] [ref:note-two-phase-engine].
-}
class Category m => PolyCarrier m where
  -- | Execute a pure static 'RowMorph' port permutation or projection.
  evalRoute :: RowMorph outs ins -> m ins outs

  -- | Execute a pure step over a gathered subset of ports and feed outputs to continuation.
  evalLiftPure ::
    RowMorph opIns ins
    -> (Record opIns -> Record opOuts)
    -> (Record opOuts -> m ins outs)
    -> m ins outs

{- |
Standard in-memory frame execution carrier: a Kleisli arrow over flat 'Record' frames.

See Note [Two-Phase Weaving and Unweaving] [ref:note-two-phase-engine].
-}
newtype FrameKleisli (f :: Type -> Type) (ins :: RowOf Type) (outs :: RowOf Type) = FrameKleisli
  { runFrameKleisli :: Record ins -> f (Record outs)
  }

instance Monad f => Category (FrameKleisli f) where
  id = FrameKleisli pure
  FrameKleisli g . FrameKleisli h = FrameKleisli (\r -> h r >>= g)

instance Monad f => PolyCarrier (FrameKleisli f) where
  {-# INLINE evalRoute #-}
  evalRoute morph = FrameKleisli $ \(RowRep rep) ->
    pure (RowRep (gatherRep morph rep))

  {-# INLINE evalLiftPure #-}
  evalLiftPure inMap f k = FrameKleisli $ \(RowRep inRep) -> do
    let !opInFrame = RowRep (gatherRep inMap inRep)
        !opOutFrame = f opInFrame
    runFrameKleisli (k opOutFrame) (unsafeCoerce# opOutFrame)

{- |
A first-order \((m, n, k)\)-ary effect handler into target carrier @m@.
Receives:
1. 'RowMorph opIns frameIns': Static map to extract the operation's \(m\) input ports.
2. 'Any': Dynamic payload of the first-order operation.
3. 'Record exits': Downstream continuation branches already interpreted into carrier @m@.

See Note [Two-Phase Weaving and Unweaving] [ref:note-two-phase-engine].
-}
newtype Handler
          (ef :: RowOf PolyEffect)
          (m  :: RowOf Type -> RowOf Type -> Type)
          (e  :: PolyEffect)
  = Handler
  { runHandler
      :: forall (opIns :: RowOf Type) (exits :: RowOf Type)
                (frameIns :: RowOf Type) (frameOuts :: RowOf Type).
         RowMorph opIns frameIns
      -> Any
      -> Record exits
      -> m frameIns frameOuts
  }

-- | Convenient type alias for a record of first-order handlers.
type Handlers ef m = Record (MapRow (Handler '[] m) ef)

{- |
Phase 2 bulk vector-dispatched algebraic interpretation over first-order operations.
Dispatches each 'OpF' constructor tag via branchless \(O(1)\) 'indexSmallArray#' read
into the handler record, with zero linear search cascades.

Contravariant handler reduct gathering via 'gatherRecord' adapts outer handlers to inner
scopes across 'ScopeRow' boundaries in \(O(|ef_{sub}|)\) time without walking the inner sub-tree.

See Note [Two-Phase Weaving and Unweaving] [ref:note-two-phase-engine] and
Note [Institutional Satisfaction and Zero-Traversal Weakening] [ref:note-satisfaction-weakening].
-}
{-# INLINE interpretAll #-}
interpretAll ::
  forall (ef :: RowOf PolyEffect) m (ins :: RowOf Type) (outs :: RowOf Type).
  PolyCarrier m =>
  Record (MapRow (Handler '[] m) ef)
  -> Penelope '[] ef ins outs
  -> m ins outs
interpretAll !handlers = go
  where
    interpCont ::
      BranchCont (Penelope '[] ef i o) branchOuts
      -> BranchCont (m i o) branchOuts
    interpCont (BranchCont k) = BranchCont (\branchFrame -> go (k branchFrame))

    go :: Penelope '[] ef i o -> m i o
    go = \case
      Route morph ->
        evalRoute morph

      LiftPure inMap f (BranchCont k) ->
        evalLiftPure inMap f (\opOutFrame -> go (k opOutFrame))

      OpF tag# inMap opAny conts ->
        let !interpretedConts = rmapFused interpCont conts
            !(RowRep (Rep _ arr)) = handlers
            !(Identity handlerAny) = indexSmallArray arr (I# (word2Int# tag#))
            !(Handler handleFn) = unsafeCoerce# handlerAny
        in handleFn (unsafeCoerce# inMap) opAny interpretedConts

      ScopeRow _ehMorph efMorph subPipe ->
        let !subHandlers = unsafeCoerce# (gatherRecord (unsafeCoerce# efMorph) handlers)
        in interpretAll subHandlers (unsafeCoerce# subPipe)

      OpH {} ->
        errorWithoutStackTrace "Penelope.Core.Engine.Interpret: impossible OpH in empty '[] higher-order row"

{- |
Run a fully handled pure 'Penelope' pipeline over an initial register frame.

See Note [Two-Phase Weaving and Unweaving] [ref:note-two-phase-engine].
-}
{-# INLINE runPenelopePure #-}
runPenelopePure ::
  Penelope '[] '[] ins outs
  -> Record ins
  -> Record outs
runPenelopePure pipe inFrame =
  case runFrameKleisli (interpretAll emptyRecord pipe) inFrame of
    Identity outFrame -> outFrame