gargamelle-logic-0.1.0.0: src/Gargamelle/Logic/Defunctionalize.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Gargamelle.Logic.Defunctionalize
( TyFun
, type (~>)
, Apply
, SingSymbol(..)
, ObsMor(..)
, evalObsMor
, inspectObsMor
) where
import Control.Category (Category(..))
import Data.Kind (Type)
import Data.Profunctor (Profunctor(..), Strong(..), Choice(..))
import Data.Bifunctor (first, second)
import Prelude hiding (id, (.))
-- ============================================================================
-- 1. TYPE-LEVEL DEFUNCTIONALIZATION (For PolyKinds Signature Morphisms)
-- ============================================================================
-- | Dummy type constructor representing a defunctionalized type-level function
-- from kind @k1@ to kind @k2@.
data TyFun (k1 :: Type) (k2 :: Type) :: Type
-- | Type-level arrow kind @k1 ~> k2@ that can be partially applied and passed
-- to typeclasses without triggering GHC's unsaturated type family restriction.
type (k1 :: Type) ~> (k2 :: Type) = TyFun k1 k2 -> Type
-- | Open evaluation type family interpreting defunctionalized symbols.
type family Apply (f :: k1 ~> k2) (x :: k1) :: k2
-- | Runtime Singleton witness for a defunctionalized type-level symbol,
-- allowing runtime code analyzers to inspect type-level signature mappings.
data SingSymbol (f :: k1 ~> k2) = SingSymbol
{ symbolName :: String
, mappedIdentifiers :: [(String, String)]
} deriving (Eq, Show)
-- ============================================================================
-- 2. TERM-LEVEL OBSERVABLE MORPHISM GADT (Replaces Opaque `->` Closures)
-- ============================================================================
{- Note [Defunctionalized Observable Morphisms]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[tag:note-defunctionalized-obsmor]
In standard Haskell architectures, transformations and signature morphisms are
represented as native function closures @(a -> b)@. However, Haskell function
closures are completely opaque:
1. A function closure cannot be inspected: an autonomous agent or verifier cannot
determine whether @f@ reads a database, modifies a field, or drops information.
2. A function closure cannot be serialized or translated: it cannot be mapped to an
SMT-LIB2 relation or emitted into an explainable proof certificate.
3. A function closure prevents algebraic rewriting: categorical laws like
@f . id = f@ or @first (g . h) = first g . first h@ cannot be applied at compile
or run time.
To resolve this opacity, 'ObsMor a b' defunctionalizes morphism arrows into a first-class
GADT. Every pipeline step—whether an identity, a composition, a tagged field projection,
a tagged variant injection, or a profunctor splitting—is represented as an inspectable term.
Autonomous reasoning engines can:
- Statically inspect the transformation plan via 'inspectObsMor' without evaluating code.
- Compile the AST to native executable code via 'evalObsMor'.
- Verify functorial and profunctorial preservation across institutional boundaries.
-}
-- | Deeply embedded, inspectable morphism AST replacing native @(->)@.
--
-- See Note [Defunctionalized Observable Morphisms] [ref:note-defunctionalized-obsmor].
--
-- Autonomous agents can pattern-match on @ObsMor@ to extract symbolic dependencies,
-- or compile it to a native Haskell function via @evalObsMor@.
data ObsMor a b where
IdMor :: ObsMor a a
CompMor :: ObsMor b c -> ObsMor a b -> ObsMor a c
ProjectTag :: String -> (a -> b) -> ObsMor a b
InjectTag :: String -> (a -> b) -> ObsMor a b
FirstMor :: ObsMor a b -> ObsMor (a, c) (b, c)
SecondMor :: ObsMor a b -> ObsMor (c, a) (c, b)
LeftMor :: ObsMor a b -> ObsMor (Either a c) (Either b c)
RightMor :: ObsMor a b -> ObsMor (Either c a) (Either c b)
SymbolicStep :: String -> (a -> b) -> ObsMor a b
instance Category ObsMor where
id = IdMor
(.) = CompMor
instance Profunctor ObsMor where
dimap f g m = SymbolicStep "post" g . m . SymbolicStep "pre" f
instance Strong ObsMor where
first' = FirstMor
second' = SecondMor
instance Choice ObsMor where
left' = LeftMor
right' = RightMor
-- | Interpret the observable morphism AST as an executable Haskell function.
evalObsMor :: ObsMor a b -> (a -> b)
evalObsMor IdMor = id
evalObsMor (CompMor g f) = evalObsMor g . evalObsMor f
evalObsMor (ProjectTag _ f) = f
evalObsMor (InjectTag _ f) = f
evalObsMor (FirstMor f) = first (evalObsMor f)
evalObsMor (SecondMor f) = second (evalObsMor f)
evalObsMor (LeftMor f) = either (Left . evalObsMor f) Right
evalObsMor (RightMor f) = fmap (evalObsMor f)
evalObsMor (SymbolicStep _ fn) = fn
-- | Extract a structural trace of all transformations inside a morphism without running it.
inspectObsMor :: ObsMor a b -> [String]
inspectObsMor IdMor = ["id"]
inspectObsMor (CompMor g f) = inspectObsMor f ++ inspectObsMor g
inspectObsMor (ProjectTag tag _) = ["ProjectTag:" ++ tag]
inspectObsMor (InjectTag tag _) = ["InjectTag:" ++ tag]
inspectObsMor (FirstMor f) = map ("First(" ++) (inspectObsMor f)
inspectObsMor (SecondMor f) = map ("Second(" ++) (inspectObsMor f)
inspectObsMor (LeftMor f) = map ("Left(" ++) (inspectObsMor f)
inspectObsMor (RightMor f) = map ("Right(" ++) (inspectObsMor f)
inspectObsMor (SymbolicStep tag _) = ["Step:" ++ tag]