optional static typing guido van rossum (with paul prescod, greg stein, and the types-sig)
TRANSCRIPT
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: Tdef __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 instantiationdecl s: IntStacks = IntStack() # or s = Stack() ???s.push(1)decl x: intx = s.pop()s.push("spam") # ERROR
Parameterized Functions
• Example:def bisect<T>(a: [T], x: T) -> int:
decl lo, mid, hi: intlo, hi = 0, len(a)while lo < hi: mid = (lo+hi)/2 if x < a[mid]: hi = mid else: lo = mid+1return 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: print “an int: %d” % istring, s: print “a string: %s” % `s`None: print “nothing”else: print “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: ... # ERRORclass D’(B):
def method(self, x: any) -> int: ... # okclass D’’(B):
def method(self, x: number) -> any: ... # ERROR