Exercise 5 Records and Subtyping |
|||||
English only |
|||||
Administrative Issue: This is the last exercise session before graded assignments begin. You might want to use the session to ask any questions about the things you learned up to now.
Exercise 5 Records and Subtyping
In this informal grammar,
In this exercise, we add records, like this one
The new tree nodes are (from
Again, the first parameter
Careful: Records are represented an an ordered list of key-value pairs! But we want them to be unordered, such that the above example would be the same
as Note: We use the symbol |- for the algorithmic typing and subtyping relations, for readability. The book uses a a different symbol for declarative and algorithmic definitions of typing.
Only the highlighted sections of the type system are new. Algorithmic subtyping is specified by the contra-co-variant rule for functions, and the rule for records. The record rule checks at the same time whether a record has more fields and whether a field has a subtype. Good news: You need not implement an Evaluator in this exercise. Just for information, here are the new computation and congruence rules for records and field selection:
Task 5.1Download the partial implementation and complete the missing cases insrc/subrec/TypeChecking.scala
You can test with
Task 5.2Refine the typing rule with joins and meets, such that we can give do better typechecking for "if"
meet(s:Type, t:Type):Type and join(s:Type, t:Type):Type that compute the meet ∧ and the join ∨ of two types.
Here is their mutually recursive definition
ifjoin.sub works with your adapted typechecker.
TipsThere are several ways to manipulate lists. You will need to use one in order to handle the contents of records.Nested recursive functions These are functions that take a list as an argument, and make a pattern match. For nonempty lists, they handle the case x::xs , where x is an
element and xs is a list. For empty lists, they return
some neutral value. Nested functions can be defined anywhere, in particular
also inside other functions. For instance, here is a function that could transform
a List[Pair[String,Expr]] into a List[Pair[String,Type]]
def doSomething(li: List[Pair[String,Expr]]): List[Pair[String,Type]] = li match { case Pair(key,value)::xs => ... :: doSomething( xs ) case Nil => Nil; } val newList = doSomething(myList) using map and higher-order functions. All recursive functions to map a list like above are very similar. Using map one can
type a bit less and achieve the same:
val newList = myList.map { x => ... } for-comprehensions. To do something for each element in a list, one can write for(val c <- myList) { ... }The results of the body are thrown away, and the whole "for" returns nothing (has type Unit ). If you want the results of the body to be saved and concatenated to a list, you can use
val newList = for(val c <- myList) yield { ... } Iterators. If you call the elements method of a list, you get an iterator, which can be traversed with a while loop.
val it = myList.elements; while( it.hasNext ) { val c = it.next; ... }The good thing about iterators is that they have a find method
which takes a boolean function. This can help finding an entry in a list, like
here
val myList:List[Pair[String,Type]] = ...; myList.elements.find { x => x._1 == "foo" } match { case Some(Pair(_,tpe)) => ... myList contains the Pair("foo",tpe)... case _ => ... myList does not contain an entry for "foo" } |
|