Implementing a Language with Flow-Sensitive and Structural Subtyping on the JVM David J. Pearce and...
-
Upload
stephen-simpson -
Category
Documents
-
view
217 -
download
0
Transcript of Implementing a Language with Flow-Sensitive and Structural Subtyping on the JVM David J. Pearce and...
Implementing a Language with Flow-Sensitive and Structural
Subtyping on the JVM
David J. Pearce and James NobleVictoria University of Wellington
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;
} }
def buildLabelMap(bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if type(b) == “Label”:
labels[b.name] = idx
idx = idx + 1
Python:
Java:
The Whiley Language
• Design Goals:
– Look and feel of a dynamically typed language
– But, still provide static type checking
– Simple programming model
– Amenable to program verification
void buildLabelMap([Bytecode] bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if b ~= Label:
labels[b.name] = idx
idx = idx + 1
Whiley:
The Whiley Language
• Design Goals:
– Look and feel of a dynamically typed language
– But, still provide static type checking
– Simple programming model
– Amenable to program verification
void buildLabelMap([Bytecode] bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if b ~= Label:
labels[b.name] = idx
idx = idx + 1
Whiley:
The Whiley Language
• Design Goals:
– Look and feel of a dynamically typed language
– But, still provide static type checking
– Simple programming model
– Amenable to program verification
void buildLabelMap([Bytecode] bytecodes):
labels = {}
idx = 0
for b in bytecodes:
if b ~= Label:
labels[b.name] = idx
idx = idx + 1
Whiley:
Union Types
• Union types have many uses– Such as neatly handling null references– Or, combining different kinds (i.e. unions of structs)
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
• Union types have many uses– Such as neatly handling null references– Or, combining different kinds (i.e. unions of structs)
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]
Structural Subtyping
• Defined types are not nominal– i.e. LinkedList is just a name that “expands”
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))
Structural Subtyping
• Defined types are not nominal– i.e. LinkedList is just a name that “expands”
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))
Numbers
• 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)
real weird(real x, [int] map):
if x ~= int:
// convert x to int
x = map[x]
// convert x back to real
return x
Records
• HashMap implementation:– Map field names to objects– Record access are HashMap lookups
• Array implementation– Fields allocated to array elements– Faster lookup times– Problem with width subtyping of records
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)
“x”
12
“y”
3
Runtime Type Tests
• Type testing without names is difficult!
– Elements may require recursive type test
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
Type Testing Records
• Type testing records == checking for fields
– Can do better than brute-force “check all” approach
– Test smallest set of “uniquely identifying” fields
define Point as {int x, int y}
define Point3D as {int x, int y, int z}
int weird(Point p):
if p ~= Point3D:
…
else:
…
Testing Recursive Types
• Recursive types are also awkward
– Must recursively check every node– Potentially expensive – like for lists
define LinkedList as {int data, LinkedList y}
define WeirdList as {null|int data, WeirdList y}
int weird(WeirdList p):
if p ~= LinedList:
…
else:
…
JVM not fully flow-sensitive!!!
• JVM uses flow-sensitive typing algorithm
– Doesn’t account for instanceof tests
– Have to insert casts to keep verifier happy
define nstring as null|string
int length(nstring ns):
if ns ~= string:
return |ns|
else:
return 0
0: aload_0
1: dup
2: instanceof java/util/List
3: ifeq …
4: checkcast java/util/List
5: invokeinterface size()I
6: …
Cloning
• 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)
void System::main([string] args):
xs = [1,2,3]
ys = xs // clone required
ys[0] = 2
out->println(str(xs))
out->println(str(ys))
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 … ?