{-# LANGUAGE LambdaCase, OverloadedStrings, InstanceSigs #-} {-# LANGUAGE DerivingStrategies, GeneralizedNewtypeDeriving, DeriveGeneric, DeriveDataTypeable #-} module Data.Digit where import Prelude.Spiros -- import Data.Word (Word8) import GHC.Exts (IsString) -------------------------------------------------------------------------------- {-| a single-digit decimal number, i.e. @[0..9]@ i.e. @Fin 10@. the @Num@ instance provides numerical literals, and is partial like @Natural@. -} newtype Digit = Digit Int -- Word8 Natural deriving stock (Show,Read,Data) deriving newtype (Eq,Ord,Num,Ix,{-Bits,FiniteBits,-}NFData,Hashable) -- no Generic: it's abstract -- TODO are the two bits classes correct ? since they use Int's instance -- TODO custom number instance with modular arithmetic or something instance Bounded Digit where minBound = minimumDigit maxBound = maximumDigit -- | 'toEnum' is partial instance Enum Digit where toEnum :: Int -> Digit toEnum = unsafeDigit :: Int -> Digit fromEnum :: Digit -> Int fromEnum = fromDigit -- instance Enumerable Digit where -- enumerated = [0..9] -- cardinality _ = 10 -- instance Finite Digit where -- type Cardinality Digit = 10 minimumDigit :: (Num a) => a minimumDigit = 0 {-# SPECIALIZE minimumDigit :: Digit #-} {-# SPECIALIZE minimumDigit :: Int #-} maximumDigit :: (Num a) => a maximumDigit = 9 {-# SPECIALIZE maximumDigit :: Digit #-} {-# SPECIALIZE maximumDigit :: Int #-} fromDigit :: Digit -> Int fromDigit (Digit d) = d toDigit :: (Integral a) => a -> Maybe Digit toDigit i = if a <= i && i <= b then Just $ Digit (fromIntegral i) else Nothing where a = minimumDigit b = maximumDigit {-# SPECIALIZE toDigit :: Int -> Maybe Digit #-} unsafeDigit :: (Integral a) => a -> Digit unsafeDigit i = i & (toDigit >>> maybe (__ERROR__ e) id) where e = ("[spiros:Digit.unsafeDigit] a Digit must be a single-digit number") -- e = ("[spiros:Digit.unsafeDigit] " ++ show i ++ " is not a single-digit number") -- `show` forces Show constraint {-# SPECIALIZE unsafeDigit :: Int -> Digit #-} allDigits :: [Digit] -- Set Digit allDigits = [0..9] -------------------------------------------------------------------------------- parseDigit' :: Char -> Maybe Digit parseDigit' = \case '0' -> Just $ Digit 0 '1' -> Just $ Digit 1 '2' -> Just $ Digit 2 '3' -> Just $ Digit 3 '4' -> Just $ Digit 4 '5' -> Just $ Digit 5 '6' -> Just $ Digit 6 '7' -> Just $ Digit 7 '8' -> Just $ Digit 8 '9' -> Just $ Digit 9 _ -> Nothing parseDigit :: (IsString s, Eq s) => s -> Maybe Digit parseDigit = \case "0" -> Just $ Digit 0 "1" -> Just $ Digit 1 "2" -> Just $ Digit 2 "3" -> Just $ Digit 3 "4" -> Just $ Digit 4 "5" -> Just $ Digit 5 "6" -> Just $ Digit 6 "7" -> Just $ Digit 7 "8" -> Just $ Digit 8 "9" -> Just $ Digit 9 _ -> Nothing -------------------------------------------------------------------------------- {- newtype Digit = Digit Word8 -- TODO modular arithmetic: type Digit = Natural `Mod` 10 deriving (Show,Read,Eq,Ord,Generic,Data,NFData,Hashable) instance Bounded Digit where minBound = Digit 0 maxBound = Digit 9 instance Enum Digit where toEnum i | minBound <= i && i <= maxBound = Digit (toEnum i) | otherwise = error ("Digit.toEnum: " ++ show i ++ " is not a single-digit integer") fromEnum (Digit i) = fromEnum i parseDigit :: (IsString s, Eq s) => s -> Maybe Digit parseDigit = \case "0" -> Just $ Digit 0 "1" -> Just $ Digit 1 "2" -> Just $ Digit 2 "3" -> Just $ Digit 3 "4" -> Just $ Digit 4 "5" -> Just $ Digit 5 "6" -> Just $ Digit 6 "7" -> Just $ Digit 7 "8" -> Just $ Digit 8 "9" -> Just $ Digit 9 _ -> Nothing toDigit :: (Integral a) => a -> Maybe Digit toDigit i = if 0 >= i && i <= 9 then Just (Digit (fromIntegral i)) else Nothing -}