Possible Duplicate:
How to create a type bounded within a certain range
I have the data type:
data Expr = Num Int
| Expression Expr Operator Expr
In the context of the problem, the numbers that (Num Int) will represent are single digit only. Is there a way to ensure that restriction within the type declaration?
Of course we could define a function to test whether the Expr is valid, but it would be nice to have the type system handle it.
You can use an abstract data type with a smart constructor:
newtype Digit = Digit { digitVal :: Int }
deriving (Eq, Ord, Show)
mkDigit :: Int -> Maybe Digit
mkDigit n
| n >= 0 && n < 10 = Just (Digit n)
| otherwise = Nothing
If you put this in another module and don't export the Digit constructor, then client code can't construct values of type Digit outside of the range [0,9], but you have to manually wrap and unwrap it to use it. You could define a Num instance that does modular arithmetic, if that would be helpful; that would also let you use numeric literals to construct Digits. (Similarly for Enum and Bounded.)
However, this doesn't ensure that you never try to create an invalid Digit, just that you never do. If you want more assurance, then the manual solution Jan offers is better, at the cost of being less convenient. (And if you define a Num instance for that Digit type, it will end up just as "unsafe", because you'd be able to write 42 :: Digit thanks to the numeric literal support you'd get.)
(If you don't know what newtype is, it's basically data for data-types with a single, strict field; a newtype wrapper around T will have the same runtime representation as T. It's basically just an optimisation, so you can pretend it says data for the purpose of understanding this.)
Edit: For the more theory-oriented, 100% solution, see the rather cramped comment section of this answer.
Since there are only ten possibilities, you could use Enum to specify all of them.
data Digit = Zero | One | Two deriving (Enum, Show)
Then you'd have to use fromEnum to treat them as numbers.
1 == fromEnum One
Similarly, using toEnum you can get a Digit from a number.
toEnum 2 :: Digit
We can go even further and implement Num.
data Digit = Zero | One | Two deriving (Enum, Show, Eq)
instance Num Digit where
fromInteger x = toEnum (fromInteger x) :: Digit
x + y = toEnum $ fromEnum x + fromEnum y
x * y = toEnum $ fromEnum x * fromEnum y
abs = id
signum _ = 1
Zero + 1 + One == Two
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With