There are several important reasons why it is found desirable to associate a type with constants and variables:
Constraints may be imposed on named types: for example a range constraint for a scalar type, or an index constraint for an array type. In general, constraints define certain requirements whose satisfaction is to be checked dynamically. A subtype name serves as an abbreviation for a type name together with a constraint associated with the type. Several difficulties in the types of Pascal that have been noted by Habermann and others [Hab 73, WSH 77] are overcome in Ada by the notion of subtype.
In contrast, objects that have different subtypes of the same type are compatible: the value of an object may be assigned to a variable that has the same type, whether or not the object and the variable have the same subtype. Constraints are normally checked at execution time, although in many cases these checks can be done at compilation time, in anticipation.
Certain explicit conversions are allowed between closely related types. Such explicit conversions are defined among numeric types, among sufficiently similar array types, and among derived types of the same family. Being explicit, these conversions are safe. On the other hand, no implicit conversion is possible among user-defined types.
Parameterization at compilation time is achieved by the very powerful mechanism of generic units. Whereas parameterization at execution time by index bounds and discriminants is limited to scalar values, the parameters of generic units can even be subprograms and types. For example, we could model the length of a stack by a discriminant; but, to allow for different types of elements, we would need to define stacks within a generic package and have the element type be a generic parameter. We could then create several instances of the generic package, for example one for stacks of integers, one for stacks of characters, and so on.