Optional Static Typing
Download
Report
Transcript Optional Static Typing
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