Class notes for Wednesday, March 22, 2006 Adding support for typed letrec expressions to our type checker ------------------------------------------------------------------------------ New grammatical form ::= (letrec (*) ) ::= ( (*) -> ) ::= ( ) ------------------------------------------------------------------------------ Traditional block-structure syntax looks something like this: block { int progA(int x, bool y, int z) { } bool progB(int n) { } int progC(int num, (int -> bool) pred) { } } which can be expressed in list-structure syntax using letrec: (letrec ((int progA ((int x) (bool y) (int z)) -> ) (bool progB ((int n)) -> ) (int progC ((int num) ((int -> bool) pred)) -> )) ) ------------------------------------------------------------------------------ General form of a typed letrec expression: (letrec ((typeA nameA ((t1A x1A) (t2A x2A) ...) -> funbodyA) (typeB nameB ((t1B x1B) (t2B x2B) ...) -> funbodyB) ... (typeN nameN ((t1N x1N) (t2N x2N) ...) -> funbodyN)) body) To parse this, we will create the following lists of subexpressions, which will make the task of type-checking the whole expression easier: fun-names = (nameA nameB ... nameN) return-types = (typeA typeB ... typeN) param-name-lists = ((x1A x2A x3A ...) (x1B x2B x3B ...) ... (x1N x2N x3N ...)) param-type-lists = ((t1A t2A t3A ...) (t1B t2B t3B ...) ... (t1N t2N t3N ...)) fun-bodies = (funbodyA funbodyB ... funbodyN) letrec-body = body (define-datatype expression expression? ... (letrec-exp (fun-names (list-of symbol?)) (return-types (list-of type-expression?)) (param-name-lists (list-of (list-of symbol?))) (param-type-lists (list-of (list-of type-expression?))) (fun-bodies (list-of expression?)) (letrec-body expression?)) ... ) ------------------------------------------------------------------------------ We add the following code to the parser: (define parse (lambda (datum) (cond ... ((letrec? datum) (parse-letrec datum)) ...))) (define letrec? (lambda (datum) (and (list? datum) (= (length datum) 3) (equal? (car datum) 'letrec)))) (define parse-letrec (lambda (datum) (let ((declarations (cadr datum)) (body (caddr datum))) (letrec-exp (map decl->fun-name declarations) (map decl->return-type declarations) (map decl->param-names declarations) (map decl->param-types declarations) (map parse (map decl->body declarations)) (parse body))))) (define decl->fun-name (lambda (decl) (cadr decl))) (define decl->return-type (lambda (decl) (car decl))) (define decl->param-names (lambda (decl) (map cadr (caddr decl)))) (define decl->param-types (lambda (decl) (map car (caddr decl)))) (define decl->body (lambda (decl) (car (cddddr decl)))) Example: (define d '(int progA ((int x) (bool y) (int z)) -> (x + z))) (decl->fun-name d) => progA (decl->return-type d) => int (decl->param-names d) => (x y z) (decl->param-types d) => (int bool int) (decl->body d) => (x + z) ------------------------------------------------------------------------------ We add the following code to the type checker: (define type-of-expression (lambda (exp tenv) (cases expression exp ... (letrec-exp (fun-names return-types param-name-lists param-type-lists fun-bodies letrec-body) (let* ((fun-types (map make-fun-type param-type-lists return-types)) (letrec-body-tenv (extend tenv fun-names fun-types))) (for-each (lambda (return-type param-names param-types fun-body) (let* ((fun-tenv (extend letrec-body-tenv param-names param-types)) (body-type (type-of-expression fun-body fun-tenv))) (assert-types-equal! body-type return-type))) return-types param-name-lists param-type-lists fun-bodies) (type-of-expression letrec-body letrec-body-tenv))) ... ))) ------------------------------------------------------------------------------ (define example '(letrec ((int fact ((int n)) -> (if (zero? n) 1 (times n (fact (n - 1))))) (bool zero? ((int num)) -> (num = 0)) (int times ((int a) (int b)) -> (a * b))) fact)) fun-names: (fact zero? times) return-types: (int bool int) param-name-lists: ((n) (num) (a b)) param-type-lists: ((int) (int) (int int)) fun-types: ((int -> int) (int -> bool) (int * int -> int)) letrec-body-tenv = (extend tenv fun-names fun-types) fact = (int -> int) zero? = (int -> bool) times = (int * int -> int) we next typecheck the function declarations... for each return-type, param-names, param-types, and fun-body... fact: fun-tenv = (extend letrec-body-tenv (n) (int)) t = (type-of-expression fact-body fun-tenv) (assert-types-equal! t int) zero?: fun-tenv = (extend letrec-body-tenv (num) (int)) t = (type-of-expression zero?-body fun-tenv) (assert-types-equal! t bool) times: fun-tenv = (extend letrec-body-tenv (a b) (int int)) t = (type-of-expression times-body fun-tenv) (assert-types-equal! t int) and then return the type of the letrec body... return (type-of-expression letrec-body letrec-body-tenv) (type-check example) => (int -> int)