George Neuner said:
Now this is getting ridiculous.
It's not at all ridiculous. The fact that it seems ridiculous is a good
reason to educate people about what static typing does (and doesn't)
mean, and specifically that it doesn't imply any constraints on the kind
of behavioral property that's being checked; but only on the way that
the check occurs.
(As a subtle point here, we're assuming that this really is an error;
i.e., it shouldn't happen in a correct program. If this is validation
against a user mistake, then that's a different matter; obviously, that
would typically be done with a conditional statement like if, and
wouldn't count as a type error in any context.)
Regardless of implementation
language, Pascal's example is of a data dependent, runtime constraint.
99% of program errors (excluding only syntax errors and the like) are
data-dependent runtime errors. Types are a way of classifying that data
until such errors become obvious at compile time.
Is the compiler to forbid a person object from aging? If not, then
how does this person object suddenly become a different type when it
becomes 18? Is this conversion to be automatic or manual?
The object doesn't have a type (this is static typing, remember?), but
expressions do. The expressions have types, and those types are
probably inferred in such a circumstance (at least, this one would even
more of a pain without type inference).
If your marvelous static type
checker does the conversion automatically, then obviously types are
not static and can be changed at runtime.
There really is no conversion, but it does infer the correct types
automatically. It does so by having more complex rules regarding how to
determine the type of an expression. Java's current type system, of
course, is considerably less powerful. In particular, Java mostly
assures that a variable name has a certain type when used as an
expression, regardless of the context of that expression. Only with
some of the Java 5 features (capture conversion, generic method type
inference, etc) did this cease to be the case. It would be necessary to
drop this entirely to make the feature above work. For example,
consider the following in a hypothetical Java language augmented with
integer type ranges:
int{17..26} a;
int{14..22} b;
...
if (a < b)
{
// Inside this block, a has type int{17..21} and b has type
// int{18..22}
signContract(a); // error, because a might be 17
signContract(b); // no error, because even though the declared
// type of b is int{14..22}, it has a local
// type of int{18..22}.
}
(By the way, I hate this example... hence I've changed buyPorn to
signContract, which at least in the U.S. also requires a minimum age of
18. Hopefully, you'll agree this doesn't change the sunstantive
content.
Note that when a programmer is validating input from a user, they will
probably write precisely the kind of conditional expression that allows
the compiler to deduce a sufficiently restrictive type and thus allows
the type checker to succeed. Or if they don't do so -- say, for
example, the programmer produces a fencepost error by accidentally
entering "if (age >= 17)" instead of "if (age > 17)" -- then the
compiler will produce a type error as a result. If the programmer
stores the value into a variable of general type int (assuming such a
thing still exists in our augmented Java-esque language), then the
additional type information is forgotten, just like type information is
forgotten when a reference of type String is assigned to a reference of
type Object. In both cases, a runtime-checked cast will be required to
restore the lost type information.
Incidentally, I'm not saying that such a feature would be a good idea.
It generally isn't provided in languages specifically because it gets to
be a big pain to maintain all of the type specifications for this kind
of stuff. However, it is possible, and it is a static type system,
because expressions are assigned typed syntactically, rather than values
being checked for correctness at runtime.