packages feed

data-debruijn-0.1.0.0: src-safe/Data/DeBruijn/Index/Safe.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}

module Data.DeBruijn.Index.Safe (
  -- * DeBruijn indices
  Ix (FZ, FS),
  eqIx,
  toSafe,
  fromSafe,
  fromIx,
  fromIxRaw,
  isPos,
  thin,
  thick,
  inject,
  raise,

  -- * Existential Wrapper
  SomeIx (..),
  withSomeIx,
  toSomeIx,
  toSomeIxRaw,
  fromSomeIx,
  fromSomeIxRaw,

  -- * Specialised target for conversion
  IxRep,
) where

import Control.DeepSeq (NFData (..))
import Data.DeBruijn.Index.Fast (IxRep)
import Data.DeBruijn.Index.Fast qualified as Fast
import Data.Kind (Type)
import Data.Type.Equality ((:~:) (Refl))
import Data.Type.Nat (type Nat (..), type Pos, type (+))
import Data.Type.Nat.Singleton.Safe (SNat (..), SNatRep, SomeSNat (..), decSNat, fromSNat, fromSNatRaw, plusUnitR, toSomeSNat)
import Text.Printf (printf)

{- $setup
>>> import Data.DeBruijn.Index.Safe.Arbitrary
-}

--------------------------------------------------------------------------------
-- DeBruijn Indexes
--------------------------------------------------------------------------------

-- | @'Ix' n@ is the type of DeBruijn indices less than @n@.
type Ix :: Nat -> Type
data Ix n where
  FZ :: Ix (S n)
  FS :: Ix n -> Ix (S n)

eqIx :: Ix n -> Ix m -> Bool
eqIx FZ FZ = True
eqIx (FS i) (FS j) = eqIx i j
eqIx _ _ = False

instance Eq (Ix n) where
  (==) :: Ix n -> Ix n -> Bool
  (==) = eqIx

instance Show (Ix n) where
  showsPrec :: Int -> Ix n -> ShowS
  showsPrec p =
    showParen (p > 10) . \case
      FZ -> showString "FZ"
      FS n -> showString "FS " . showsPrec 11 n

instance NFData (Ix n) where
  rnf :: Ix n -> ()
  rnf FZ = ()
  rnf (FS i) = rnf i

-- | Convert from the efficient representation 'Fast.Ix' to the safe representation 'Ix'.
toSafe :: Fast.Ix n -> Ix n
toSafe Fast.FZ = FZ
toSafe (Fast.FS i) = FS (toSafe i)

-- | Convert from the safe representation 'Ix' to the efficient representation 'Fast.Ix'.
fromSafe :: Ix n -> Fast.Ix n
fromSafe FZ = Fast.FZ
fromSafe (FS i) = Fast.FS (fromSafe i)

-- | Convert an 'Ix' to 'Word'.
fromIx :: (Integral i) => Ix n -> i
fromIx = \case
  FZ -> 0
  FS i -> 1 + fromIx i
{-# SPECIALIZE fromIx :: Ix n -> IxRep #-}

fromIxRaw :: Ix n -> IxRep
fromIxRaw = fromIx
{-# INLINE fromIxRaw #-}

-- | If any value of type @'Ix' n@ exists, @n@ must have a predecessor.
isPos :: Ix n -> ((Pos n) => a) -> a
isPos FZ r = r
isPos (FS _) r = r

-- | Thinning.
thin :: Ix (S n) -> Ix n -> Ix (S n)
thin FZ j = FS j
thin (FS _) FZ = FZ
thin (FS i) (FS j) = FS (thin i j)

-- | Thickening.
thick :: Ix (S n) -> Ix (S n) -> Maybe (Ix n)
thick FZ FZ = Nothing
thick FZ (FS j) = Just j
thick (FS i) FZ = isPos i $ Just FZ
thick (FS i) (FS j) = isPos i $ FS <$> thick i j

-- | Inject.
inject :: Ix n -> SNat m -> Ix (n + m)
inject FZ m = case plusUnitR m of Refl -> FZ
inject (FS i) n = FS (inject i n)

-- | Raise.
raise :: SNat n -> Ix m -> Ix (n + m)
raise Z j = j
raise (S n) j = FS (raise n j)

--------------------------------------------------------------------------------
-- Existential Wrapper
--------------------------------------------------------------------------------

-- | An existential wrapper around indexes.
type SomeIx :: Type
data SomeIx = forall (n :: Nat). SomeIx
  { bound :: !(SNat n)
  , index :: !(Ix n)
  }

instance Eq SomeIx where
  (==) :: SomeIx -> SomeIx -> Bool
  SomeIx n i == SomeIx m j
    | Just Refl <- decSNat n m = eqIx i j
    | otherwise = False

deriving instance Show SomeIx

instance NFData SomeIx where
  rnf :: SomeIx -> ()
  rnf SomeIx{..} = rnf bound `seq` rnf index

withSomeIx :: (forall n. SNat n -> Ix n -> a) -> SomeIx -> a
withSomeIx action (SomeIx n i) = action n i

{-| @'toSomeIx' n@ constructs the index @n@ at type @'Ix' n@ from the number @n@.

prop> toSomeIx (fromSomeIx i) == i
-}
toSomeIx :: (Integral n, Integral i) => (n, i) -> SomeIx
toSomeIx (bound, index)
  | index < 0 = error $ printf "index cannot contain negative value, found index %d" (toInteger index)
  | bound <= fromIntegral index = error $ printf "bound must be larger than index, found bound %d and index %d" (toInteger bound) (toInteger index)
  | bound >= 1, index == 0, SomeSNat n <- toSomeSNat (pred bound) = SomeIx (S n) FZ
  | SomeIx n i <- toSomeIx (pred bound, pred index) = SomeIx (S n) (FS i)

{-| @'toSomeIxRaw' n@ constructs the index @n@ at type @'Ix' n@ from the 'IxRep' @n@.

prop> toSomeIxRaw (fromSomeIxRaw i) == i
-}
toSomeIxRaw :: (SNatRep, IxRep) -> SomeIx
toSomeIxRaw = toSomeIx

-- | @'fromSomeSNat' n@ returns the numeric representation of the wrapped index.
fromSomeIx :: (Integral n, Integral i) => SomeIx -> (n, i)
fromSomeIx = withSomeIx (\n i -> (fromSNat n, fromIx i))

-- | @'fromSomeSNat' n@ returns the 'IxRep' representation of the wrapped index.
fromSomeIxRaw :: SomeIx -> (SNatRep, IxRep)
fromSomeIxRaw = withSomeIx (\n i -> (fromSNatRaw n, fromIxRaw i))