packages feed

kind-rational-0.5.0: lib/KindRational.hs

{-# LANGUAGE StrictData #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}

-- | This module provides a type-level representation for term-level
-- 'P.Rational's. This type-level representation is also named 'P.Rational',
-- So import this module qualified to avoid name conflicts.
--
-- @
-- import "KindRational" qualified as KR
-- @
--
-- The implementation details are similar to the ones for type-level 'Natural's
-- as of @base-4.18@ and @singletons-base-3.1.1@, and they will continue to
-- evolve together with @base@ and @singletons-base@, trying to more or less
-- follow their API.
module KindRational {--}
  ( -- * Rational
    Rational
  , type (:%)
  , FromNatural, fromNatural, sFromNatural
  , FromInteger, fromInteger, sFromInteger
  , Num, sNum
  , Den, sDen
  , ToRational(..)
  , sMkRational
  , (%%)
  , (%)

    -- * SRational
  , KnownRational
  , Reduced
  , rationalSing
  , rationalVal
  , withKnownRational
  , SomeRational(..)
  , someRationalVal
  , SRational
  , pattern SRational
  , fromSRational
  , withSomeSRational

    -- * Proofs
  , sNegateRefl

    -- * Show
    --
    -- | Besides the following /\*Lit/ tools, 'P.PShow' and 'P.SShow' can
    -- be used to display as "Prelude".'P.Rational' does.
  , ShowLit, showLit, sShowLit
  , ShowsLit, showsLit, sShowsLit
  , ShowsPrecLit, showsPrecLit, sShowsPrecLit
  , readPrecLit

    -- * Arithmethic
  , Signum, sSignum, sSignumRefl
  , Recip, sRecip, sRecip'
  , Div, sDiv, div
  , Rem, rem, sRem
  , DivRem, divRem, sDivRem
  , I.Round(..), I.SRound(..)

    -- * Decimals
  , IsTerminating
  , isTerminating
  , termination
  , Terminating
  , NonTerminating
  , pattern SRationalTerminating
  , pattern SRationalNonTerminating

    -- * Comparisons
    --
    -- | Additional comparison tools are available at 'SDdecide',
    -- 'TestEquality', 'TestCoercion', 'P.PEq', 'P.SEq', 'P.POrd', 'P.SOrd'
    -- and 'Compare'.
  , cmpRational
  , sameRational


    -- * Defunctionalization
  , type (%@#@$), type (%@#@$$), type (%@#@$$$)
  , type (:%@#@$), type (:%@#@$$), type (:%@#@$$$)
  , FromNaturalSym0, FromNaturalSym1
  , FromIntegerSym0, FromIntegerSym1
  , NumSym0, NumSym1
  , DenSym0, DenSym1
  , ToRationalSym0, ToRationalSym1, ToRationalSym2
  , ReducedSym0, ReducedSym1
  , ShowLitSym0, ShowLitSym1
  , ShowsLitSym0, ShowsLitSym1, ShowsLitSym2
  , ShowsPrecLitSym0, ShowsPrecLitSym1, ShowsPrecLitSym2, ShowsPrecLitSym3
  , IsTerminatingSym0, IsTerminatingSym1
  , TerminatingSym0, TerminatingSym1
  , NonTerminatingSym0, NonTerminatingSym1
  , SignumSym0, SignumSym1
  , RecipSym0, RecipSym1
  , DivSym0, DivSym1, DivSym2
  , RemSym0, RemSym1, RemSym2
  , DivRemSym0, DivRemSym1, DivRemSym2
  , I.RoundUpSym0
  , I.RoundDownSym0
  , I.RoundZeroSym0
  , I.RoundAwaySym0
  , I.RoundHalfUpSym0
  , I.RoundHalfDownSym0
  , I.RoundHalfZeroSym0
  , I.RoundHalfAwaySym0
  , I.RoundHalfEvenSym0
  , I.RoundHalfOddSym0
  ) --}
  where

import Control.Monad
import Data.Bits
import Data.Bool.Singletons (SBool(..))
import Data.Eq.Singletons qualified as EqS
import Data.Maybe
import Data.Ord.Singletons qualified as OrdS
import Data.Proxy
import Data.Singletons
import Data.Singletons.Decide
import Data.String
import Data.Type.Coercion
import Data.Type.Equality (TestEquality(..))
import Data.Type.Ord
import GHC.Base (WithDict(..))
import GHC.Exts (TYPE, Constraint)
import GHC.Read qualified as Read
import GHC.Real qualified as P (Ratio(..), (%))
import GHC.Show (appPrec, appPrec1)
import GHC.Stack (HasCallStack)
import GHC.TypeLits qualified as L hiding (someNatVal)
import GHC.TypeNats qualified as L (someNatVal)
import KindInteger (SInteger, Integer)
import KindInteger qualified as I
import Numeric.Natural (Natural)
import Prelude hiding (Rational, Integer, Num, div, rem, fromInteger)
import Prelude qualified as P
import Prelude.Singletons qualified as P
import Text.ParserCombinators.ReadP as ReadP
import Text.ParserCombinators.ReadPrec as ReadPrec
import Text.Read.Lex qualified as Read
import Text.Show.Singletons (AppPrec1)
import Unsafe.Coerce(unsafeCoerce)

--------------------------------------------------------------------------------

-- | Type-level version of "Prelude".'P.Rational', used only as a kind.
--
-- Use t'%' to construct one, use t':%' to pattern-match on it.
data Rational = Integer :% Natural
  -- ^ Note that @n ':%' d@ doesn't guarantee that @n@ be 'I.Normalized', nor
  -- that @d@ be non-zero, nor that the rational number is in 'Reduced' form.
  -- To make sure that's the case, use t'%' to construct type-level rationals,
  -- and use t':%' only for pattern-matching purposes. Alternatively, consider
  -- using the 'KnownRational' constraint or the 'Reduced' type-family. Both
  -- of them will reject any 'Rational' that fails to satisfy any of these
  -- constraints.

  -- Note: We export type-synonym ':%' rather than type-constructor ':%' so
  -- as to avoid having to use the leading tick, and to prevent term-level
  -- uses of the constructor.


-- | Shows just as a term-level "Prelude".'P.Rational'.
instance P.SShow Rational where
  sShowsPrec _ si ss = withSomeSing
                         (fromString (show (fromSing si)) <> fromSing ss)
                         unsafeCoerce

-- | Shows just as a term-level "Prelude".'P.Rational'.
--
-- 'ShowsPrec' type-checks only if the type-level 'Rational' is 'Reduced'.
instance P.PShow Rational where
  type ShowsPrec p r s = P.ShowParen (p P.>= AppPrec1)
    (P.ShowsPrecSym2 AppPrec1 (Num r) P..@#@$$$
     P.ShowStringSym1 " % " P..@#@$$$
     P.ShowsSym1 (Den r)) s

-- | Shows as a type-level "KindRational".'Rational' apears literally at the
-- type-level. Type-checks only if the type-level 'Rational' is 'Reduced'.
type ShowLit (r :: Rational) = ShowsLit r "" :: L.Symbol

data ShowLitSym0 :: Rational ~> L.Symbol
type ShowLitSym1 :: Rational -> L.Symbol

type instance Apply ShowLitSym0 i = ShowLitSym1 i
type ShowLitSym1 i = ShowLit i

-- | Shows as a type-level "KindRational".'Rational' apears literally at the
-- type-level. Type-checks only if the type-level 'Rational' is 'Reduced'.
type ShowsLit (r :: Rational) (s :: L.Symbol) = ShowsPrecLit 0 r s :: L.Symbol

data ShowsLitSym0 :: Rational ~> L.Symbol ~> L.Symbol
data ShowsLitSym1 :: Rational -> L.Symbol ~> L.Symbol
type ShowsLitSym2 :: Rational -> L.Symbol -> L.Symbol

type instance Apply ShowsLitSym0 i = ShowsLitSym1 i
type instance Apply (ShowsLitSym1 i) s = ShowsLitSym2 i s
type ShowsLitSym2 i s = ShowsLit i s

-- | Shows as a type-level "KindRational".'Rational' apears literally at the
-- type-level. Type-checks only if the type-level 'Rational' is 'Reduced'.
type ShowsPrecLit (p :: Natural) (r :: Rational) (s :: L.Symbol) =
  P.ShowParen (p P.>= AppPrec1)
    (I.ShowsLitSym1 (Num r) P..@#@$$$
     P.ShowStringSym1 " :% " P..@#@$$$
     P.ShowsSym1 (Den r)) s :: L.Symbol

data ShowsPrecLitSym0 :: Natural ~> Rational ~> L.Symbol ~> L.Symbol
data ShowsPrecLitSym1 :: Natural -> Rational ~> L.Symbol ~> L.Symbol
data ShowsPrecLitSym2 :: Natural -> Rational -> L.Symbol ~> L.Symbol
type ShowsPrecLitSym3 :: Natural -> Rational -> L.Symbol -> L.Symbol

type instance Apply ShowsPrecLitSym0 p = ShowsPrecLitSym1 p
type instance Apply (ShowsPrecLitSym1 p) i = ShowsPrecLitSym2 p i
type instance Apply (ShowsPrecLitSym2 p i) s = ShowsPrecLitSym3 p i s
type ShowsPrecLitSym3 p i s = ShowsPrecLit p i s

-- | Singleton version of 'ShowLit'.
sShowLit :: SRational r -> Sing (ShowLit r)
sShowLit sr = sShowsLit sr (sing @"")

-- | Demoted version of 'ShowLit'.
showLit :: P.Rational -> String
showLit r = showsLit r ""

-- | Singleton version of 'ShowsLit'.
sShowsLit :: SRational r -> Sing (s :: P.Symbol) -> Sing (ShowsLit r s)
sShowsLit = sShowsPrecLit (sing @0)

-- | Demoted version of 'ShowsLit'.
showsLit :: P.Rational -> ShowS
showsLit = showsPrecLit 0

-- | Singleton version of 'ShowsPrecLit'.
sShowsPrecLit
  :: Sing (p :: Natural)
  -> SRational r
  -> Sing (s :: P.Symbol)
  -> Sing (ShowsPrecLit p r s)
sShowsPrecLit sp si ss =
  let p = fromMaybe (error "sShowsPrecLit: invalid precedence")
                    (toIntegralSized (fromSing sp))
      t = fromString (showsPrecLit p (fromSing si) "")
  in withSomeSing (t <> fromSing ss) unsafeCoerce

-- | Demoted version of 'ShowsPrecLit'.
showsPrecLit :: Int -> P.Rational -> ShowS
showsPrecLit p _ | p < 0 = error "showsPrecLit: negative precedence"
showsPrecLit p (unsafeReduced -> n P.:% d) =
  showParen (p >= appPrec1) $
    I.showsPrecLit appPrec n .
    showString " :% " .
    shows d

-- | Inverse of 'showsPrecLit'.
readPrecLit :: ReadPrec.ReadPrec P.Rational
readPrecLit = Read.parens $ do
    n :: P.Integer <- Read.parens $ ReadPrec.step I.readPrecLit
    Read.expectP (Read.Symbol ":%")
    d :: Natural <- Read.parens $ ReadPrec.step $ ReadPrec.lift pNatural
    either fail pure $ rationalLit n (toInteger d)
  where
    pNatural :: ReadP.ReadP Natural
    pNatural = read <$> ReadP.munch1 (\c -> c >= '0' && c <= '9')

-- | Creates a "Prelude".'P.Rational' using the given literal numerator
-- and denominator, if in reduced form with a non-zero denominator.
rationalLit :: P.Integer -> P.Integer -> Either String P.Rational
rationalLit = \n d -> do
  when (d == 0) $ Left "Denominator is zero."
  when (d < 0) $ Left "Rational is not reduced."
  let r@(n' P.:% d') = n P.% d
  when (n /= n' || d /= d') $ Left "Rational is not reduced."
  Right r

-- | Reduces the given rational. 'error's if it can't be reduced.
unsafeReduce :: HasCallStack => P.Rational -> P.Rational
unsafeReduce = \case
  n P.:% d | d /= 0 -> n P.% d
           | otherwise -> error "Denominator is zero."

-- | 'error's if the given rational is not reduced.  Otherwise, returns it.
unsafeReduced :: HasCallStack => P.Rational -> P.Rational
unsafeReduced = \r0 -> case unsafeReduce r0 of
  r1 | r0 == r1 -> r0
     | otherwise -> error $ concat
        [ "Expected reduced rational ", showsPrec appPrec1 r1 ""
        , ", got " , showsPrec appPrec1 r0 "." ]

--------------------------------------------------------------------------------

-- | Singleton version of 'Num'.
sNum :: SRational r -> SInteger (Num r)
sNum sr | n P.:% _ <- fromSing sr =
  withSomeSing n unsafeCoerce

-- | Literal /'Num'erator/ of a type-level 'Rational'.
-- It fails to type-check if the 'Rational' is not 'Reduced'.
type Num (r :: Rational) = Num_ (Reduced r) :: Integer
type family Num_ (r :: Rational) :: Integer where Num_ (n :% _) = n

data NumSym0 :: Rational ~> Integer
type NumSym1 :: Rational -> Integer

type instance Apply NumSym0 i = Num i
type NumSym1 i = Num i

-- | Singleton version of 'Den'.
sDen :: SRational r -> Sing (Den r :: Natural)
sDen sr | _ P.:% d <- fromSing sr =
  withSomeSing @Natural (P.fromInteger d) unsafeCoerce

-- | Literal /'Den'ominator/ of a type-level 'Rational'.
-- It fails to type-check if the 'Rational' is not 'Reduced'.
type Den (r :: Rational) = Den_ (Reduced r) :: Natural
type family Den_ (r :: Rational) :: Natural where Den_ (_ :% d) = d

data DenSym0 :: Rational ~> Natural
type DenSym1 :: Rational -> Natural

type instance Apply DenSym0 i = Den i
type DenSym1 i = Den i

-- | Pattern-match on a type-level 'Rational'.
--
-- Note that t':%' doesn't check that the 'Rational' be 'Reduced' nor
-- have a non-zero denominator.
--
-- When constructing 'Rational's, use t'%' instead, which not only accepts
-- more polymorphic inputs, but also returns a 'Reduced' result with
-- denominator known to be non-zero.
type (n :: I.Integer) :% (d :: Natural) = n ':% d :: Rational

data (:%@#@$) :: Integer ~> Natural ~> Rational
data (:%@#@$$) :: Integer -> Natural ~> Rational
type (:%@#@$$$) :: Integer -> Natural -> Rational

type instance Apply ((:%@#@$$) b) p = (:%@#@$$$) b p
type instance Apply (:%@#@$) b = (:%@#@$$) b
type (:%@#@$$$) b p = b :% p

--------------------------------------------------------------------------------

-- | A 'Reduce'd rational number is one in which the numerator and denominator
-- have no common denominators. 'Reduced' fails to type-check if the given
-- type-level 'Rational' is not reduced, otherwise it returns the given
-- 'Rational', unmodified.  It also fails to type-check if the 'I.Integer'
-- numerator isn't 'I.Normalized', or if the denominator is zero.
--
-- Only reduced 'Rational's can be reliably constrained for equality
-- using '~'. Only reduced 'Rational's are 'KnownRational's.
--
-- The type-level functions in the "KindRational" module
-- always produce reduced 'Rational's.
type Reduced (r :: Rational) = Reduced_ r (Reduce r) :: Rational
type family Reduced_ (r :: Rational) (x :: Rational) :: Rational where
  Reduced_ r r = r
  Reduced_ (na :% da) (nb :% db) = L.TypeError
    ('L.Text "Expected reduced rational (" 'L.:<>:
     'L.Text (I.ShowsLit nb " :% ") 'L.:<>:
     'L.Text (P.Shows db "), got (") 'L.:<>:
     'L.Text (I.ShowsLit na " :% ") 'L.:<>:
     'L.Text (P.Shows da ")."))

data ReducedSym0 :: Rational ~> Rational
type ReducedSym1 :: Rational -> Rational

type instance Apply ReducedSym0 i = Reduced i
type ReducedSym1 i = Reduced i

-- | 'SRational's always contain a 'Reduced' 'Rational'.
sReducedRefl :: SRational r -> (r :~: Reduced r)
sReducedRefl !_ = unsafeCoerce Refl

-- | Reduce a type-level 'Rational' so that the numerator and denominator
-- have no common factors. It fails to type-check if the 'I.Integer'
-- numerator isn't 'I.Normalized'.
--
-- Only reduced 'Rational's can be reliably constrained for equality
-- using '~'. Only reduced 'Rational's are 'KnownRational's.
type family Reduce (r :: Rational) :: Rational where
  Reduce (n :% 0) = L.TypeError
    ('L.Text "Denominator is zero in (" 'L.:<>: 'L.Text (I.ShowsLit n " :% 0)"))
  Reduce (n :% d) =
    I.Fold (FromNatural 0) (ReduceNegSym1 d) (ReducePosSym1 d) n

data ReduceNegSym1 :: Natural -> Natural ~> Rational
type instance Apply (ReduceNegSym1 d) n = ReduceNeg d n
type ReduceNeg (d :: Natural) (n :: Natural)
  = ReduceNeg_ (ReducePos d n) :: Rational
type ReduceNeg_ (rpos :: Rational) = P.Negate (Num_ rpos) :% Den_ rpos

data ReducePosSym1 :: Natural -> Natural ~> Rational
type instance Apply (ReducePosSym1 d) n = ReducePos d n
type family ReducePos (d :: Natural) (n :: Natural) :: Rational where
  ReducePos d n =
    I.FromNatural (L.Div n (I.GCD (I.FromNatural n) (I.FromNatural d)))
               :% (L.Div d (I.GCD (I.FromNatural n) (I.FromNatural d)))

--------------------------------------------------------------------------------

-- | Construct a type-level 'Rational' from a type-level 'Natural'.
type FromNatural (n :: Natural) = I.FromNatural n :% 1 :: Rational

data FromNaturalSym0 :: Natural ~> Rational
type FromNaturalSym1 :: Natural -> Rational

type instance Apply FromNaturalSym0 i = FromNatural i
type FromNaturalSym1 i = FromNatural i

-- | Singleton version of 'FromNatural'.
sFromNatural :: Sing (n :: Natural) -> SRational (FromNatural n)
sFromNatural = UnsafeSRational . fromIntegral . fromSing
{-# INLINE sFromNatural #-}

-- | Demoted version of 'FromNatural'.
fromNatural :: Natural -> P.Rational
fromNatural = P.fromIntegral
{-# INLINE fromNatural #-}

--------------------------------------------------------------------------------

-- | Construct a type-level 'Rational' from a type-level 'Integer'.
-- It fails to type-check if the 'I.Integer' isn't 'I.Normalized'.
type FromInteger (i :: Integer) = I.Normalized i :% 1 :: Rational

data FromIntegerSym0 :: Integer ~> Rational
type FromIntegerSym1 :: Integer -> Rational

type instance Apply FromIntegerSym0 i = FromInteger i
type FromIntegerSym1 i = FromInteger i

-- | Singleton version of 'FromInteger'.
sFromInteger :: SInteger n -> SRational (FromInteger n)
sFromInteger = UnsafeSRational . fromInteger . fromSing
{-# INLINE sFromInteger #-}

-- | Demoted version of 'FromInteger'.
fromInteger :: P.Integer -> P.Rational
fromInteger = P.fromInteger
{-# INLINE fromInteger #-}

--------------------------------------------------------------------------------

infixl 7 %, %%, :%, :%@#@$$$, %@#@$$$

-- | @'ToRational' kn kd@ enables constructing type-level 'Rational's
-- from a numerator of kind @kn@ and a denominator of kind @kd@.
class (SingKind kn, SingKind kd) => ToRational kn kd where
  -- | @n t'%' d@ constructs and reduces a type-level 'Rational'
  -- with numerator @n@ and denominator @d@.
  --
  -- This type-family accepts any combination of 'Natural', 'Integer' and
  -- 'Rational' as input.
  --
  -- @
  -- ( t'%') :: 'Natural'  -> 'Natural'  -> 'Rational'
  -- ( t'%') :: 'Natural'  -> 'Integer'  -> 'Rational'
  -- ( t'%') :: 'Natural'  -> 'Rational' -> 'Rational'
  --
  -- ( t'%') :: 'Integer'  -> 'Natural'  -> 'Rational'
  -- ( t'%') :: 'Integer'  -> 'Integer'  -> 'Rational'
  -- ( t'%') :: 'Integer'  -> 'Rational' -> 'Rational'
  --
  -- ( t'%') :: 'Rational' -> 'Natural'  -> 'Rational'
  -- ( t'%') :: 'Rational' -> 'Integer'  -> 'Rational'
  -- ( t'%') :: 'Rational' -> 'Rational' -> 'Rational'
  -- @
  --
  -- It's not possible to pattern-match on @n t'%' d@.  Instead, you must
  -- pattern match on @x t':%' y@, where @x t':%' y ~ n t'%' d@.
  type (n :: kn) % (d :: kd) :: Rational
  -- | Demoted version of t'%'. Returns 'Nothing' where t'%' would fail
  -- to type-check (that is, denominator is 0).
  -- See '%' for an unsafe version of this.
  mkRational :: Demote kn -> Demote kd -> Maybe P.Rational

-- | Like 'mkRational', but fails with 'error' where 'mkRational' would
-- fail with 'Nothing'.
(%) :: forall kn kd
    .  (ToRational kn kd, HasCallStack)
    => Demote kn
    -> Demote kd
    -> P.Rational
n % d = fromMaybe (error "Denominator is zero.") (mkRational n d)
{-# INLINE (%) #-}

-- | Like 'sMkRational', but never fails, thanks to a
-- 'KnownRational' constraint.
(%%) :: forall kn kd n d
     .  (ToRational kn kd, KnownRational (n % d))
     => Sing (n :: kn)
     -> Sing (d :: kd)
     -> SRational (n % d)
(%%) sn sd = fromJust (sMkRational sn sd)
{-# INLINE (%%) #-}

-- | Singleton version of 'mkRational'.
sMkRational
  :: forall kn kd n d
  .  (ToRational kn kd)
  => Sing (n :: kn)
  -> Sing (d :: kd)
  -> Maybe (SRational (n % d))
sMkRational sn sd =
  UnsafeSRational <$> mkRational (fromSing sn) (fromSing sd)
{-# INLINE sMkRational #-}

data ToRationalSym0 :: kkn ~> kkd ~> Constraint
data ToRationalSym1 :: kkn -> kkd ~> Constraint
-- type ToRationalSym2 :: kkn -> kkd -> Constraint  --  (inferred)

type instance Apply ToRationalSym0 kn = ToRationalSym1 kn
type instance Apply (ToRationalSym1 kn) kd = ToRationalSym2 kn kd
type ToRationalSym2 kn kd = ToRational kn kd

data (%@#@$) :: kn ~> kd ~> Rational
data (%@#@$$) :: kn -> kd ~> Rational
type (%@#@$$$) :: kn -> kd -> Rational

type instance Apply ((%@#@$$) b) p = (%@#@$$$) b p
type instance Apply (%@#@$) b = (%@#@$$) b
type (%@#@$$$) b p = b % p

instance ToRational Natural Natural where
  type n % d = I.FromNatural n % d
  mkRational _ 0 = Nothing
  mkRational n d = Just $! toInteger n P.% toInteger d

instance ToRational Natural Integer where
  type n % d = (I.FromNatural n P.* P.Signum d) % I.Abs d
  mkRational _ 0 = Nothing
  mkRational n d = Just $! toInteger n P.% d

instance ToRational Natural Rational where
  type n % d = FromNatural n P.* Recip d
  mkRational _ 0 = Nothing
  mkRational n d = Just $! fromIntegral n P./ d

instance ToRational Integer Natural where
  type n % d = Reduce (n :% d)
  mkRational _ 0 = Nothing
  mkRational n d = Just $! n P.% toInteger d

instance ToRational Integer Integer where
  type n % d = (n P.* P.Signum d) % I.Abs d
  mkRational _ 0 = Nothing
  mkRational n d = Just $! (n P.* P.signum d) P.% P.abs d

instance ToRational Integer Rational where
  type n % d = FromInteger n P.* Recip d
  mkRational _ 0 = Nothing
  mkRational n d = Just $! fromInteger n P.* recip d

instance ToRational Rational Natural where
  type n % d = n P.* Recip (FromNatural d)
  mkRational _ 0 = Nothing
  mkRational n d = Just $! n P.* P.recip (fromNatural d)

instance ToRational Rational Integer where
  type n % d = n P.* Recip (FromInteger d)
  mkRational _ 0 = Nothing
  mkRational n d = Just $! n P.* P.recip (fromInteger d)

instance ToRational Rational Rational where
  type n % d = n P.* Recip d
  mkRational _ 0 = Nothing
  mkRational n d = Just $! n P.* P.recip d

--------------------------------------------------------------------------------

instance P.PNum Rational where
  type l + r = Add l r
  type l - r = Add l (P.Negate r)
  type l * r = Mul l r
  type Negate r = Reduce (P.Negate (Num r) :% Den r)
  type Abs r = Reduce (P.Abs (Num r) :% Den r)
  type Signum r = P.Signum (Num r) :% 1
  type FromInteger n = FromNatural n

instance P.SNum Rational where
  l %+ r = UnsafeSRational (fromSing l + fromSing r)
  l %- r = UnsafeSRational (fromSing l - fromSing r)
  l %* r = UnsafeSRational (fromSing l * fromSing r)
  sNegate = UnsafeSRational . negate . fromSing
  sAbs = UnsafeSRational . abs . fromSing
  sSignum = UnsafeSRational . signum . fromSing
  sFromInteger = sFromNatural

sNegateRefl :: Sing (r :: Rational) -> (r :~: P.Negate (P.Negate r))
sNegateRefl !_ = unsafeCoerce Refl

-- | Sign of type-level 'Rational's, as a type-level 'Integer'.
--
-- * 'I.Z' if zero.
--
-- * @'I.P' 1@ if positive.
--
-- * @'I.N' 1@ if negative.
type Signum (r :: Rational) = P.Signum (Num r) :: Integer

data SignumSym0 :: Rational ~> Integer
type SignumSym1 :: Rational -> Integer

type instance Apply SignumSym0 i = Signum i
type SignumSym1 i = Signum i

-- | Singleton version of 'Signum'.
sSignum :: Sing (r :: Rational) -> SInteger (Signum r)
sSignum sr = P.sSignum (sNum sr)

-- | The 'Signum' of a 'Rational' equals the 'PNum' 'P.Signum' of its
-- 'Num'erator, as well as the 'Num'erator of its 'PNum' 'P.Signum'.
sSignumRefl
  :: SRational r
  -> (Signum r :~: P.Signum (Num r),
      Signum r :~: Num (P.Signum r))
sSignumRefl !_ = unsafeCoerce (Refl, Refl)

type Mul (a :: Rational) (b :: Rational) =
  Mul_ (Reduce a) (Reduce b) :: Rational
type family Mul_ (a :: Rational) (b :: Rational) where
  Mul_ (n1 :% d1) (n2 :% d2) = Reduce ((n1 P.* n2) :% (d1 L.* d2))

type Add (a :: Rational) (b :: Rational) =
  Add_ (Reduce a) (Reduce b) :: Rational
type family Add_ (a :: Rational) (r :: Rational) :: Rational where
  Add_ (an :% ad) (bn :% bd) =
    (an P.* I.FromNatural bd P.+ bn P.* I.FromNatural ad) % (ad L.* bd)

--------------------------------------------------------------------------------

-- | Singleton version of 'Recip'. Type-checks only with a non-zero @r@.
sRecip :: I.Z :% 1 < P.Abs r => SRational r -> SRational (Recip r)
sRecip = UnsafeSRational . recip . fromSing

-- | Like 'sRecip', except it fails with 'Nothing' when @r@ is zero, rather
-- than requiring to satisfy this as a type-level constraint.
sRecip' :: forall r. SRational r -> Maybe (SRational (Recip r))
sRecip' sa = case fromSing sa of
               0 -> Nothing
               a -> Just (UnsafeSRational (recip a))

-- | /'Recip'rocal/ of the type-level 'Rational'.
-- Also known as /multiplicative inverse/.
type Recip (a :: Rational) = Den a % Num a :: Rational

data RecipSym0 :: Rational ~> Rational
type RecipSym1 :: Rational -> Rational

type instance Apply RecipSym0 i = Recip i
type RecipSym1 i = Recip i

--------------------------------------------------------------------------------

-- | Singleton version of 'Div'.
sDiv :: I.SRound r -> SRational a -> SInteger (Div r a)
sDiv sr sa = withSomeSing (div (fromSing sr) (fromSing sa)) unsafeCoerce

-- | Quotient of the 'Div'ision of the 'Num'erator of type-level 'Rational' @a@
-- by its 'Den'ominator, using the specified 'I.Round'ing @r@.
--
-- @
-- forall (r :: 'I.Round') (a :: 'Rational').
--   ('KnownRational' a) =>
--     'Rem' r a  '=='  a '-' 'Div' r a t'%' 1
-- @
--
-- Use this to approximate a type-level 'Rational' to an 'Integer'.
type Div (r :: I.Round) (a :: Rational) = Div_ r (Reduce a) :: Integer
type Div_ (r :: I.Round) (a :: Rational) =
  I.Div r (Num a) (I.FromNatural (Den a)) :: Integer

data DivSym0 :: I.Round ~> Rational ~> Integer
data DivSym1 :: I.Round -> Rational ~> Integer
type DivSym2 :: I.Round -> Rational -> Integer

type instance Apply DivSym0 a = DivSym1 a
type instance Apply (DivSym1 a) b = DivSym2 a b
type DivSym2 a b = Div a b

-- | Singleton version of 'Rem'.
sRem :: I.SRound r -> SRational a -> SRational (Rem r a)
sRem sr sa = snd (sDivRem sr sa)

-- | 'Rem'ainder from 'Div'iding the 'Num'erator of the type-level 'Rational'
-- @a@ by its 'Den'ominator, using the specified 'I.Round'ing @r@.
--
-- @
-- forall (r :: 'I.Round') (a :: 'Rational').
--   ('KnownRational' a) =>
--     'Rem' r a  '=='  a '-' 'Div' r a t'%' 1
-- @
type Rem (r :: I.Round) (a :: Rational) = P.Snd (DivRem r a) :: Rational

data RemSym0 :: I.Round ~> Rational ~> Rational
data RemSym1 :: I.Round -> Rational ~> Rational
type RemSym2 :: I.Round -> Rational -> Rational

type instance Apply RemSym0 a = RemSym1 a
type instance Apply (RemSym1 a) b = RemSym2 a b
type RemSym2 a b = Rem a b

-- | Singleton version of 'DivRem'.
sDivRem :: I.SRound r -> SRational a
        -> (SInteger (Div r a), SRational (Rem r a))
sDivRem sr sa =
  let (d1, r1) = divRem (fromSing sr) (fromSing sa)
  in (withSomeSing d1 unsafeCoerce, UnsafeSRational r1)

-- | Get both the quotient and the 'Rem'ainder of the 'Div'ision of the
-- 'Num'erator of type-level 'Rational' @a@ by its 'Den'ominator,
-- using the specified 'I.Round'ing @r@.
--
-- @
-- forall (r :: 'I.Round') (a :: 'Rational').
--   ('KnownRational' a) =>
--     'DivRem' r a  '=='  '('Div' r a, 'Rem' r a)
-- @
type DivRem (r :: I.Round) (a :: Rational) =
  DivRem_ r (Reduce a) :: (Integer, Rational)
type DivRem_ (r :: I.Round) (a :: Rational) =
  DivRem__ (Den a) (I.DivRem r (Num a) (I.FromNatural (Den a)))
    :: (Integer, Rational)
type DivRem__ (d :: Natural) (qm :: (Integer, Integer)) =
  '(P.Fst qm, Reduce (P.Snd qm :% d)) :: (Integer, Rational)

data DivRemSym0 :: I.Round ~> Rational ~> (Integer, Rational)
data DivRemSym1 :: I.Round -> Rational ~> (Integer, Rational)
type DivRemSym2 :: I.Round -> Rational -> (Integer, Rational)

type instance Apply DivRemSym0 a = DivRemSym1 a
type instance Apply (DivRemSym1 a) b = DivRemSym2 a b
type DivRemSym2 a b = DivRem a b

-- | Term-level version of 'Div'.
--
-- Takes a "KindRational".'Rational' as input, returns a "Prelude"
-- 'P.Integer'.
--
-- NB: 'error's if the 'P.Rational' denominator is 0.
div :: I.Round -> P.Rational -> P.Integer
div rnd = \(unsafeReduce -> n P.:% d) -> f n d
  where f = I.div rnd

-- | Term-level version of 'Rem'.
--
-- Takes a "Prelude".'P.Rational' as input, returns a "Prelude".'P.Rational'.
--
-- NB: 'error's if the 'P.Rational' denominator is 0.
rem :: I.Round -> P.Rational -> P.Rational
rem rnd = snd . divRem rnd

-- | Term-level version of 'DivRem'.
--
-- Takes a "Prelude".'P.Rational' as input, returns a pair of
-- /(quotient, reminder)/.
--
-- @
-- forall ('r' :: 'I.Round') (a :: 'P.Rational').
--   ('P.denominator' a 'P./=' 0) =>
--     'divRem' r a  'P.=='  ('div' r a, 'rem' r a)
-- @
--
-- NB: 'error's if the 'P.Rational' denominator is 0.
divRem :: I.Round -> P.Rational -> (P.Integer, P.Rational)
divRem rnd = \(unsafeReduce -> n P.:% d) ->
                 let (q, m) = f n d
                 in  (q, m P.:% d) -- (m :% d) == ((n :% d) - q)
  where f = I.divRem rnd

--------------------------------------------------------------------------------

-- | Determine whether @r@ is 'Terminating' or 'NonTerminating' at the
-- term-level, and create the corresponding type-level proof.
termination
  :: forall r a
  .  (NonTerminating r => a)
  -> (Terminating r => a)
  -> SRational r
  -> a
termination f t sr =
  withKnownRational sr $ case isTerminating (fromSRational sr) of
    False | Refl <- (unsafeCoerce Refl :: IsTerminating r :~: 'False) -> f
    True  | Refl <- (unsafeCoerce Refl :: IsTerminating r :~: 'True)  -> t

-- | This is essentially the same as @('KnownRational' r, 'IsTerminating' r ~ 'True')@,
-- except with a nicer error message when @'IsTerminating' r ~ 'False'@.
type Terminating (r :: Rational) = Terminating_ r (IsTerminating r) :: Constraint
type family Terminating_ r (b :: Bool):: Constraint where
  Terminating_ r 'True = (IsTerminating r ~ 'True, KnownRational r)
  Terminating_ r 'False = L.TypeError
    ('L.Text "Unexpected: IsTerminating ("
     'L.:<>: 'L.ShowType r 'L.:<>: 'L.Text ") ~ 'False")

data TerminatingSym0 :: Rational ~> Constraint
type TerminatingSym1 :: Rational -> Constraint

type instance Apply TerminatingSym0 i = Terminating i
type TerminatingSym1 i = Terminating i

-- | This is essentially the same as @('KnownRational' r, 'IsTerminating' r ~ 'False')@,
-- except with a nicer error message when @'IsTerminating' r ~ 'False'@.
type NonTerminating (r :: Rational) = NonTerminating_ r (IsTerminating r) :: Constraint
type family NonTerminating_ r (b :: Bool):: Constraint where
  NonTerminating_ r 'False = (IsTerminating r ~ 'False, KnownRational r)
  NonTerminating_ r 'True  = L.TypeError
    ('L.Text "Unexpected: IsTerminating ("
     'L.:<>: 'L.ShowType r 'L.:<>: 'L.Text ") ~ 'True")

data NonTerminatingSym0 :: Rational ~> Constraint
type NonTerminatingSym1 :: Rational -> Constraint

type instance Apply NonTerminatingSym0 i = NonTerminating i
type NonTerminatingSym1 i = NonTerminating i


-- | Whether the type-level 'Rational' is terminating. That is, whether
-- it can be fully represented as a finite decimal number.
type IsTerminating (r :: Rational) = IsTerminating_ (Den r) :: Bool
type family IsTerminating_ (n :: Natural) :: Bool where
  IsTerminating_ 5 = 'True
  IsTerminating_ 2 = 'True
  IsTerminating_ 1 = 'True
  IsTerminating_ d = IsTerminating_5 d (L.Mod d 5)

-- @IsTerminating_5@ is here to prevent @IsTerminating_@ from recursing into
-- @IsTerminating_ (Div d 5)@ if it would diverge.
type family IsTerminating_5 (d :: Natural) (md5 :: Natural) :: Bool where
  IsTerminating_5 d 0 = IsTerminating_ (L.Div d 5)
  IsTerminating_5 d _ = IsTerminating_2 d (L.Mod d 2)

-- @IsTerminating_2@ is here to prevent @IsTerminating_5@ from recursing into
-- @IsTerminating_ (Div d 2)@ if it would diverge, and also to prevent calculating
-- @Mod d 2@ unless necessary.
type family IsTerminating_2 (d :: Natural) (md2 :: Natural) :: Bool where
  IsTerminating_2 d 0 = IsTerminating_ (L.Div d 2)
  IsTerminating_2 _ _ = 'False

data IsTerminatingSym0 :: Rational ~> Bool
type IsTerminatingSym1 :: Rational -> Bool

type instance Apply IsTerminatingSym0 i = IsTerminating i
type IsTerminatingSym1 i = IsTerminating i

-- | Term-level version of the "IsTerminating" function.
--
-- NB: 'error's if the 'P.Rational' denominator is 0.
isTerminating :: P.Rational -> Bool
isTerminating = \(unsafeReduce -> _ P.:% d) -> go d
  where
    go = \case
      5 -> True
      2 -> True
      1 -> True
      n | (q, 0) <- P.divMod n 5 -> go q
        | (q, 0) <- P.divMod n 2 -> go q
      _ -> False

{-# COMPLETE SRationalTerminating, SRationalNonTerminating #-}

-- | Matches a 'SRational' that is 'Terminating'.
pattern SRationalTerminating
  :: forall r. () => (Terminating r) => SRational r
pattern SRationalTerminating <-
  (termination Nothing (Just Dict) -> Just (Dict :: Dict (Terminating r)))

-- | Matches a 'SRational' that is 'NonTerminating'.
pattern SRationalNonTerminating
  :: forall r. () => (NonTerminating r) => SRational r
pattern SRationalNonTerminating <-
  (termination (Just Dict) Nothing -> Just (Dict :: Dict (NonTerminating r)))

--------------------------------------------------------------------------------

-- | Comparison of type-level 'Rational's, as a function.
type CmpRational (a :: Rational) (b :: Rational) =
  CmpRational_ (Reduce a) (Reduce b) :: Ordering
type family CmpRational_ (a :: Rational) (b :: Rational) :: Ordering where
  CmpRational_ a a = 'EQ
  CmpRational_ (an :% ad) (bn :% bd) =
    P.Compare (an P.* I.FromNatural bd) (bn P.* I.FromNatural ad)

-- | "Data.Type.Ord" support for type-level 'Rational's.
type instance Compare (a :: Rational) (b :: Rational) = CmpRational a b

instance OrdS.POrd Rational where
  type Compare a b = CmpRational a b

instance OrdS.SOrd Rational where
  sCompare sa sb = case compare (fromSing sa) (fromSing sb) of
    LT -> unsafeCoerce OrdS.SLT
    EQ -> unsafeCoerce OrdS.SEQ
    GT -> unsafeCoerce OrdS.SGT

instance EqS.PEq Rational where
  type a == b = CmpRational a b P.== 'EQ

instance EqS.SEq Rational where
  sa %== sb
    | fromSing sa P.== fromSing sb = unsafeCoerce STrue
    | otherwise                    = unsafeCoerce SFalse

--------------------------------------------------------------------------------

-- | This class gives the 'SRational' associated with a type-level 'Rational'.
--
-- There are instances for every 'Reduced' 'Rational'.

-- Note: Ideally, 'KnownRational' wouldn' exist and the 'Constraint's metioned
-- there would be superclasses to 'KnownRational_'. However, 'withDict' doesn't
-- allow superclasses, so we treat 'KnownRational_' as internal an export
-- 'KnownRational' only.
class KnownRational_ (r :: Rational) where
  rationalSing_ :: SRational r

-- | Type-level 'Rational's satisfying 'KnownRational' can be converted to
-- 'SRational's using 'rationalSing'. Moreover, 'KnownRational' implies that
-- the numerator is a 'I.KnownInteger', and that the denominator is a
-- 'L.KnownNat'.
type KnownRational (r :: Rational) =
  ( KnownRational_ r
  , Reduced r ~ r
  , I.KnownInteger (Num r)
  , L.KnownNat (Den r)
  )

-- | Convert an implicit 'KnownRational' to an explicit 'SRational'.
rationalSing :: KnownRational r => SRational r
rationalSing = rationalSing_ -- The difference is in the constraint.
{-# INLINE rationalSing #-}

instance (KnownRational r) => KnownRational_ r where
  rationalSing_ =
    UnsafeSRational (demote @(Num r) P.:% L.natVal (Proxy @(Den r)))

-- | Normalized term-level "Prelude" 'P.Rational' representation of the
-- type-level 'Rational' @r@.
rationalVal :: forall r proxy. KnownRational r => proxy r -> P.Rational
rationalVal _ = case rationalSing :: SRational r of UnsafeSRational x -> x
{-# INLINE rationalVal #-}

-- | This type represents unknown type-level 'Rational'.
data SomeRational = forall n. KnownRational n => SomeRational (Proxy n)


-- | Convert a term-level "Prelude".'P.Rational' into an
-- extistentialized 'KnownRational' wrapped in 'SomeRational'.
--
-- NB: 'error's if a non-'Reduced' 'P.Rational' is given.
someRationalVal :: P.Rational -> SomeRational
someRationalVal = \r ->
  withSomeSRational r $ \(sr :: SRational r) ->
  withKnownRational sr $ SomeRational (Proxy @r)

instance Eq SomeRational where
  SomeRational x == SomeRational y =
    rationalVal x P.== rationalVal y
  {-# INLINE (==) #-}

instance Ord SomeRational where
  compare (SomeRational x) (SomeRational y) =
    compare (rationalVal x) (rationalVal y)
  {-# INLINE compare #-}

-- | As for "Prelude".'P.Rational'.
instance Show SomeRational where
  showsPrec p (SomeRational i) = showsPrec p (rationalVal i)

-- | As for "Prelude".'P.Rational'.
instance Read SomeRational where
  readPrec = fmap someRationalVal Read.readPrec

--------------------------------------------------------------------------------

-- | We either get evidence that this function was instantiated with the
-- same type-level 'Rational's, or 'Nothing'.
sameRational
  :: forall a b proxy1 proxy2
  .  (KnownRational a, KnownRational b)
  => proxy1 a
  -> proxy2 b
  -> Maybe (a :~: b)
sameRational _ _ = testEquality (rationalSing @a) (rationalSing @b)

-- | Like 'sameRational', but if the type-level 'Rational's aren't equal, this
-- additionally provides proof of 'LT' or 'GT'.
cmpRational
  :: forall a b proxy1 proxy2
  .  (KnownRational a, KnownRational b)
  => proxy1 a
  -> proxy2 b
  -> OrderingI a b
cmpRational x y = case compare (rationalVal x) (rationalVal y) of
    EQ -> case unsafeCoerce Refl :: CmpRational a b :~: 'EQ of
      Refl -> case unsafeCoerce Refl :: a :~: b of
        Refl -> EQI
    LT -> case unsafeCoerce Refl :: (CmpRational a b :~: 'LT) of
      Refl -> LTI
    GT -> case unsafeCoerce Refl :: (CmpRational a b :~: 'GT) of
      Refl -> GTI

--------------------------------------------------------------------------------

-- | Singleton type for a type-level 'Rational' @r@.
newtype SRational (r :: Rational) = UnsafeSRational P.Rational
type role SRational nominal

-- | A explicitly bidirectional pattern synonym relating an 'SRational' to a
-- 'KnownRational' constraint.
--
-- As an __expression__: Constructs an explicit @'SRational' r@ value from an
-- implicit @'KnownRational' r@ constraint:
--
-- @
-- 'SRational' @r :: 'KnownRational' r => 'SRational' r
-- @
--
-- As a __pattern__: Matches on an explicit @'SRational' r@ value bringing
-- an implicit @'KnownRational' r@ constraint into scope:
--
-- @
-- f :: 'SRational' r -> ..
-- f SRational = {- SRational r in scope -}
-- @
pattern SRational :: forall r. () => KnownRational r => SRational r
pattern SRational <- (knownRationalInstance -> KnownRationalInstance)
  where SRational = rationalSing_

-- | An internal data type that is only used for defining the 'SRational' pattern
-- synonym.
data KnownRationalInstance (r :: Rational) where
  KnownRationalInstance :: KnownRational r => KnownRationalInstance r

-- | An internal function that is only used for defining the 'SRational' pattern
-- synonym.
knownRationalInstance :: SRational r -> KnownRationalInstance r
knownRationalInstance si = withKnownRational si KnownRationalInstance

instance Show (SRational r) where
  showsPrec p (UnsafeSRational r) =
    showParen (p >= appPrec1) $
      showString "SRational @" .
      showsPrecLit appPrec1 r

instance Eq (SRational r) where
  _ == _ = True
  {-# INLINE (==) #-}

instance Ord (SRational r) where
  compare _ _ = EQ
  {-# INLINE compare #-}

instance TestEquality SRational where
  testEquality = decideEquality
  {-# INLINE testEquality #-}

instance TestCoercion SRational where
  testCoercion = decideCoercion
  {-# INLINE testCoercion #-}

-- | Return the term-level "Prelude".'P.Rational' number corresponding to @r@.
fromSRational :: SRational r -> P.Rational
fromSRational (UnsafeSRational r) = r

-- | Convert an explicit @'SRational' r@ value into an implicit
-- @'KnownRational_' r@ constraint.
withKnownRational_
  :: forall r rep (x :: TYPE rep)
  .  SRational r
  -> (KnownRational_ r => x)
  -> x
withKnownRational_ = withDict @(KnownRational_ r)

-- | Convert an explicit @'SRational' r@ value into an implicit
-- @'KnownRational' r@ constraint.
withKnownRational
  :: forall r rep (x :: TYPE rep)
  .  SRational r
  -> (KnownRational r => x)
  -> x
withKnownRational sr x
  | n P.:% d <- fromSRational sr
  , I.SomeInteger @n _ <- I.someIntegerVal n
  , L.SomeNat @d _ <- L.someNatVal (P.fromInteger d)
  , Refl <- sReducedRefl sr
  , -- These unsafeCoreces are safe because this module doesn't offer any tool
    -- for constructing non-reduced SRationals. Very unsafe otherwise.
    Refl <- unsafeCoerce Refl :: d :~: Den r
  , Refl <- unsafeCoerce Refl :: n :~: Num r
  = withKnownRational_ sr x

-- | Convert a "Prelude".'P.Rational' number into an @'SRational' n@ value,
-- where @n@ is a fresh type-level 'Rational'.
withSomeSRational
  :: forall rep (x :: TYPE rep). P.Rational -> (forall r. SRational r -> x) -> x
withSomeSRational (unsafeReduced -> !r) k = k (UnsafeSRational r)
-- It's very important to keep this NOINLINE! See the docs at "GHC.TypeNats"
{-# NOINLINE withSomeSRational #-}

--------------------------------------------------------------------------------

type instance Sing = SRational

instance (KnownRational r) => SingI (r :: Rational) where
  sing = rationalSing
  {-# INLINE sing #-}

instance SingKind Rational where
  type Demote Rational = P.Rational
  fromSing = fromSRational
  {-# INLINE fromSing #-}
  toSing r = withSomeSRational r SomeSing
  {-# INLINE toSing #-}

instance SDecide Rational where
  UnsafeSRational l %~ UnsafeSRational r =
    -- This is safe because this library doesn't expose any tool to construct
    -- non-normalized SRationals. Otherwise, very unsafe.
    case l P.== r of
      True  -> Proved (unsafeCoerce Refl)
      False -> Disproved (\Refl -> error "Rational: SDecide")

--------------------------------------------------------------------------------
-- Extra stuff that doesn't belong here.

data Dict (c :: Constraint) where
  Dict :: c => Dict c