Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Restricting values in type constructors [duplicate]

Tags:

haskell

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.

like image 385
Doug Moore Avatar asked Dec 14 '11 19:12

Doug Moore


2 Answers

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.

like image 76
ehird Avatar answered Sep 26 '22 16:09

ehird


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
like image 32
Jan Avatar answered Sep 23 '22 16:09

Jan