packages feed

diff-loc-0.1.0.0: src/DiffLoc/Index.hs

{-# LANGUAGE
  AllowAmbiguousTypes,
  DataKinds,
  DerivingStrategies,
  DerivingVia,
  GeneralizedNewtypeDeriving,
  ScopedTypeVariables,
  TypeApplications,
  TypeFamilies #-}

-- | Indices and offsets.
module DiffLoc.Index
  ( -- * One-dimensional indices
    -- ** Unbounded indices
    Plain(..)

    -- ** Indices bounded by an origin
  , IndexFrom()
  , indexFromM
  , indexFromM0
  , indexFromM1
  , fromIndex
  , fromIndex0
  , fromIndex1
  , zeroIndex
  , oneIndex

    -- ** Offsets
  , Offset()
  , offsetM
  , fromOffset
  ) where

import Data.Monoid (Sum(..))
import Data.Proxy (Proxy(..))
import GHC.TypeNats (KnownNat, Nat, natVal)
import Text.Show.Combinators (showCon, (@|))
import DiffLoc.Shift

-- | One-dimensional indices.
newtype Plain a = Plain a
  deriving (Eq, Ord, Show)

instance (Num a, Ord a) => Amor (Plain a) where
  type Trans (Plain a) = Offset a
  Plain y .+ Offset x = Plain (x Prelude.+ y)
  Plain x .-.? Plain y | y <= x = Just (Offset (x - y))
                       | otherwise = Nothing

--

-- | One-dimensional indices with an origin (an initial index).
-- Indices must be greater than the origin, hence the constructor is hidden.
--
-- Use 'indexFromM' to construct indices, with @TypeApplications@ to make the
-- indexing scheme explicit, and 'fromIndex' to destruct them.
--
-- @
-- (origin :: IndexFrom n a) <= i    -- for all i
-- @
newtype IndexFrom (n :: Nat) a = IndexFrom a
  deriving (Eq, Ord)
  deriving Amor via (Plain a)

instance Show a => Show (IndexFrom n a) where
  showsPrec = flip $ \(IndexFrom i) -> showCon "indexFrom" @| i

instance (Num a, Ord a, KnownNat n) => Origin (IndexFrom n a) where
  origin = IndexFrom (knownNum @n)

-- | Reify a 'KnownNat'.
--
-- @
-- knownNum @42 = 42
-- @
knownNum :: forall n a. (KnownNat n, Num a) => a
knownNum = fromIntegral (natVal @n Proxy)

-- | Constructor for 'IndexFrom'.
--
-- See also 'DiffLoc.Unsafe.indexFrom' in "DiffLoc.Unsafe", a variant of 'indexFromM' that
-- throws errors instead of using @Maybe@.
indexFromM :: forall n a. (KnownNat n, Num a, Ord a) => a -> Maybe (IndexFrom n a)
indexFromM i | knownNum @n <= i = Just (IndexFrom i)
             | otherwise = Nothing

-- | 'indexFromM' specialized to 0-indexing.
indexFromM0 :: forall a. (Num a, Ord a) => a -> Maybe (IndexFrom 0 a)
indexFromM0 = indexFromM

-- | 'indexFromM' specialized to 1-indexing.
indexFromM1 :: forall a. (Num a, Ord a) => a -> Maybe (IndexFrom 1 a)
indexFromM1 = indexFromM

-- | Destructor for 'IndexFrom'.
fromIndex :: forall n a. IndexFrom n a -> a
fromIndex (IndexFrom i) = i

-- | 'fromIndex' specialized to 0-indexing.
fromIndex0 :: IndexFrom 0 a -> a
fromIndex0 = fromIndex

-- | 'fromIndex' specialized to 1-indexing.
fromIndex1 :: IndexFrom 1 a -> a
fromIndex1 = fromIndex

-- | Convert from zero-indexing to one-indexing.
oneIndex :: Num a => IndexFrom 0 a -> IndexFrom 1 a
oneIndex (IndexFrom i) = IndexFrom (i Prelude.+ 1)

-- | Convert from one-indexing to zero-indexing.
zeroIndex :: Num a => IndexFrom 1 a -> IndexFrom 0 a
zeroIndex (IndexFrom i) = IndexFrom (i - 1)

-- | Type of nonnegative offsets.
newtype Offset a = Offset a
  deriving (Eq, Ord)
  deriving (Semigroup, Monoid) via (Sum a)

instance Show a => Show (Offset a) where
  showsPrec = flip $ \(Offset i) -> showCon "offset" @| i

-- | Construct a nonnegative 'Offset'.
--
-- See also 'DiffLoc.Unsafe.offset' in "DiffLoc.Unsafe", a variant of 'offsetM' that
-- throws errors instead of using @Maybe@.
offsetM :: (Num a, Ord a) => a -> Maybe (Offset a)
offsetM i | 0 <= i = Just (Offset i)
          | otherwise = Nothing

-- | Unwrap 'Offset'.
fromOffset :: Offset a -> a
fromOffset (Offset i) = i