CS 641 Lecture -*- Outline -*- * The core language type system for SML ** Type checking how does one check the following? (Write rules on board) application f(0), g(x), (fn x:int => x+1)(3) if if E0 then E1 else E2 binding val x: T = E recursive bind val rec fact: (int-> int) = fn x:int => if x=0 then 1 else x*(fact (x-1)) Key ideas: need environment that records types of identifiers can reify this in rules as a list of (id, type) pairs expressions are checked from the bottom up (on syntax tree) basic rules: application, if if expression rule: both arms should have the same type ** Reconstructing (Infering) type declarations for monomorphic expressions What constraints can be placed on the subexpression types? lambda fn x => x+1 if if E0 then E1 else E2 binding val x = E recursive bind val rec fact = fn x => if x=0 then 1 else x*(fact (x-1)) Key ideas: similar to the work done in type checking anyway... identifiers in environment have known types (ignore overloading) arguments must have specified types for "if", condition : bool, arms must be same type (=) Formalization: system of typings and equations, solve equations for unknowns ** Polymorphism same piece of code operates on different types of data with approximately the same effect motivation: reuse of same definition in different contexts untyped (latently typed) language (LISP): flexible polymorphism not statically checked, so no aid to verification may still have representation independence statically typed language: less flexible *** explicit ------------------ val id = fn (t:type) => (fn (a:t) => a) id(int)(3) id(bool)(true) ------------------ note: some types omitted even here: on id itself, return types, ... *** implicit ------------------ val id = (fn (a:'t) => a) id(3) id(true) ------------------ can be thought of as an abbreviation for explicit form unbound type-denoting ids are type variables basic idea: implicit polymorphism is less verbose type-free definition ------------------ val id = (fn a => a) ------------------ lots of shades of implicit polymorphism, grey area ** Reconstruction of polymorphic types What is the type of the following? lambda fn a => a application (fn a => a) 3 if fn (b,c,d) => if b then c else d recursive bind val rec length = fn l => if null(l) then 0 else succ(length(tail(l))) Key ideas: type variables used whenever type is not known (from env.) to solve equations between type expressions with type vars use unification (as in prolog...) "if" expression constraints made explicit null(l) : bool both arms and whole if stmt have type 'c (gamma) for each function, write down its type using variables, types of arguments and application in those terms solve for type of whole expression (= type of recursively defined ident.) how do you check that the whole system is consistent? *** Basic algorithm [\var] lambda variables are assigned a new type variable, the pair is stored in env. [if] condition must have type bool, arms are unified in "if" expression [scope] body of lambda abstraction is inferred in context where lambda variable is as above [appl] in f(a) the type of f is unified against A->'b, where A is the type of a, 'b is new type variable *** Let expressions and Generic type variables **** heterogeneous application of function argument --------------- (fn f => pair(f(3)) (f(true))) --------------- problem: f has to have types int -> 'b and bool -> 'b could f have type 'a -> 'b? (good question) thinking of 'a -> 'b as 'a and 'b are unknown? then succ has type 'a -> 'b but get error thinking of 'a -> 'b as all (a,b:type) a->b then cannot pass (fn x => 0) as argument other interpretations? (there are some sound ones) all (a:type) a -> 'b? non-generic type variables: type variables appearing in type of a lambda-bound identifier -shared among all occurrences of lambda-bound id (e.g., f) -prevent heterogenous applications of lambda-bound functions **** heterogeneous application of declared functions try to do better for let (and global declarations) otherwise polymorphic functions couldn't be reused ------------------- let val f = (fn a => a) in pair(f(3)) (f(true)) end; ------------------- why the difference? in a let, one knows what the polymorphic function is (at least it's type) generic type variable: type variable appearing in type of a let-bound identifier e.g., type of f above is (all 'a: type) 'a -> 'a 'a is a generic type variable only generic type variables can be instantiated to arbitrary types at each use (have to make copy of type variables) No copy of non-generic variables --------------- (fn g => let val f = g in pair(f(3)) (f(true)) end; --------------- formally: a type variable occuring in the type of an expression E is generic for a given scope if it does not occur in the type of a lambda-variable declaration that encloses the given scope thus within a function declaration, the argument type is non-generic but after the declaration it becomes generic *** Extended algorithm maintain stack of non-generic variables those not elements of stack are generic when enter lambda, push on list, cut back when leave [let] typecheck declaration part, get new env, use that to typecheck body [decl] declaration treated by checking all definitions xi = ei put in enironment [rec dec] first create environment containing pairs for all xi 'ai non-generic, then check the ei in that environment, finally match the types of ei against 'ai ** Inference system (see Cardelli paper) is the "ide" rule sufficiently formalized? the "let" rule must be used with "bind", "then" and "rec" justifies algorithm given above (check this) ** Limitations *** No recursive types: type 'a stream = unit -> ('a * 'a stream) no way to use this in the algorithm solution: use abstraction to break the recursion (think of above equation as an isomorhpism) up, down are provided implicitly by the datatype constructor (up when applied to values down when used in pattern matching) --------------- - datatype 'a stream = STR of unit -> ('a * 'a stream); - exception empty; - val emtpy_str = STR (fn x => raise empty); val emtpy_str = STR fn : 'a stream - val ones = let fun s1 () = (1, STR s1) in STR s1 end; val ones = int stream - let fun sf (STR s) = s in (sf ones)() end; val it = (1,STR fn) : int * int stream --------------- *** Polymorphic functions are not first-class objects! type variables in a function parameter are not generic no self-application, no Y combinator ------------------ - fun F f = fn (a,b) => (f(a), f(b)); val F = fn : ('a -> 'b) -> 'a * 'a -> 'b * 'b ------------------ This is not a limitation of the type inference system just of the algorithm