typed-digits-0.1.0.0: src/Data/TypedDigits/Internal.hs
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-|
Module : Data.TypedDigits.Internal
Description : Data.TypedDigits internals
Portability : portable
This module is considered internal, and may change
without warning.
The Package Versioning Policy does not apply to it.
= Description
Digits, indexed by their base at the type level.
-}
module Data.TypedDigits.Internal (
module Data.TypedDigits.Internal
, KnownNat
)
where
import Data.Maybe
import Data.Proxy
import Data.Singletons.TypeLits (Nat, Sing(..), KnownNat )
import Data.Singletons (singByProxy, SingI(..), fromSing )
-- | digits, indexed by their base at the type level
newtype Digit (base :: Nat) = Digit { getVal :: Int }
-- | @getBaseT p@: if @p@ is of some type @a base@,
-- then reflect the base into a value.
--
-- Useful for getting the base, just given a 'Proxy'; e.g.:
--
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> let p = Proxy :: Proxy 3
-- >>> getBaseT p
-- 3
getBaseT :: forall a base . (KnownNat base) => a base -> Int
getBaseT _ = fromIntegral $ fromSing s
where
s :: Sing base
s = singByProxy (Proxy :: Proxy base)
-- | @digit x@: construct a digit. 0 <= @x@ < base must hold
-- true.
--
-- Nicer if TypeApplications is enabled, then you can say:
--
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> digit @9 8
-- Just 8 (base 9)
digit :: forall base . (KnownNat base) => Int -> Maybe (Digit base)
digit x =
if x >= 0 && x < fromIntegral (getBaseT (Proxy :: Proxy base))
then Just $ Digit x
else Nothing
-- | Like 'digit', but throws an 'ErrorCall' if the value is
-- out of range.
--
-- sample usage:
--
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> digit' @9 7
-- 7 (base 9)
digit' :: KnownNat base => Int -> Digit base
digit' x = fromMaybe (error "bad value for base") (digit x)
-- get the Sing type fore the base, given some digit
digitSing :: KnownNat base => Digit base -> Sing base
digitSing Digit{} =
sing
-- | @getBase d@: return the base of the digit @d@
getBase :: KnownNat base => Digit base -> Int
getBase d = fromIntegral $ fromSing (digitSing d)
deriving instance Eq (Digit base)
deriving instance Ord (Digit base)
instance (KnownNat base) => Show (Digit base) where
show d = show (getVal d) ++ " (base " ++ show (getBase d) ++ ")"
instance KnownNat base => Enum (Digit base) where
toEnum = digit'
fromEnum = getVal
instance (KnownNat base) => Bounded (Digit base) where
minBound = digit' 0
maxBound = digit' $ n - 1
where
n = getBaseT (Proxy :: Proxy base)
-- | Making 'Digit' an instance of 'Num' does not seem
-- sensible, but it's nevertheless useful to be able to
-- add and subtract digits, so '<+>' and '<->' are provided
-- for this.
--
-- @a <+> b@: Add @a@ and @b@. Throws an 'ErrorCall' if the
-- result is out of range.
(<+>) :: KnownNat base => Digit base -> Digit base -> Digit base
a <+> b = digit' $ getVal a + getVal b
-- | @a <-> b@: Subtract @b@ from @a@. Throws an 'ErrorCall' if the
-- result is out of range.
(<->) :: KnownNat base => Digit base -> Digit base -> Digit base
a <-> b = digit' $ getVal a - getVal b