A more generic version allows you to parameterize based on the bound:
newtype BoundedInt b = BoundedInt Int
class Bound b where
boundRange :: t b -> (Int, Int)
fromBounded :: BoundedInt b -> Int
fromBounded (BoundedInt x) = x
toBounded :: Bound b => Int -> BoundedInt b
toBounded x =
let result = BoundedInt x
(minb, maxb) = boundRange result
in if minb <= result && result <= maxb
then result
else error $ "Out of range: " ++ show x
instance Bound b => Num (BoundedInt b) where
abs = toBounded . abs . fromBounded
negate = toBounded . negate . fromBounded
signum = toBounded . signum . fromBounded
x + y = toBounded (fromBounded x + fromBounded y)
x - y = toBounded (fromBounded x - fromBounded y)
x * y = toBounded (fromBounded x * fromBounded y)
fromInteger = toBounded . fromInteger
This assumes you want exceptions for overflow. Nowadays, you can put numbers in the type system, obviating the need for a "Bound" class, but I'm not familiar with it yet. The implementation above will also silently overflow given large enough bounds.