(+) :: a -> a -> a
newtype RgVld (lo :: Nat) (hi :: Nat) a = RgVld { unRgVld :: a } deriving (Eq, Ord)
instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => Num (RgVld lo hi a) where (RgVld l) + (RgVld r) = chkVld "(+)" $ RgVld $ l+r (RgVld l) - (RgVld r) = chkVld "(-)" $ RgVld $ lr (RgVld l) * (RgVld r) = chkVld "(*)" $ RgVld $ l*r fromInteger n = chkVld "fromInteger" $ RgVld $ fromInteger n abs = id signum (RgVld v) = RgVld $ signum v
class CheckValidation a where chkVld:: String -> a -> a
(which can be useful for other types of checks) with a single function chkVld , which will pass values that fall into the range and throw an exception for values out of range. Its first argument is a substring in the exception message that shows the function that caused the exception. instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => CheckValidation (RgVld lo hi a) where chkVld whr r@(RgVld v) = let lo' = natVal (Proxy :: Proxy lo) hi' = natVal (Proxy :: Proxy hi) in if v < fromInteger lo' || v > fromInteger hi' then throw $ OutOfRangeException $ "out of range [" ++ show lo' ++ " .. " ++ show hi' ++ "], value " ++ show v ++ " in " ++ whr else r
data OutOfRangeException = OutOfRangeException String deriving Typeable instance Show OutOfRangeException where show (OutOfRangeException s) = s instance Exception OutOfRangeException
instance (KnownNat lo, KnownNat hi, Show a) => Show (RgVld lo hi a) where show (RgVld v) = show v instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a, Read a) => Read (RgVld lo hi a) where readsPrec w = \s -> case readsPrec ws of [] -> [] [(v,s')] -> [(chkVld "readsPrec" $ RgVld v, s')] instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => Bounded (RgVld lo hi a) where minBound = fromInteger $ natVal (Proxy :: Proxy lo) maxBound = fromInteger $ natVal (Proxy :: Proxy hi)
ab:: Int -> RgVld 1 20 Int ab = RgVld
*RangeValidation> ab 2 + ab 3 5 *RangeValidation> ab 12 + ab 13 *** Exception: out of range [1 .. 20], value 25 in (+) *RangeValidation>
*RangeValidation> ab 20 + ab 0 20 *RangeValidation>
class Num' ab where (+.) :: a -> b -> a (-.) :: a -> b -> a (*.) :: a -> b -> a
which implements arithmetic with operands of different types, and make RgVld its instance by defining instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => Num' (RgVld lo hi a) a where (RgVld l) +. r = chkVld "(+.)" $ RgVld $ l+r (RgVld l) -. r = chkVld "(-.)" $ RgVld $ lr (RgVld l) *. r = chkVld "(*.)" $ RgVld $ l*r
*RangeValidation> ab 5 -. (3 :: Int) 2 *RangeValidation>
- yes, the type of the number will have to be specified explicitly if this is a constant. fuel:: Double -> RgVld 0 15 Double fuel = RgVld
*RangeValidation> fuel 4.6 + fuel 4.5 9.1 *RangeValidation> fuel 9.1 + fuel 6 *** Exception: out of range [0 .. 15], value 15.1 in (+) *RangeValidation>
- Oh no no no. Pour over! {-# LANGUAGE DataKinds, KindSignatures, ScopedTypeVariables, MultiParamTypeClasses, FlexibleInstances #-} {-# LANGUAGE DeriveDataTypeable #-} module RangeValidation where import Data.Proxy import GHC.TypeLits import Data.Typeable import Control.Exception data OutOfRangeException = OutOfRangeException String deriving Typeable instance Show OutOfRangeException where show (OutOfRangeException s) = s instance Exception OutOfRangeException class CheckValidation a where chkVld:: String -> a -> a instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => CheckValidation (RgVld lo hi a) where chkVld whr r@(RgVld v) = let lo' = natVal (Proxy :: Proxy lo) hi' = natVal (Proxy :: Proxy hi) in if v < fromInteger lo' || v > fromInteger hi' then throw $ OutOfRangeException $ "out of range [" ++ show lo' ++ " .. " ++ show hi' ++ "], value " ++ show v ++ " in " ++ whr else r newtype RgVld (lo :: Nat) (hi :: Nat) a = RgVld { unRgVld :: a } deriving (Eq, Ord) instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => Num (RgVld lo hi a) where (RgVld l) + (RgVld r) = chkVld "(+)" $ RgVld $ l+r (RgVld l) - (RgVld r) = chkVld "(-)" $ RgVld $ lr (RgVld l) * (RgVld r) = chkVld "(*)" $ RgVld $ l*r fromInteger n = chkVld "fromInteger" $ RgVld $ fromInteger n abs = id signum (RgVld v) = RgVld $ signum v infixl 6 +.,-. infixl 7 *. class Num' ab where (+.) :: a -> b -> a (-.) :: a -> b -> a (*.) :: a -> b -> a -- (/.) :: a -> b -> a instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => Num' (RgVld lo hi a) a where (RgVld l) +. r = chkVld "(+.)" $ RgVld $ l+r (RgVld l) -. r = chkVld "(-.)" $ RgVld $ lr (RgVld l) *. r = chkVld "(*.)" $ RgVld $ l*r instance (KnownNat lo, KnownNat hi, Show a) => Show (RgVld lo hi a) where show (RgVld v) = show v instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a, Read a) => Read (RgVld lo hi a) where readsPrec w = \s -> case readsPrec ws of [] -> [] [(v,s')] -> [(chkVld "readsPrec" $ RgVld v, s')] instance (KnownNat lo, KnownNat hi, Num a, Ord a, Show a) => Bounded (RgVld lo hi a) where minBound = fromInteger $ natVal (Proxy :: Proxy lo) maxBound = fromInteger $ natVal (Proxy :: Proxy hi) -- examples ab:: Int -> RgVld 1 20 Int ab = RgVld fuel:: Double -> RgVld 0 15 Double fuel = RgVld
Source: https://habr.com/ru/post/253157/
All Articles