packages feed

covenant-1.0.0: src/Covenant/DeBruijn.hs

{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}

-- |
-- Module: Covenant.DeBruijn
-- Copyright: (C) MLabs 2025
-- License: Apache 2.0
-- Maintainer: koz@mlabs.city, sean@mlabs.city
--
-- DeBruijn indexing type and helpers. These are mainly used for scope tracking.
module Covenant.DeBruijn
  ( DeBruijn (Z, S),
    asInt,
  )
where

import Control.Monad (guard)
import Data.Coerce (coerce)
import Data.List.NonEmpty (NonEmpty)
import Data.Semigroup (Semigroup (sconcat, stimes), Sum (Sum))
import Data.Word (Word32)
import Test.QuickCheck (Arbitrary)

-- | A DeBruijn index.
--
-- @since 1.0.0
newtype DeBruijn = DeBruijn Word32
  deriving
    ( -- | @since 1.0.0
      Eq,
      -- | @since 1.0.0
      Ord,
      -- | @since 1.0.0
      Arbitrary
    )
    via Word32
  deriving stock
    ( -- | @since 1.0.0
      Show
    )

-- | Enables some manner of arithmetic with 'DeBruijn's. In this case, '<>' is
-- analogous to '+', while @'stimes' b@ is analogous to scalar multiplication by
-- @b@. Note that 'DeBruijn's cannot be scaled by negative numbers.
--
-- @since 1.0.0
instance Semigroup DeBruijn where
  {-# INLINEABLE (<>) #-}
  DeBruijn x <> DeBruijn y = DeBruijn (x + y)
  {-# INLINEABLE sconcat #-}
  sconcat = DeBruijn . sum . coerce @(NonEmpty DeBruijn) @(NonEmpty Word32)
  {-# INLINEABLE stimes #-}
  stimes b = DeBruijn . coerce . stimes b . coerce @_ @(Sum Word32)

-- | @since 1.0.0
instance Monoid DeBruijn where
  {-# INLINEABLE mempty #-}
  mempty = Z

-- | The zero index.
--
-- @since 1.0.0
pattern Z :: DeBruijn
pattern Z <- DeBruijn 0
  where
    Z = DeBruijn 0

-- | Successor to an index.
--
-- @since 1.0.0
pattern S :: DeBruijn -> DeBruijn
pattern S x <- (reduce -> Just x)
  where
    S (DeBruijn x) = DeBruijn (x + 1)

{-# COMPLETE Z, S #-}

-- | Convert a DeBruijn index into a (non-negative) 'Int'.
--
-- @since 1.0.0
asInt :: DeBruijn -> Int
asInt (DeBruijn i) = fromIntegral i

-- Helpers

reduce :: DeBruijn -> Maybe DeBruijn
reduce (DeBruijn x) = DeBruijn (x - 1) <$ guard (x > 0)