You'd need to use a smart constructor. You enforce your constraints by controlling what you export from a module.
module Bounded exposing (Bounded, fromInt, toInt)
type Bounded
= Bounded Int
fromInt : Int -> Maybe Bounded
fromInt n =
if n < 0 || n > 29 then
Nothing
else
Just (Bounded n)
toInt : Bounded -> Int
toInt (Bounded n) = n
Explanation
The Bounded
type is defined as a union type with one data constructor. When exporting the type we don't export the constructor, i.e. it's not part of the public API. This is known as an opaque type.
Users of the module can only ever create Bounded
types using fromInt
. fromInt
is called a smart constructor because it has some logic (the smarts) to enforce the constraints.
If you need to work with the integer it wraps you use toInt
. toInt
is guaranteed to always return an integer between 0 and 29 inclusive. There's no hidden backdoor that would allow any other integer to be wrapped.
The HaskellWiki has a nice write up on smart constructors.
Finally, Richard Feldman in his talk "Types and Tests at The Gilded Rose" goes through a nice example here that explains exactly what you want to do.