> let's say that some integer must be between 10 and 100, do you use type checks for this?
Yep. In particular, I would use:
- A "wrapper" type around an unsigned byte (we don't need negatives, or a whole machine word)
- A "newtype" feature, to replace the wrapper with a Byte after type-checking (Haskell calls this "newtype"; Scala calls this "opaque type aliases").
- A private/unexported/scoped constructor, to prevent arbitrary Byte values getting wrapped
- A "smart constructor" which checks the bounds of a given Byte, returning a 'Maybe MyBoundedIntType' or some other type-checked error mechanism (Scala's 'Try[MyBoundedIntType]' works well).
- Polymorphism/overloading to call that smart constructor of various numeric types (char, int, long, signed, unsigned, etc.)
In Scala that would look something like:
opaque type MyBoundedIntType = Char
object MyBoundedIntType {
def apply(c: Char): Try[MyBoundedIntType] =
if (c >= 10 && c <= 100)
Success(c)
else
Failure(new IllegalArgumentException(s"Value ${c.toInt} outside range [10, 100]"))
def apply(i: Int ): Try[MyBoundedIntType] = Try(i.toChar).flatMap(MyBoundedIntType(_))
def apply(l: Long): Try[MyBoundedIntType] = Try(l.toChar).flatMap(MyBoundedIntType(_))
}
In Haskell:
module MyModule (MyBoundedIntType(), toByte, MakeBounded(..)) where
newtype MyBoundedIntType = MBIT { toByte :: Word8 }
class MakeBounded t where
mkBounded :: t -> Either String MyBoundedIntType
instance MakeBounded Word8 where
mkBounded b | b >= 10 && b <= 100 = Right (MBIT b)
mkBounded b | otherwise = Left ("Value " ++ show b ++ " not in range [10, 100]")
instance MakeBounded Int where
mkBounded i = toWord8 i >>= mkBounded
instance MakeBounded Integer where
mkBounded i = toInt i >>= mkBounded