packages feed

moonlight-core-0.1.0.0: src-syntax/Moonlight/Core/Theory.hs

{-# LANGUAGE ScopedTypeVariables #-}

-- | The cheaply-canonicalizable slice of equational theory over a 'Language':
-- 'StructuralLaw' names node shapes whose canonical form is a sort (commutativity today).
-- Arbitrary equations belong to the e-graph layer, not here.
module Moonlight.Core.Theory
  ( StructuralLaw (..),
    TheorySpec (..),
    emptyTheorySpec,
    commutativeBinary,
    canonicalizeLayerByTheory,
    canonicalizePatternByTheory,
    expandPatternByTheory,
  )
where

import Data.Foldable (Foldable, toList)
import Data.Kind (Type)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Traversable (traverse)
import Moonlight.Core.Language (Language)
import Moonlight.Core.Pattern (Pattern (..))
import Prelude (Ord (..), const, (.), (<$>))

type StructuralLaw :: (Type -> Type) -> Type -> Type
data StructuralLaw f a
  = Ordinary
  | CommutativeBinary !(a -> a -> f a)

type TheorySpec :: (Type -> Type) -> Type
data TheorySpec f = TheorySpec
  { tsClassify :: forall a. f a -> StructuralLaw f a
  }

emptyTheorySpec :: TheorySpec f
emptyTheorySpec = TheorySpec {tsClassify = const Ordinary}

commutativeBinary :: (a -> a -> f a) -> StructuralLaw f a
commutativeBinary =
  CommutativeBinary

canonicalizeLayerByTheory :: (Foldable f, Ord a) => TheorySpec f -> f a -> f a
canonicalizeLayerByTheory spec node =
  case tsClassify spec node of
    Ordinary -> node
    CommutativeBinary law -> canonicalizeCommutativeBinary law node

canonicalizePatternByTheory :: Language f => TheorySpec f -> Pattern f -> Pattern f
canonicalizePatternByTheory spec patternValue =
  case patternValue of
    PatternVar patternVar ->
      PatternVar patternVar
    PatternNode node ->
      PatternNode (canonicalizeLayerByTheory spec (canonicalizePatternByTheory spec <$> node))

canonicalizeCommutativeBinary :: (Foldable f, Ord a) => (a -> a -> f a) -> f a -> f a
canonicalizeCommutativeBinary rebuild node =
  case toList node of
    [left, right] ->
      if right < left
        then rebuild right left
        else rebuild left right
    _ -> node

expandPatternByTheory :: forall f. Language f => TheorySpec f -> Pattern f -> [Pattern f]
expandPatternByTheory spec =
  Set.toList . expandPatternOrbit
  where
    expandPatternOrbit :: Pattern f -> Set (Pattern f)
    expandPatternOrbit patternValue =
      case patternValue of
        PatternVar patternVar ->
          Set.singleton (PatternVar patternVar)
        PatternNode node ->
          Set.unions (localPatternNodeOrbit <$> traverse (Set.toList . expandPatternOrbit) node)

    localPatternNodeOrbit :: f (Pattern f) -> Set (Pattern f)
    localPatternNodeOrbit node =
      Set.map PatternNode (commutedNodeOrbit (tsClassify spec node) node)

    commutedNodeOrbit :: StructuralLaw f (Pattern f) -> f (Pattern f) -> Set (f (Pattern f))
    commutedNodeOrbit (CommutativeBinary rebuild) node =
      case toList node of
        [left, right] ->
          Set.fromList [node, rebuild right left]
        _ -> Set.singleton node
    commutedNodeOrbit Ordinary node =
      Set.singleton node