packages feed

penelope-effects-0.1.0.0: src/Penelope/Effects/Unary.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.Unary
Description : Heftia unary monad 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 standard unary monadic interface ('Eff') over Penelope's polycategorical core.

See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
module Penelope.Effects.Unary
  ( -- * Unary Monad Facade
    Eff (..)

    -- * Effect Injection
  , send
  , sendEff

    -- * Handlers and Lifting Adapters
  , UnaryEffect
  , EffHandler
  , EffHandlers
  , mkEffHandler

    -- * Runners
  , runEff
  , runEffM
  ) where

import Control.Monad (ap)
import Data.Functor.Identity (Identity (..))
import Data.Kind (Type)
import Data.Primitive.SmallArray (indexSmallArray)
import GHC.Exts (Any, Word (W#), unsafeCoerce#)
import GHC.TypeLits (Symbol)
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 (preComposeRoute, seqPenelope)
import Polytropos (Field (..), Record, RowOf, emptyRecord, insertField)
import Polytropos.Core (ConstructorIndex (..), Pair (..), Rep (..), RowRep (..))
import Polytropos.RowMorph (unsafeMkRowMorph)

{- |
Zero-cost unary monadic projection over Penelope's underlying polycategorical engine.
A computation of type @Eff eh ef a@ is isomorphic to a pipeline with zero input registers
and a single output register @'["val" ':= a]@.

See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
newtype Eff (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect) a = Eff
  { unEff :: Penelope eh ef '[] '["val" ':= a]
  }

instance Functor (Eff eh ef) where
  {-# INLINE fmap #-}
  fmap f m = m >>= (\a -> pure (f a))

instance Applicative (Eff eh ef) where
  {-# INLINE pure #-}
  pure a =
    let p :: Penelope eh ef '[] '["val" ':= a]
        p = LiftPure
          (unsafeMkRowMorph [])
          (\_ -> insertField (Field @"val") a emptyRecord)
          (BranchCont (\_ -> Route (unsafeMkRowMorph [0])))
    in Eff p

  {-# INLINE (<*>) #-}
  (<*>) = ap

instance Monad (Eff eh ef) where
  {-# INLINE (>>=) #-}
  (>>=) = bindEff

{-# INLINE bindEff #-}
bindEff ::
  forall eh ef a b.
  Eff eh ef a
  -> (a -> Eff eh ef b)
  -> Eff eh ef b
bindEff (Eff p) k =
  let stage :: Penelope eh ef '["val" ':= a] '["val" ':= b]
      stage = LiftPure
        (unsafeMkRowMorph [0])
        (\rec -> rec)
        (BranchCont $ \res ->
          let !(Identity aVal) = indexSmallArray (let RowRep (Rep _ arr) = res in arr) 0
              !a = unsafeCoerce# aVal :: a
              !(Eff nextP) = k a
          in preComposeRoute (unsafeMkRowMorph []) nextP
        )
  in Eff (seqPenelope p stage)

{- |
Inject a first-order unary algebraic effect into 'Eff'.
Resolves the constructor tag in \(O(1)\) at compile time via 'ConstructorIndex'.

See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
{-# INLINE sendEff #-}
sendEff ::
  forall (l :: Symbol) (sig :: Type -> Type) a (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect).
  ConstructorIndex l ef =>
  sig a
  -> Eff eh ef a
sendEff op =
  let retCont = BranchCont (\_ -> Route (unsafeMkRowMorph [0]))
      conts = insertField (Field @"ret") retCont emptyRecord
      !tag# = case constructorIndex @_ @l @ef of W# w# -> w#
      p = OpF tag# (unsafeMkRowMorph []) (unsafeCoerce# op) conts
  in Eff p

-- | Convenient alias for 'sendEff'.
{-# INLINE send #-}
send ::
  forall (l :: Symbol) (sig :: Type -> Type) a (eh :: RowOf PolyHEffect) (ef :: RowOf PolyEffect).
  ConstructorIndex l ef =>
  sig a
  -> Eff eh ef a
send = sendEff @l

{- |
Type-level adapter lifting a standard unary effect GADT (@sig :: Type -> Type@)
into Penelope's poly-kinded 'PolyEffect' representation.

See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
data UnaryEffect (sig :: Type -> Type) :: PolyEffect

-- | Handler for a single first-order effect under a target monad @m@.
type EffHandler m (sig :: Type -> Type) = Handler '[] (FrameKleisli m) (UnaryEffect sig)

-- | Record of handlers for the first-order effect row @ef@.
type EffHandlers ef m = Record (MapRow (Handler '[] (FrameKleisli m)) ef)

{- |
Construct an 'EffHandler' from a standard unary interpreter function.

See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
{-# INLINE mkEffHandler #-}
mkEffHandler ::
  forall (sig :: Type -> Type) m.
  Monad m =>
  (forall a. sig a -> m a)
  -> Handler '[] (FrameKleisli m) (UnaryEffect sig)
mkEffHandler runOp = Handler $ \_inMap opAny kConts ->
  let !op = unsafeCoerce# opAny :: sig Any
      !(Identity elabK) = indexSmallArray (let RowRep (Rep _ arr) = kConts in arr) 0
      !(BranchCont retK) = unsafeCoerce# elabK
  in FrameKleisli $ \_inF -> do
       res <- runOp op
       let resFrame = insertField (Field @"val") res emptyRecord
       runFrameKleisli (retK resFrame) resFrame

{- |
Execute a pure, closed 'Eff' computation to its final value.

See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
{-# INLINE runEff #-}
runEff :: Eff '[] '[] a -> a
runEff (Eff p) =
  let outFrame = runPenelopePure p emptyRecord
      !(Identity val) = indexSmallArray (let RowRep (Rep _ arr) = outFrame in arr) 0
  in unsafeCoerce# val

{- |
Execute an effectful 'Eff' computation into a target 'Monad' using a handler vector.

See Note [Zero-Cost Surface Specializations] [ref:note-surface-facades].
-}
{-# INLINE runEffM #-}
runEffM ::
  forall ef m a.
  Monad m =>
  Record (MapRow (Handler '[] (FrameKleisli m)) ef)
  -> Eff '[] ef a
  -> m a
runEffM handlers (Eff p) = do
  let kleisli = interpretAll handlers p
  outFrame <- runFrameKleisli kleisli emptyRecord
  let !(Identity val) = indexSmallArray (let RowRep (Rep _ arr) = outFrame in arr) 0
  pure (unsafeCoerce# val)