COMP 205 Style

Download Report

Transcript COMP 205 Style

Implementing a Language with
Flow-Sensitive and Structural
Subtyping on the JVM
David J. Pearce and James Noble
Victoria University of Wellington
Java:
void buildLabelMap(List<Bytecode> bytecodes) {
HashMap<String,Integer> labels = new HashMap<String,Integer>();
int idx = 0
for(Bytecode b : bytecodes) {
if(b instanceof Bytecode.Label) {
Bytecode.Label lab = (Bytecode.Label) b;
labels.put(lab.name, idx);
}
idx = idx + 1;
} }
Python:
def buildLabelMap(bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if type(b) == “Label”:
labels[b.name] = idx
idx = idx + 1
The Whiley Language
Whiley:
void buildLabelMap([Bytecode] bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if b ~= Label:
labels[b.name] = idx
idx = idx + 1
• Design Goals:
– Look and feel of a dynamically typed language
– But, still provide static type checking
– Simple programming model
– Amenable to program verification
The Whiley Language
Whiley:
void buildLabelMap([Bytecode] bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if b ~= Label:
labels[b.name] = idx
idx = idx + 1
• Design Goals:
– Look and feel of a dynamically typed language
– But, still provide static type checking
– Simple programming model
– Amenable to program verification
The Whiley Language
Whiley:
void buildLabelMap([Bytecode] bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if b ~= Label:
labels[b.name] = idx
idx = idx + 1
• Design Goals:
– Look and feel of a dynamically typed language
– But, still provide static type checking
– Simple programming model
– Amenable to program verification
Union Types
null|int indexOf(string str, char c):
…
[string] split(string str, char c):
idx = indexOf(str,c)
if idx ~= int:
below = str[0..idx]
above = str[idx..]
return [below,above]
else:
return [str]
• Union types have many uses
– Such as neatly handling null references
– Or, combining different kinds (i.e. unions of structs)
Union Types
null|int indexOf(string str, char c):
…
[string] split(string str, char c):
idx = indexOf(str,c)
if idx ~= int:
below = str[0..idx]
above = str[idx..]
return [below,above]
else:
return [str]
• Union types have many uses
– Such as neatly handling null references
– Or, combining different kinds (i.e. unions of structs)
Structural Subtyping
define LinkedList as null | {int dat, LinkedList nxt}
define NonEmptyList as {int dat, LinkedList nxt}
int sum(LinkedList l):
if !(l ~= NonEmptyList):
return 0
else:
return l.dat + sum(l.nxt)
void main([string] args):
l={dat: 1, nxt: null}
l={dat: 2, nxt: l}
out->println(sum(l))
• Defined types are not nominal
– i.e. LinkedList is just a name that “expands”
Structural Subtyping
define LinkedList as null | {int dat, LinkedList nxt}
define NonEmptyList as {int dat, LinkedList nxt}
int sum(LinkedList l):
if l ~= NonEmptyList:
return 0
else:
return l.dat + sum(l.nxt)
void main([string] args):
l={dat: 1, nxt: null} // l->{int dat, null nxt}
l={dat: 2, nxt: l}
// l->{int dat, {int dat, null nxt} nxt}}
out->println(sum(l))
• Defined types are not nominal
– i.e. LinkedList is just a name that “expands”
Implementation
Numbers
real weird(real x, [int] map):
if x ~= int:
// convert x to int
x = map[x]
// convert x back to real
return x
• Unbounded Integers & Rationals
– How to implement efficiently on JVM?
1)BigInteger and BigRational
2)SmallInt, BigInt and BigRational [Clojure & Erjang]
3)int[]
• Adding 100,000 random (small) ints
–
BigInteger(11ms), SmallInt/BigInt (1ms), int[] (1ms)
Records
• HashMap implementation:
– Map field names to objects
– Record access are HashMap lookups
define Point as {int x, int y}
define Point3D as {int x, int y, int z}
int weird(Point p):
return p.x + p.y
int alsoWierd(Point3D p3d):
return weird(p3d)
• Array implementation
– Fields allocated to array elements
– Faster lookup times
– Problem with width subtyping of records
12
3
“x”
“y”
Runtime Type Tests
• Type testing without names is difficult!
void f([real] xs):
if xs ~= [int]:
…
else:
…
void f(List xs):
for(Object _x : xs) {
Number x = (Number) x;
if(!x.isInteger()) {
goto elseLab;
} }
…
return
elseLab:
…
return
– Elements may require recursive type test
Type Testing Records
define Point as {int x, int y}
define Point3D as {int x, int y, int z}
int weird(Point p):
if p ~= Point3D:
…
else:
…
• Type testing records == checking for fields
– Can do better than brute-force “check all” approach
– Test smallest set of “uniquely identifying” fields
Testing Recursive Types
• Recursive types are also awkward
define LinkedList as {int data, LinkedList y}
define WeirdList as {null|int data, WeirdList y}
int weird(WeirdList p):
if p ~= LinedList:
…
else:
…
– Must recursively check every node
– Potentially expensive – like for lists
JVM not fully flow-sensitive!!!
define nstring as null|string
int length(nstring ns):
if ns ~= string:
return |ns|
else:
return 0
0:
1:
2:
3:
4:
5:
6:
aload_0
dup
instanceof java/util/List
ifeq
…
checkcast java/util/List
invokeinterface size()I
…
• JVM uses flow-sensitive typing algorithm
– Doesn’t account for instanceof tests
– Have to insert casts to keep verifier happy
Cloning
void System::main([string] args):
xs = [1,2,3]
ys = xs
// clone required
ys[0] = 2
out->println(str(xs))
out->println(str(ys))
•
Data types have value semantics:
–
i.e. lists are not references (as in Java)
–
E.g. must “clone” lists on assignment
–
This is inefficient – want to reduce number of clones
–
E.g. 28125 clones in Chess benchmark (508LOC)
Conclusion
• Design Goals:
– Look and feel of a dynamically typed language
– But, still provide static type checking
• Implementation on JVM
–
–
–
–
Efficient representation of integers / rationals
Efficient representation of records
Efficient type testing … ?
Efficient value semantics … ?
http://whiley.org