packages feed

rerefined-0.3.0: src/Rerefined/Predicate/Relational/Value.hs

{-# LANGUAGE UndecidableInstances #-} -- for PredicateName
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE AllowAmbiguousTypes #-} -- for signPretty

module Rerefined.Predicate.Relational.Value where

import Rerefined.Predicate.Common
import Rerefined.Predicate.Relational.Internal
import GHC.TypeNats ( Natural, KnownNat, natVal' )
import TypeLevelShow.Utils
import TypeLevelShow.Natural
import GHC.TypeLits ( Symbol )

-- | Compare value to a type-level 'Natural' using the given 'RelOp'.
data CompareValue op (sign :: Sign) (n :: Natural)

-- | Precedence of 4 (matching base relational operators e.g. '>=').
instance Predicate (CompareValue op sign n) where
    type PredicateName d (CompareValue op sign n) = ShowParen (d > 4)
        (    "Value " ++ ShowRelOp op ++ ShowChar ' '
          ++ ShowSign sign ++ ShowNatDec n )

instance
  ( KnownNat n, Num a, Ord a
  , ReifyRelOp op, ReifySignedNat sign n
  , KnownPredicateName (CompareValue op sign n)
  ) => Refine (CompareValue op sign n) a where
    -- note that we show the reified 'Natural' rather than the coerced numeric
    -- type, as otherwise we'd need a @'Show' a@
    validate p a =
        validateBool p
            ("bad value")
            (reifyRelOp @op a (reifySignedNat @sign @n))

data Sign = Pos | Neg

type family ShowSign (sign :: Sign) :: Symbol where
    ShowSign Pos = ""
    ShowSign Neg = "-"

-- TODO do I add any KnownNat constraints anywhere here
class ReifySignedNat (sign :: Sign) (n :: Natural) where
    reifySignedNat :: (Num a, KnownNat n) => a

instance ReifySignedNat Pos n where
    reifySignedNat = fromIntegral (natVal' (proxy# @n))

instance ReifySignedNat Neg n where
    reifySignedNat = negate (fromIntegral (natVal' (proxy# @n)))