packages feed

roc-id-0.3.0.0: components/roc-id/ROC/ID/Digit.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module ROC.ID.Digit
  ( Digit (..)
  , fromChar
  , toChar
  , generate
  , FromChar
  , FromNat
  , ToNat
  ) where

import Control.Monad.Random
  ( MonadRandom )
import GHC.Generics
  ( Generic )
import GHC.TypeNats
  ( Nat )
import ROC.ID.Utilities
  ( maybeBoundedEnum, randomBoundedEnum )
import Text.Read
  ( Read (readPrec), readMaybe )

import Prelude hiding
  ( fromIntegral )

import qualified Prelude

-- | Represents a single decimal digit in the range @0@ to @9@.
--
data Digit
  = D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9
  deriving (Bounded, Enum, Eq, Generic, Ord)

-- | Arithmetic modulo 10.
--
instance Num Digit where
  a + b = fromIntegral (fromEnum a + fromEnum b)
  a * b = fromIntegral (fromEnum a * fromEnum b)
  a - b = fromIntegral (fromEnum a - fromEnum b)
  abs a = a
  fromInteger = fromIntegral
  signum D0 = D0
  signum __ = D1

instance Read Digit where readPrec = fromInteger <$> readPrec

instance Show Digit where show = show . fromEnum

-- | Attempts to parse a 'Digit' from a character.
--
-- The 'Char' must be a decimal digit in the range @0@ to @9@.
--
fromChar :: Char -> Maybe Digit
fromChar c = readMaybe [c] >>= maybeBoundedEnum

-- | Converts a 'Digit' to a decimal digit character.
--
toChar :: Digit -> Char
toChar digit = case show digit of
  [c] -> c
  _ -> error "toChar"

-- | Creates a 'Digit' from an integral number (modulo 10).
--
fromIntegral :: Integral i => i -> Digit
fromIntegral i = toEnum (Prelude.fromIntegral (i `mod` 10))

-- | Generates a random digit.
--
generate :: MonadRandom m => m Digit
generate = randomBoundedEnum

type family FromChar (c :: Char) :: Maybe Digit where
  FromChar '0' = Just D0
  FromChar '1' = Just D1
  FromChar '2' = Just D2
  FromChar '3' = Just D3
  FromChar '4' = Just D4
  FromChar '5' = Just D5
  FromChar '6' = Just D6
  FromChar '7' = Just D7
  FromChar '8' = Just D8
  FromChar '9' = Just D9
  FromChar _   = Nothing

type family FromNat (n :: Nat) :: Maybe Digit where
  FromNat 0 = Just D0
  FromNat 1 = Just D1
  FromNat 2 = Just D2
  FromNat 3 = Just D3
  FromNat 4 = Just D4
  FromNat 5 = Just D5
  FromNat 6 = Just D6
  FromNat 7 = Just D7
  FromNat 8 = Just D8
  FromNat 9 = Just D9
  FromNat _ = Nothing

type family ToNat (d :: Digit) :: Nat where
  ToNat D0 = 0
  ToNat D1 = 1
  ToNat D2 = 2
  ToNat D3 = 3
  ToNat D4 = 4
  ToNat D5 = 5
  ToNat D6 = 6
  ToNat D7 = 7
  ToNat D8 = 8
  ToNat D9 = 9