Optional Static Typing Guido van Rossum (with Paul Prescod, Greg Stein, and the types-SIG) Talk Overview • • • • • • • • Why add static type checking? Checked vs. unchecked modules Declaration syntax Type expressions Dynamic casts Parameterized types Open issues Implementation...? Why Add Static Typing? • Two separate goals: – faster code (OPT) – better compile-time errors (ERR) • Mostly interested in (ERR) – (OPT) will follow suit • Of course it will be optional – and (mostly) backwards compatible Checked vs. Unchecked • A module is checked or unchecked – no partially checked modules • Checked modules are: – guaranteed not to raise runtime type errors • except at point of call from unchecked modules • except at explicit dynamic casts – protected at runtime against violations by unchecked modules • e.g. messing with __dict__ or assignment to module globals or class/instance variables Declaration Syntax • Two forms: inline and explicit – explicit form is easy to remove • Inline: • def gcd(a: int, b: int) -> int: ... • Explicit (two variants): • decl gcd: def(int, int) -> int def gcd(a, b): ... • def gcd(a, b): decl a: int, b: int decl return: int ... Declaring Classes • decl in class declares instance attributes – (by default, anyway) – can add syntax for private, protected etc. – methods declared excluding ‘self’ • derived classes must play by the rules – can’t override methods w. conflicting types – must call Base.__init__() – once this exists, we can think about require/ensures again! Type Expressions • standard types (in __builtin__): – int, long, float, complex, str, tuple, list, dict • int is same as types.IntType, etc. – None (special case: type == value) • standard abstract types (__builtin__): – number, string, sequence, mapping, any • class names: – may be imported from checked modules • typedefs (decl myType = P.M.Type) Constructing Types • Syntax for type composition: – list with items of type T: [T] – tuple of T1, T2, T3: (T1, T2, T3) • (this explains why we have both tuples and lists!) – dict with key/value types T1/T2: {T1: T2} – union of types T1 and T2: T1 | T2 – function (e.g.): def(T1, T2)->T3 • Example: – {str: (int, int) | (int, int, str) | None} Dynamic Casts • Proposed by Greg Stein • General syntax: expression ! type • Example: x = y ! int – if y has type int: assign y to x – otherwise: raise TypeError • Problem: – must catch exception to decide union type • Alternative: ‘typecase’ statement? – no syntax proposal exists yet Parameterized Types • Needed e.g. for container classes: class Stack<T>: decl st: T def __init__(self): self.st = [] def push(self, x: T): self.st.append(x) def pop(self) -> T: x = self.st[-1]; del self.st[-1]; return x decl IntStack = Stack<int> # template instantiation decl s: IntStack s = IntStack() # or s = Stack() ??? s.push(1) decl x: int x = s.pop() s.push("spam") # ERROR Parameterized Functions • Example: def bisect<T>(a: [T], x: T) -> int: decl lo, mid, hi: int lo, hi = 0, len(a) while lo < hi: mid = (lo+hi)/2 if x < a[mid]: hi = mid else: lo = mid+1 return lo decl a: [int] • Idea: – bisect() could be called without template instantiation – argument types will be matched against the type parameters <T> Open Issues (1) • A subclass is not always a subtype! • int is a subtype of any, but [int] not of [any] • similar for parameterized classes • • • • • Dynamic cast or typecase statement? Should we declare exceptions? How? How much type inference is needed? Interface declarations? var=default:type -or- var:type=default? Open Issues (2) • Access to type objects at runtime – reflection on types must be possible • • • • • • How to spell arbitrary tuple of T: (T*)? Typedef syntax (in expressions?) Type conformance rules Syntax ambiguity for lambda How to spell the type of self Should classes be types? (Yes!) Open Issues (3) • Avoid NameError and AttributeError? – Need flow analysis plus special rules • e.g. all instance variables must be defined when __init__ returns (but what about calls it makes?) • for local variables, it’s relatively easy • for globals, how to do it? • how to deal with recursive imports? blah! • Choice of names – int/integer, str/string may be too confusing Implementation...? • Who? me? :-) • Possible phases: – Experimental type checker written in Python • can hide decl from Python in string literals – Allow alternate parsers in core Python • checked modules could use *.cpy extension – Support for type checker in bytecode – Rewrite checker in C as part of core Python – Add aggressive optimizer Typecase Strawman decl x: int | string | None | [any] | .... typecase x: int, i: string, s: None: else: print print print print “an int: %d” % i “a string: %s” % `s` “nothing” “don’t know” • All possible types must be accounted for, otherwise it’s a static error Contravariance class B: def method(self, x: number) -> int|long: ... def other(self): decl i: int|long i = self.method(3.14) class D(B): def method(self, x: int) -> int|long: ... class D’(B): def method(self, x: any) -> int: ... class D’’(B): def method(self, x: number) -> any: ... # ERROR # ok # ERROR