UNB/ CS/ David Bremner/ teaching/ cs4613/ assignments/ CS4613 Assignment 4


In this assignment you will implement a simple structural typechecker inspired by the Typed Classes discussed in Lecture 14. Starting with the interpreter of Assignment 2, the given skeleton adds a syntax for anonymous objects with named fields. An example is the following taken from the tests

 {hello true}
 {goodbye false}
 {a-num 42}
 {n-func {lam {x : num} x}}
 {b-func {lam {x : bool} x}}}

This object has 5 fields: two boolean fields, one number field, and two function fields, AKA methods. There are no classes in this language, but each object has a type. In this case the object has the type (e.g. for annotating function variables) of

{obj {hello bool}
     {goodbye bool}
     {a-num num}
     {n-func {num -> num}}
     {b-func {bool -> bool}}}

You task is to update the typecheck function in the given skeleton to understand these objects and the corresponding msg operation. For example following expression has type {num -> num}

 {obj {func {lam x {+ x 1}} 3}}

Changes from Assignment 2

The type Exp for abstract syntax has been augmented with two new variants. objE defines an object with a list of named fields, each an arbitrary expression. msgE defines message sending operation. In order to make the type system easier to manage, the messages are symbols rather than expressions. The parser has been updated to support a corresponding concrete syntax, used in the tests below.

[objE (fields : (Listof (Symbol * Exp)))]
[msgE (obj : Exp) (selector : Symbol)]

Since the goal is implement a typed language, the abstract syntax for type annotations has similarly been extended.

(define-type TypeExp
  [arrowTE (arg : TypeExp)
           (result : TypeExp)]
  [objTE (fields : (Listof (Symbol * TypeExp)))])

Finally the type for representing types in our toy language has been extended. Note that in the actual Type objT variants, the fields are a hash table rather than a list. This makes field lookup more efficient. The interp-te function that translates from TypeExp to Type has been extended to convert from the list of fields to a hash table.

(define-type Type
  [arrowT (arg : Type)
           (result : Type)]
  [objT (fields : (Hashof Symbol Type))])

1. Typecheck objects

Your first task is to complete the objE and msgE cases for the typecheck function. In the language of the textbook, objE introduces an object (like lamE for functions) and msgE eliminates them (i.e. evaluates to one of the fields). Your solution should pass the following tests, including throwing errors for typing issues like using an known field name. Thus, the set of fields for a given object does not change after it is created.

Your completed typecheck function should pass the following tests. In order to re-use the same toy language fragments between tests, these tests make extensive use of quasiquote syntax.

(module+ test
  (define sampler `{obj {hello true}
                        {goodbye false}
                        {a-num 42}
                        {n-func {lam {x : num} x}}
                        {b-func {lam {x : bool} x}}
  (test (tc sampler)
        (objT (hash (list (pair 'hello (boolT))
                          (pair 'goodbye (boolT))
                          (pair 'a-num (numT))
                          (pair 'n-func (arrowT (numT) (numT)))
                          (pair 'b-func (arrowT (boolT) (boolT)))))))
  (test (tc `{msg ,sampler hello}) (boolT))
  (test/exn (tc `{msg 1 hello}) "object")
  (test/exn (tc `{msg ,sampler blah}) "unknown field")
  (define obj-fun `{lam {x : (obj (n-func (num -> num)))} {{msg x n-func} 3}})
  (test (tc obj-fun) (arrowT
                      (objT (hash (list (pair 'n-func (arrowT (numT) (numT))))))
  (test (tc `{,obj-fun {obj {n-func {lam {x : num} x}}}}) (numT))
  (test/exn (tc `{,obj-fun 2}) "argument type")
  (test/exn (tc `{if true ,obj-fun 2}) "branches")
  (test (tc `{rec {fact : (obj (run (num -> num)))}
                  {obj {run {lam {n : num}
                                 {if {<= n 0} 1 {* n {{msg fact run} {- n 1}}}}}}}
                  {{msg fact run} 10}})


2. Structural typing

According to the substitution principle, we call type $X$ a subtype of type $Y$ of another if any value with type $X$ it can be safely used where some expression expects a value of type $Y$. Surprisingly, for our object types this means that the fields of $X$ are a superset of the fields of $Y$.

2.1 Write subtype?

Write a function subtype? the checks whether the first argument is a subtype of the second argument. This means that every field of the second argument must be present in the first argument with the matching type.

(module+ test
  (define hello-t (objT (hash (list (pair 'hello (numT))))))
  (define hello-goodbye-t (objT (hash (list
                                       (pair 'hello (numT))
                                       (pair 'goodbye (boolT))))))
  (test (subtype? (numT) (boolT)) #f)
  (test (subtype? (numT) (numT)) #t)
  (test (subtype? (numT) hello-t) #f)
  (test (subtype? hello-t (objT (hash (list (pair 'hello (boolT)))))) #f)
  (test (subtype? hello-goodbye-t hello-t) #t)
  (test (subtype? hello-t hello-goodbye-t) #f))

2.2 Use subtype?

Update the appE case of the typechecker to use subtype?, so that the following tests pass.

(module+ test
    (test (tc `{,obj-fun {obj {n-func {lam {x : num} x}}
                              {b-func {lam {x : bool} x}}}}) (numT))

    (test (tc `{let1 {f : {(obj (n-func (num -> num))) -> num}}
                     {f ,sampler}})


Referring to the grading grading rubric for guidance, add any needed tests for your solution.

As always, make sure you have complete test coverage.