String
object,
tree type, etc.
[1,true,2,false,3,true] can be
programmed in
an untyped language, but this is dangerous and it would be much
preferred to represent this as [(1,true),(2,false),(3,true)] :
(int * bool) list
Y-combinator cannot be typed!
int, float, ..
'a -> 'b, 'a * 'b
type
struct and typedef
Class ..., Object ...
raises SnorkFail on Java methods)
'a -> 'a
ColorPoint to a
function which expects a Point.
int -x,y-> int indicating variables x
and y assigned to in this function (the raises
SnorkFail is a form of effect type)
x:Point, a
concrete class analysis produces a set such as
{Point, ColorPoint, SnorkPoint}
This means at
run-time x could
either be a Point, a ColorPoint, or a
SnorkPoint (and, no other). Useful in optimization.
int -> { x:int | odd(x)
} for a function returning odd numbers.
We will
use the "T" prefix to indicate a typed version of an
untyped language
previously studied. Thus, we have TD,
TDS, TDR,
TDSR, TDOB, TDX,
TDSRX, ...
There are too many languages to look at,
so we consider only TD for a warm-up, and then do
full-blown TDSRX.
How much explicit type information? How much type information is the program text to be decorated with, and how much is inferred by the compiler? A spectrum of possibilities exists.
# function x -> x;; - : 'a -> 'a = <fun> # ((function (x : int) -> (x : int)) : int -> int);; - : int -> int = <fun>We will concentrate on the C/Pascal view.
For a typed language, the compiler should typecheck the program before generating code.
3+4)
Gamma |- e : taureads "in type environment
Gamma, e is of
type tau". A type environment gives the types of free
variables in e, and is a list
Gamma = x_1 : tau_1, ..., x_n : tau_nif
x is listed twice in Gamma, the rightmost
(innermost) binding is the
proper type. We write Gamma(x) = tau to indicate that
tau is the innermost type for x in Gamma.
tau in concrete syntax are
tau ::= Int | Bool | tau -> tauIn abstract syntax within Caml, they are
type dtype = Int | Bool | Arrow of dtype * dtypeThe expressions of TD are almost identical D, but functions differ slightly in that they come with type decoration:
Function x : tau -> e : tau'which in the abstract syntax is
Function of ide * dtype * expr * dtype
------------------------ (Hyp) Gamma |- x : tau for Gamma(x) = tau ----------------------------- (Int) Gamma |- n : Int for n an integer ----------------------------- (Bool) Gamma |- b : Bool for b either True or False Gamma |- e : Int , Gamma |- e' : Int ---------------------------------------- (+) Gamma |- e + e' : Int Gamma |- e : Int , Gamma |- e' : Int ---------------------------------------- (-) Gamma |- e - e' : Int Gamma |- e : Int, Gamma |- e' : Int -------------------------------------------- (=) Gamma |- e = e' : Bool (note, equality only typechecks for integers, not booleans.) (And, Or, NOT rules should be obvious) Gamma |- e : Bool, Gamma |- e' : tau, Gamma |- e'' : tau ------------------------------------------------------------------ (If) Gamma |- If e Then e' Else e'' : tau Gamma, x : tau |- e : tau' ------------------------------------------------ (Function) Gamma |- (Function x : tau -> e : tau') : tau -> tau' Gamma |- e : tau -> tau', Gamma |- e' : tau -------------------------------------------------- (Appl) Gamma |- e e' : tau'Just as in operational semantics, a derivation of
Gamma |- e : tau is a tree of rule applications where the leaves are
axioms (Hyp, Int or Bool rules) and the root is Gamma |- e : tau.
|- (Function x : Int -> (Function y : Bool -> (If y Then x Else x+1) :
Int) : Bool -> Int) : Int -> Bool -> Int
Because by the function rule, it suffices to prove
x:Int |- Function y: Bool -> (If y Then x Else x+1): Int) : Bool->Int
Because by the function rule again, it suffices to prove
x:Int, y: Bool |- If y Then x Else x+1 : Int
Because by the If rule, it suffices to prove
x:Int, y: Bool |- y : Bool
x:Int, y: Bool |- x : Int
x:Int, y: Bool |- x+1 : Int
all of which either follow by the hypothesis rule or + and hypothesis.
Given the above and letting
f = (Function x : Int -> (Function y: Bool -> (If y Then x Else x+1): Int) : Bool->Int)we then have
|- f 5 True : Int
Because by the application rule,
|- f : Int -> Bool -> Int
(which we derived above)
|- 5 : Int by the Int rule
And thus
|- f 5 : Bool -> Int by the application rule.
Given this and
|- True : Bool by the Bool rule
we can get
|- f 5 True : Int by the application rule.
Y combinator.
Theorem (Type Soundness) If |- e : tau then
in the process of evaluating e, a stuck state is never reached.
0 (Function x -> x) or (Function x -> x)
+ 4
typeCheck, should take a type environment Gamma
and expression e as input and either return its type or raise
an exception
indicating e is not well-typed in environment Gamma.
Gamma and expression e down, and
expect the result type tau back up.
typecheck : envt *
expr -> dtype.
gamma : envt can be implemented as a
(ide * dtype) list, with the most recent item at the
front of the list.
let typecheck gamma e = match e with
Var x => lookup gamma x (* look up first mapping of x in list gamma *)
| Function(Ide x,t,e,t') =>
if typecheck ((Ide x),t):: gamma) e = t'
then Arrow(t,t') else raise TypeError |
| Appl(e1,e2) => let Arrow(t1,t2) = typecheck gamma e1 in
if typecheck gamma e2 = t1
then t2 else raise TypeError
| Plus(e1,e2) => if typecheck gamma e1 = Int
and typecheck gamma e2 = Int
then Int else raise TypeError
...
The typechecker should faithfully implement the TD
type system:
Lemma (faithfulness of typechecker):
|- e : tau if and only if
typecheck [] e returns tau, and
typecheck [] e
raises a typeError exception, |- e : tau is
not provable for any tau.
typecheck function is a sound
implementation of the type system for TD.
We include just about every piece of syntax we have used up to now, except the DOB classes and objects. Here is the abstract syntax defined in terms of a Caml type.
type expr =
Var of ident | Function of ident * dtype * expr * dtype | Appl of expr * expr |
Letrec of ident * ident * dtype * expr * dtype |
Plus of expr * expr | Minus of expr * expr | Equal of expr * expr |
And of expr * expr| Or of expr * expr | Not of expr |
If of expr * expr * expr | Int of int | Bool of bool
Ref of expr | Set of expr * expr | Get of expr | Cell of int |
Record of (label * expr) list | Select of label * expr |
LetExn of ident * dtype * expr |
Raise of expr * expr | TryWith of expr * expr * ident * dtype * expr | Exn of int
and
dtype = Int | Bool | Arrow of dtype * dtype |
Rec of label * dtype list | Rf of dtype | Ex of dtype
Now we will proceed to define the rules (whew!) for TDSRX.
(( insert all of the TD rules here ))
Gamma, f : tau -> tau', x : tau |- e : tau'
---------------------------------------------------------- (LetRec)
Gamma |- (Let Rec f x : tau = e : tau') : tau -> tau'
Gamma |- e1 : tau1, ..., Gamma |- en : taun
--------------------------------------------------------------------- (Record)
Gamma |- { l1 = e1, ..., ln = en } : { l1 : tau1, ..., ln : taun}
Gamma |- e : { l1 : tau1, ..., ln : taun}
-------------------------------------------------------- (Projection)
Gamma |- e.li : taui, for i a number between 1 and n
Gamma |- e : tau
-------------------------------------------- (Ref)
Gamma |- Ref e : tau Ref
Gamma |- e : tau Ref, Gamma |- e' : tau
-------------------------------------------- (Set)
Gamma |- e := e' : tau
Gamma |- e : tau Ref
-------------------------------------------- (Get)
Gamma |- !e : tau
Gamma, xn : tau Exn |- e : tau'
----------------------------------------------- (LetExn)
Gamma |- (LetExn xn : tau Exn In e ) : tau'
Gamma |- xn : tau exn Gamma |- e' : tau
-------------------------------------------- (Raise)
Gamma |- Raise (xn(e')) : tau' (any type OK)
Gamma |- xn : tau' Exn, Gamma |- e : tau, Gamma, x : tau' |- e' : tau
-------------------------------------------------------------------------- (Try)
Gamma |- Try e with xn(x : tau') -> e' : tau
(Question: why is there no typechecking rule for Cell's?
Another question: why is there actually no need for Let Rec f
x syntax in TDSRX to write recursive
functions?)
Exercise: attempt to type some of the untyped programs we have studied
up to now, e.g. the Y combinator, Let and
sequencing abbreviations, the factorial example, and the encoding of
lists.
{ m : Int; n : Int } <: { m : Int } where
<: means "subtype of".
Function x -> x.l + 1This function takes as argument a record with field
l of
type Int. So, we could write it in a typed form as
Function x : {l : Int} -> (x.l + 1) : Int
In the untyped DR language the record passed into the
function could also include other fields besides l, and
the call
(Function x -> x.l + 1) {l = 4; l' = 6}
would generate no run-time errors. However, this would not type-check
by our TDSRX rules: the function argument type is different from the
type of the values passed.
Solution: Let us re-consider record types such as
{ m : Int; n : Int } to mean a record with at least m and
n fields of type Int, but possibly other fields as well,
of unknown type. Think about the previous record operations and their
types: under this interpretation of record typing, the (Record) and
(Projection) rules both still make sense.
Gamma |- e : { l1 : tau1; ...; ln : taun}
-------------------------------------------------------- (Sub-Rec)
Gamma |- e : { l1 : tau1; ...; ln : taum} for m less than n
This rule not as good as we could do. Consider the following example.
F = Function f -> f ({ x = 5; y = 6; z = 3}) + f({x = 6; y = 4})
Here the function f informally should take a record with at
least x and y fields, but also other fields
could be present.
Let us try to type the function F.
F : ({x : Int; y : Int} -> Int) -> Int
Consider the application F G for
G = Function r -> r.x + r.xNow, however, consider the type-checking of this function G at
G : {x : Int} -> Int
This does not exactly match the type of F's argument,
{x : Int; y : Int} -> Int, and so the type-check fails.
In fact we could have typed G a type {x : Int; y
: Int} -> Int by the DSRX rules, but its too late to know that
was the type we should have used.
The (Sub-Rec) rule is of no help here. What we need is a rule that says a function of record type argument may have fields added to its record argument type, as those fields will be ignored:
Gamma |- e : { l1 : tau1; ...; ln : taun} -> tau
------------------------------------------------------------------ (Sub-Function)
Gamma |- e : { l1 : tau1; ...; ln : taun; ...; lm : taum} -> tau
Using this rule, F G will indeed type-check. We need still other rules, though. Consider records inside of records:
{pt = {x = 4; y = 5}; clr = 0} : { pt : {x : Int}; clr : Int }
should be a valid typing since the y field will be
ignored. However, there is no rule to allow this typing, either!
tau <: tau' is read "tau is a subtype of
tau'", and means that an object of type tau
may also be considered an object of type tau'. The rule
added to the TD type system is
Gamma |- e : tau, |- tau <: tau' ------------------------------------------------------------------ (Sub) Gamma |- e : tau'(in place of the above subsumption rules) The STD subtyping rules used to determine if
tau <: tau' are
----------------------------------------(Sub Refl)
|- tau <: tau
|- tau <: tau', |- tau' <: tau''
----------------------------------------(Sub Trans)
|- tau <: tau''
|- tau0' <: tau0, |- tau1 <: tau1'
----------------------------------------(Sub Function)
|- tau0 -> tau1 <: tau0' -> tau1'
|- tau1 <: tau1' ... |- taun <: taun'
-------------------------------------------------------------------(Sub Record)
|- { l1 : tau1; ...; ln : taun; ... ; lm : taum} <: { l1 : tau1'; ...; ln : taun'}
For all of the examples discussed up to now, it should be clear that
this set of more general rules will work.
We briefly sketch how the typecheck function for STD may
be written.
The TD type-checker requires certain types to be identical, e.g. the
function domain type must be identical to the type of the function
argument in an application e e'.
Gamma |- e : tau -> tau', Gamma |- e' : tau -------------------------------------------------- (Appl) Gamma |- e e' : tau'In STD, at this point we will instead see if subtyping is possible:
typecheck(e') returns
tau'' for some type tau'', and then
tau'' <: tau is
checked via a function areSubtypes(tau'',tau). This produces a
valid proof because
Gamma |- e' : tau''
--------------------- (Sub)
Gamma |- e : tau -> tau', Gamma |- e' : tau
-------------------------------------------------- (Appl)
Gamma |- e e' : tau'
is a valid typing derivation. Other rules where the TD rules require
a type match similarly are
generalized to allow the (Sub) rule to be used.
Writing the areSubtypes function: exercise.
There may be a question as to whether typecheck(e) does
not sometimes raise a typeError exception when the
program is in fact typable. it is only using the subsumption rule in
certain spots. However, this is not the case, it suffices to use the
subsumption rule in these spots only.
Subtyping and Java/C++ types
{x: Int; y: Int; color:Int} and
{x: Int; y: Int} that weren't one of the two cases
above; then, they aren't subtypes but they are in STD.
'a and then
f arg -- if
f has type 'a -> 'b and arg has
type 'c, unify (equate) 'a and 'c.
Function x -> x
tau for expression e
(where |- e : tau) has the following property:
tau' such that |-
e : tau', for any context C for which
|- C[e : tau'] : tau''(for any
tau''), then
|- C[e : tau] : tau'''as well, for some
tau'''.
'a -> 'a for the identity function, which
can be shown to be a principal
type for it.
'a and
'c: replace one with the other everywhere.
'a = 'c and then solve them at the end.
tau ::= Int | Bool | tau -> tau | 'a | 'b | ...
E,
which constrain the behavior of the type variables.
Gamma |- e : tau \
E, the same as before but tacking a set of equations on
the side.
E is an equation like 'a = 'c.
tau \ { tau1 = tau1' , ..., taun = taun' }
Each tau = tau' is an equation on types, meaning
tau and tau' have the
same meaning as types. We will let E mean some arbitrary set of type
equations. For instance,
Int -> 'a \ { 'a = Int -> 'a1, 'a1 = Bool }
is an equational type. If you think about it, this is really the same
as the type
Int -> Int -> Boolwhen = is substituted for =. This is a step we are going to want to perform, so-called equational simplification. It is also possible to write "senseless" types like
Int -> 'a \ { 'a = Int -> 'a1, 'a = Bool }
which cannot be types since they imply functions and booleans are the
same type! Such equation sets are deemed inconsistent, and
will be equated with failure of type inference.
There are also possibilities for circular (self-referential) types
that don't quite look inconsistent:
Int -> 'a \ { 'a = Int -> 'a }
Gamma is as in the TD rules, asserting
e.g. x : tau for variable x; the type
tau is a simple (non-equational) type.)
Gamma(x) = tau
------------------------------- (Hyp)
Gamma |- x : tau \ E
----------------------------- (Int)
Gamma |- n : Int \ emptyset for n an integer
----------------------------- (Bool)
Gamma |- b : Bool \ emptyset for b either True or False
Gamma |- e : tau \ E , Gamma |- e' : tau' \ E'
---------------------------------------------------------------------- (+)
Gamma |- e + e' : Int \ E union E' union {tau = Int, tau' = Int }
Gamma |- e : tau \ E , Gamma |- e' : tau' \ E'
-------------------------------------------------------------------- (-)
Gamma |- e - e' : Int \ E union E' union {tau = Int, tau' = Int }
Gamma |- e : tau \ E , Gamma |- e' : tau' \ E'
-------------------------------------------- (=)
Gamma |- e = e' : Bool \ E union E' union {tau = Int, tau' = Int }
(And, Or, NOT rules should be obvious)
Gamma |- e : tau \ E , Gamma |- e' : tau' \ E' , Gamma |- e'' : tau'' \ E''
------------------------------------------------------------------------------------------------- (If)
Gamma |- If e Then e' Else e'' : 'd \ E union E' union E'' union {tau = Bool, tau' = tau'' = 'd}
Gamma, x : 'a |- e : tau \ E
--------------------------------------------------------- (Function)
Gamma |- Function x -> e : 'a -> tau \ E
Gamma |- e : tau \ E , Gamma |- e' : tau' \ E'
--------------------------------------------------------------- (Appl)
Gamma |- e e' : 'a \ E union E' union { tau = tau' -> 'a }
Int =
Bool which denote type errors;
Closure(E), the Equational closure of set E
tau0 -> tau0' = tau1 -> tau1' in E, add
tau0 = tau1 and tau0' = tau1' to E
tau0 = tau1 and tau1 =
tau2, add tau0 = tau2 to E (transitivity)
E (note, we
implicitly will use the symmetry property on these equations).The closure serves to uncover inconsistencies. For instance
Closure({ 'a = Int -> 'b , 'a = Int -> Bool, 'b = Int}) =
{ 'a = Int -> 'b , 'a = Int -> Bool, 'b = Int,
Int -> 'b = Int -> Bool, Int = Int, 'b = Bool, Int = Bool }
directly uncovering the inconsistency Int = Bool.
Fact: the closure of E can always be
quickly computed.
After computing the closure, the constraints are consistent if
Int = Bool, Bool = tau -> tau',
or Int = tau -> tau'),
Definition: Equation solution algorithm
Given tau \ E,
RepeatThis resulting type is the type inferred by Caml.
replace some type variable'aintauwithtau', provided'a = tau'ortau' = 'aoccurs in E and eitherUntil no more such replacements are possible.
tau'is not a type variable, ortau'is a type variable'bwhich is lexicographically after'a.
This algorithm is flawed, however: it may be that these replacements may continue forever. This is the case when there is a circular type in E. Recall the above example
Int -> 'a \ { 'a = Int -> 'a }
--this produces the nonterminating chain
Int -> Int -> 'a \ { 'a = Int -> 'a }
Int -> Int -> Int -> 'a \ { 'a = Int -> 'a }
Int -> Int -> Int -> Int -> 'a \ { 'a = Int -> 'a }
...
Solution: Check for cycles in the equations before
solving them as above.
G based on E.
E
'a node to the 'b
node if there is an equation 'a = tau' in
E, and 'b occurs in
tau'.
typeError if there is a
cycle in G for which there is at least one edge representing a
constraint that isn't just between type variables ('a = 'b).
|- e : tau \ E (such a proof always exists)
E := Closure(E).
E is immediately inconsistent; if so, raise typeError
E using the above algorithm; raise typeError if there
is a cycle.
E by the above equation solution algorithm. This
algorithm will always terminate if there are no cycles in E.
tau' for e produced by
the solution algorithm.
Let x = Function y -> y In x True; x 0in Caml, this program would type-check: different uses of
Function y ->
y can have different types. However, consider ED's behavior:
expanding the definition of Let, we get
(Function x -> x True; x 0) (Function y -> y)
|- (Function x -> x True; x 0) : 'a -> 'c \ {'a = Bool -> 'b, 'a = Int -> 'c }
But by the closure we get Int = Bool: BAD!!The problem in this case:
Let body
used the same type variable 'a
Function y -> y we know that 'a can be anything.
'a can be different things.
Let and let-polymorphism
Let syntax to PED, and
include a special typing rule for Let.
Gamma |- e : tau \ E, Gamma, x : forall 'a1...'an. tau' |- e' : tau'' \ E' , ----------------------------------------------------------------------------------- (Let) Gamma |- Let x = e in e' : tau'' \ E' Where tau' is a solution of |- e : tau \ E using the above algorithm, and tau' has free type variables 'a1, .. 'an that do not occur in Gamma.
Type Schema
forall 'a1..'an. tau
forall 'a. 'a -> 'a.
'a1,..., 'an are considered
boundby this type expression.
------------------------------------------------------------ (Let-Inst) Gamma, x : forall 'a1...'an. tau' |- x : R(tau') \ emptyset whereSince these names are fresh each time x is used, the different uses won't conflict like above.R(tau')is a renaming of the variables'a1..'anto fresh names.
Example: lets type the Let version of
the program
Let x = Function y -> y In (Function x -> x True; x 0)from above.
|- Function y -> y : 'a -> 'a \ emptysetThis constraint set trivially has the solution type
'a -> 'a.
Thus, we then typecheck the Let body under the assumption
that x has type forall 'a. 'a -> 'a.
x : forall 'a. 'a -> 'a |- x : 'b -> 'b \ emptysetby
(Let-Inst) and then
x : forall 'a. 'a -> 'a |- x True : 'c \ { 'b -> 'b = Bool -> 'c }
Similarly,
x : forall 'a. 'a -> 'a |- x 0 : 'e \ { 'd -> 'd = Int -> 'e }
The key in the above is this use of x gets a
different type variable, 'd, by the
(Let-Inst) rule. Putting the two together, the type is
something like
x : forall 'a. 'a -> 'a |- x True; x 0 : 'e \ { 'b -> 'b = Bool -> 'c, 'd -> 'd = Int -> 'e }
which by the (Let) rule then produces
|- Let x = Function y -> y In (Function x -> x True; x 0)
: 'e \ { 'b -> 'b = Bool -> 'c, 'd -> 'd = Int -> 'e }
Since 'b and 'd are different variables, we
don't get the conflict we got previously.
D is not the best system to show off the power of replacing equality with subtyping: since the language does not have records, there is not any interesting subtyping that could happen! To show the usefulness of subtyping, we thus define the constraints in an environment where we have records, DRec. DRec pluc constraints is CDRec. We can contrast CDRec with the EDRec language which we did not study but you could imagine. Instead of types tau \ E for a set of equations E, CDRec has types
tau \ { tau1 <: tau1', .., taun <: taun' }
We will use the letter C to refer to a set of subtyping constraints.CDRec has the following set of type rules. These are direct generalizations of the ED rules, replacing = by <:. the <: is always in the direction of information flow.
Gamma(x) = tau
------------------------------- (HYP)
Gamma |- x : tau \ C
----------------------------- (Int)
Gamma |- n : Int \ emptyset for n an integer
----------------------------- (Bool)
Gamma |- b : Bool \ emptyset for b either True or False
Gamma |- e : tau \ C , Gamma |- e' : tau' \ C'
------------------------------------------------------- (+)
Gamma |- e + e' : Int \ C union union C' {tau <: Int, tau' <: Int }
Gamma |- e : tau \ C , Gamma |- e' : tau' \ C'
--------------------------------------- (-)
Gamma |- e - e' : Int \ C union union C' {tau <: Int, tau' <: Int }
Gamma |- e : tau \ C , Gamma |- e' : tau' \ C'
-------------------------------------------- (=)
Gamma |- e = e' : Bool \ C union C' union {tau <: Int, tau' <: Int }
(And, Or, Not rules should be obvious)
Gamma |- e : tau \ C , Gamma |- e' : tau' \ C' , Gamma |- e'' : tau'' \ C''
------------------------------------------------------------------------ (If)
Gamma |- If e Then e' Else e'' : 'd \ C union C' union C'' union {tau <: Bool, tau' <: 'd, tau'' <: 'd}
Gamma |- e1 : tau1 \ C1, ..., Gamma |- en : taun \ C2
--------------------------------------------------------------------- (Record)
Gamma |- { l1 = e1, ..., ln = en } : { l1 : tau1, ..., ln : taun} \ C1 union C2
Gamma |- e : tau \ C
-------------------------------------------------------- (Projection)
Gamma |- l.e : 'a \ C union { tau <: { l : 'a } }
Gamma, x : 'a |- e : tau \ C
--------------------------------------------------------- (Function)
Gamma |- Function x -> e : 'a -> tau \ C
Gamma, f : 'a -> 'b , x : 'a |- e : tau \ C
------------------------------------------------------------ (Rec)
Gamma |- Rec f = Function x -> e : 'a -> tau \ C union { tau <: 'b }
Gamma |- e : tau \ C , Gamma |- e' : tau' \ C'
---------------------------------------------------------- (Appl)
Gamma |- e e' : 'a \ C union C' union { tau <: tau' -> 'a }
These rules almost directly define the type inference procedure: the
proof can pretty much be built from the bottom (leaves) on up.
(Function r -> r.l + 1) {l = 5, m = True}
This program will need subtyping because intuitively the function will
only need a record with l field, but a record with
l,m fields is supplied.
The function types as
|- Function r -> r.l + 1 : 'a -> Int \ {'a <: {l: 'b}, 'b <: Int}
and the application then has type
|- (Function r -> r.l + 1) {l = 5, m = True} : 'c \
{'a <: {l: 'b}, 'b <: Int, 'a -> Int <: {l : Int, m : Bool} -> 'c, Int <: 'c}
From the closure of this constraint set we get the constraints
{l : Int, m : Bool} <: 'a <: {l: 'b}
and so we get
{l : Int, m : Bool} <: {l: 'b}
which is fine since we can always ignore record fields.The type inference algorithm for constraints is similar to the equational algorithm, but no solution is found for the constraints, for reasons we will see below.
Q: Why didn't we solve the constraints??
A: Any substitution proceeds with possible loss of
generality. Consider e.g. constraint 'a <: tau, and the
possibility of substituting 'a with tau.
Well, this precludes the possiblity that the 'a position
be a subtype of tau, as the substitution in effect
asserted the equality of 'a and tau.
Weakness of constrained types: need to keep constraints around so types are hard to read.
Solution: constrained polymorphic types
forall 'a1,...,'an.tau \ C in the assumptions Gamma, in
place of the polymorphic types (type schemes) we had in the equational
version. Skip the details, they are involved.
Constrained polymorphic types are "very good" object types. Polymorphism needed to type inheritance.
Ref cells and polymorphism leads to some
conflicts; Caml has the let-value restriction to get around this problem.
let val x = ref (Function x -> x) in (x := (Function x: int -> x + 1)); !x true endRecall the way polymorphic functions were typechecked in ED: x will have the polymorphic type
forall 'a. 'a -> 'a ref, which means each use of x can pick a different type for 'a. In the above example, the first use of x can pick int for
'a, and the
second can pick bool for 'a, and the program will
typecheck. OOPS! the program will produce a run-time error!! What
Caml does in this case is it does not let the type of x be polymorphic:
it is type 'a -> 'a for some PARTICULAR, FIXED
'a, and the program thus does not typecheck.
SML has a special kind of type variable, the imperative type
variables of the form '1a. These type variables
indicate their values will be placed in cells, so they may only be
instantited with monotypes (type variable-free types). For example,
- Function x -> ref x; val it = Function : '1a -> '1a ref--since x is placed in a cell, it gets a type '1a. It is polymorphic but in a restricted sense. Now lets try
- it (Function x -> x);
std_in:17.1-17.14 Error: nongeneric weak type variable
it : ('0Z -> '0Z) ref
-- this attempted to instantiate '1a to be 'a ->
'a, an illegal move since 'a -> 'a contains the
type variable 'a. However,
- it 4; val it = ref 4 : int refis perfectly legal since
4 is of type int
which contains no type variables.
This issue shows how polymorphism would be very difficult in the C/Scheme language framework, as there all variables may behave as cells. This is perhaps the major reason why polymorphism is a relatively recent phenomenon in programming language design.