;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; recon.scm: TYPE RECONSTRUCTOR FOR Scheme/R ;;; ;;; To run this code, first load Scheme+, then this file. ;;; ;;; Evaluate ;;; ;;; (recon ) ;;; ;;; to type-reconstruct the Scheme/R expression ;;; ;;; ;;;---------------------------------------------------------------------------- ;;; Magic for handling unit. This should really be in a separate file, or ;;; be part of Scheme+. (define-structure (unit-obj (print-procedure (lambda (state struct) (unparse-string state "#u"))))) ;; THE-UNIT is the unique instance of the UNIT-OBJ structure (define the-unit (make-unit-obj)) (define (unit? obj) (eq? obj the-unit)) ;;; Changing parser to handle #u (define parse-object/unit (let ((discard-char (access discard-char (->environment (find-package '(runtime parser)))))) (lambda () (discard-char) the-unit))) (parser-table/set-entry! system-global-parser-table '("#u" "#U") parse-object/unit) ;;; Constructors for handling UNIT (define unit->sexp (make-constructor (lambda () #u) (lambda (sexp succ fail) (if (unit? sexp) (succ) (fail))))) (define a-unit unit->sexp) ;;;---------------------------------------------------------------------------- ;;; SYM-SEXP is a synonym for SYMBOL->SEXP (and should replace it in ;;; future versions of Scheme+) (define sym->sexp (make-sexp-constructor 'sym symbol?)) ;;;---------------------------------------------------------------------------- ;;; CELLS in Scheme (define cell-tag (list '*cell*)) (define (cell val) (list cell-tag val)) (define (cell? x) (and (list? x) (= (length x) 2) (eq? (car x) cell-tag))) (define (^ x) (if (cell? x) (cadr x) (error "^: not a cell " x))) (define (:= x y) (if (cell? x) (begin (set-car! (cdr x) y) #u) (error "^: not a cell " x))) ;;;---------------------------------------------------------------------------- ;;; DATATYPES ;;; Expressions (define-datatype exp ; E ::= (unit->exp) ; Unit (boolean->exp bool) ; | Bool (integer->exp int) ; | Int (string->exp string) ; | String (symbol->exp sym) ; | (symbol Sym) (variable->exp sym) ; | I (lambda->exp (listof sym) exp) ; | (lambda (I*) E) (call->exp exp (listof exp)) ; | (E0 E*) (if->exp exp exp exp) ; | (if E1 E2 E3) (primop->exp primop (listof exp)) ; | (primop O E*) (let->exp (listof definition) exp) ; | (let ((I E)*) E0) (letrec->exp (listof definition) exp) ; | (letrec ((I E)*) E0) (set!->exp sym exp) ; | (set! I E) (begin->exp exp exp) ; | (begin E E) ;; **MODULES** (module->exp (listof definition)) ; | (module (define I E)*) (with->exp (listof sym) exp exp) ; | (with (I*) E1 E2) ;; **MODULES** ) (define-datatype definition ; (I E) (make-definition sym exp)) (define (definition-name d) (match d ((make-definition name value) name))) (define (definition-value d) (match d ((make-definition name value) value))) ;;; Types (define-datatype type (tvariable->type tvariable) ; type variable (base->type sym) ; (unit, bool, int, string, symbol) (compound->type sym (listof type)) ; ->, list-of, etc. (unknown->type) ; placeholder for unconstrained tvars ;; **MODULES** (moduleof->type (listof sym) (listof type)) ; (moduleof (val I T)*) ;; **MODULES** ) (define unit-type (base->type 'unit)) (define boolean-type (base->type 'bool)) (define integer-type (base->type 'int)) (define string-type (base->type 'string)) (define symbol-type (base->type 'sym)) (define same-constructor? eq?) ;; **MODULES** (define (same-field-names? names1 names2) (if (null? names1) (null? names2) (if (null? names2) #f (and (eq? (car names1) (car names2)) (same-field-names? (cdr names1) (cdr names2)))))) ;; **MODULES** (define (make-arrow-type arg-types body-type) (compound->type arrow-constructor (cons body-type arg-types))) (define arrow-constructor '->) (define same-name? eq?) ;;; Type schemas (define-datatype tvar-or-schema (tvar->tvar-or-schema tvariable) (schema->tvar-or-schema schema)) (define-datatype schema (make-schema (listof tvariable) type)) (define (schema-generics s) (match s ((make-schema generics typ) generics))) (define (schema-type s) (match s ((make-schema generics typ) typ))) ;;;---------------------------------------------------------------------------- ;;; TYPE RECONSTRUCTION (define (reconstruct exp tenv) (match exp ((unit->exp) unit-type) ((boolean->exp _) boolean-type) ((integer->exp _) integer-type) ((string->exp _) string-type) ((symbol->exp _) symbol-type) ((variable->exp var) (reconstruct-variable var tenv)) ((if->exp test con alt) (reconstruct-if test con alt tenv)) ((primop->exp primop args) (reconstruct-primop primop args tenv)) ((lambda->exp formals body) (reconstruct-lambda formals body tenv)) ((call->exp op args) (reconstruct-call op args tenv)) ((let->exp defs body) (reconstruct-let defs body tenv)) ((letrec->exp defs body) (reconstruct-letrec defs body tenv)) ((set!->exp id exp) (reconstruct-set! id exp tenv)) ((begin->exp exp1 exp2) (reconstruct-begin exp1 exp2 tenv)) ;; **MODULES** ((module->exp defs) (reconstruct-module defs tenv)) ; *** ((with->exp vars mod body) (reconstruct-with vars mod body tenv)) ; *** ;; **MODULES** )) (define (reconstruct-variable var tenv) (let ((tvar-or-schema (tlookup tenv var))) (match tvar-or-schema ((tvar->tvar-or-schema tvar) (tvariable->type tvar)) ((schema->tvar-or-schema schema) (instantiate-schema schema))))) (define (reconstruct-if test con alt tenv) (begin (unify! (reconstruct test tenv) boolean-type) (let ((con-type (reconstruct con tenv)) (alt-type (reconstruct alt tenv))) (begin (unify! con-type alt-type) con-type)))) (define (reconstruct-lambda vars body tenv) (let ((new-tvars (map new-tvariable vars))) (make-arrow-type (map tvariable->type new-tvars) (reconstruct body (extend-by-tvariables tenv vars new-tvars))))) (define (reconstruct-call op args tenv) (let ((arg-types (map (lambda (arg) (reconstruct arg tenv)) args)) (result-type (tvariable->type (new-tvariable 'result)))) (begin (unify! (reconstruct op tenv) (make-arrow-type arg-types result-type)) result-type))) (define (reconstruct-primop op args tenv) (let ((arg-types (map (lambda (arg) (reconstruct arg tenv)) args)) (result-type (tvariable->type (new-tvariable 'result)))) ;; Get the type of the operator from the standard type env: (begin (unify! (reconstruct-variable op standard-type-environment) (make-arrow-type arg-types result-type)) result-type))) (define (reconstruct-let defs body tenv) (reconstruct body (extend-by-schemas tenv (map definition-name defs) (map (lambda (def) (compute-schema (reconstruct (definition-value def) tenv) tenv)) defs)))) (define (reconstruct-letrec defs body tenv) (let ((names (map definition-name defs))) (let ((tvars (map new-tvariable names))) (let ((dummy-tenv (extend-by-tvariables tenv names tvars))) (let ((types (map (lambda (def) (reconstruct (definition-value def) dummy-tenv)) defs))) (begin (for-each-2 unify! (map tvariable->type tvars) types) (reconstruct body (extend-by-schemas tenv names (map (lambda (type) (compute-schema type tenv)) types))))))))) (define (for-each-2 proc lst1 lst2) (if (null? lst1) #u (begin (proc (car lst1) (car lst2)) (for-each-2 proc (cdr lst1) (cdr lst2))))) ;;; Note: the use of UNIFY!-LIST rather than FOR-EACH-2 fails to ;;; correctly type (or find a type error in) the following example: ;;; (recon '(letrec ((a (lambda () 3)) ;;; (b (if (a) 1 2))) ;;; 4)) (define (reconstruct-set! id exp tenv) (begin (unify! (reconstruct-variable id tenv) (reconstruct exp tenv)) unit-type)) (define (reconstruct-begin exp1 exp2 tenv) ;; Be sure to check type-safety of 1st expression: (begin (reconstruct exp1 tenv) (reconstruct exp2 tenv))) ;;;---------------------------------------------------------------------------- ;;; TYPE SCHEMAS (define (compute-schema type tenv) ;Function GEN from handout (make-schema (generic-tvariables type tenv) type)) ; NOTE: generic-tvariables looks not only at tvariables in the ; given type, but also at tvariables in the leaves of ; the fully unwound version of the given type. This interacts with ; a similar unwinding at instantiation time to appropriately handle ; generalization. There is potential confusion in that the returned ; list may contain types that are not manifestly in TYPE but are in ; the fully unwound tree associated with it. (define (generic-tvariables type tenv) ;Compute FTV(type) - FTE(tenv) (match (prune type) ((tvariable->type tvar) (if (generic-tvariable? tvar tenv) (list tvar) (null))) ((compound->type _ operands) (letrec ((loop (lambda (ops tvars) (if (null? ops) tvars (loop (cdr ops) (union (generic-tvariables (car ops) tenv) tvars)))))) (loop operands (null)))) ;; **MODULES** ((moduleof->type _ fields) (letrec ((loop (lambda (flds tvars) (if (null? flds) tvars (loop (cdr flds) (union (generic-tvariables (car flds) tenv) tvars)))))) (loop fields (null)))) ;; **MODULES** ((base->type _) (null)) (_ (error "This shouldn't happen!" (unparse-type type))))) (define (union l1 l2) (cond ((null? l1) l2) ((null? l2) l1) ((in-tvariable-list? (car l1) l2) (union (cdr l1) l2)) (else (cons (car l1) (union (cdr l1) l2))))) (define (in-tvariable-list? tvar tvar-list) (if (null? tvar-list) #f (if (same-tvariable? tvar (car tvar-list)) #t (in-tvariable-list? tvar (cdr tvar-list))))) ; Instantiate a type schema on a fresh set of type variables. ; [This corresponds to Cardelli's "FreshType".] (define (instantiate-schema schema) (substitute-into-type (map (lambda (g) (tvariable->type (new-tvariable (tvariable-name g)))) (schema-generics schema)) (schema-generics schema) (schema-type schema))) ; [The following corresponds to Cardelli's "Fresh"; note the call to prune.] ; Note that this unwinds TYPE out to the leaves when doing the substitution; ; this guarantees that we don't miss any substitutions because type itself ; isn't fully unwound. (define (substitute-into-type types tvars type) (let ((type (prune type))) (match type ((tvariable->type tvar) (letrec ((loop (lambda (ts tvars) (if (null? ts) type (if (same-tvariable? tvar (car tvars)) (car ts) (loop (cdr ts) (cdr tvars))))))) (loop types tvars))) ((base->type _) type) ((compound->type c args) (compound->type c (map (lambda (arg) (substitute-into-type types tvars arg)) args))) ((moduleof->type names args) (moduleof->type names (map (lambda (arg) (substitute-into-type types tvars arg)) args))) (_ (error "This shouldn't happen" (unparse-type type)))))) ;;;---------------------------------------------------------------------------- ;;; TYPE ENVIRONMENTS. ; ; Environments can be extended in either of two ways: ; extend-by-tvariables should be used by lambda and letrec to bind ; variables to type variables ; extend-by-schemas should be used by let and letrec to bind variables ; to type schemas ; ; Once constructed, there are two operations one can perform on a ; type environment: ; tlookup : tenv * var -> (tvar + schema) ; does the usual thing. ; generic-tvariable? : tvar * tenv -> bool ; returns true iff tvar is not free in the type of any var bound in tenv. (define-datatype type-environment (make-type-env tlookup-proc generic-tvariable?-proc)) (define (tenv-lookup te) (match te ((make-type-env lookup generic?) lookup))) (define (tenv-generic? te) (match te ((make-type-env lookup generic?) generic?))) (define (extend-by-tvariables outer-tenv vars tvars) (extend-tenv outer-tenv vars (map tvar->tvar-or-schema tvars) (lambda (tvar) ;; tvar is an unconstrained type variable. (letrec ((loop (lambda (tvars) (if (null? tvars) (generic-tvariable? tvar outer-tenv) (if (occurs-in-type? tvar (tvariable->type (car tvars))) ;; (same-tvariable? tvar (car tvars)) #f (loop (cdr tvars))))))) (loop tvars))))) (define (extend-by-schemas outer-tenv vars schemas) (extend-tenv outer-tenv vars (map schema->tvar-or-schema schemas) (lambda (tvar) (generic-tvariable? tvar outer-tenv)))) ;Students' code should not call this (define (extend-tenv outer-tenv vars typas generic-tvariable?-proc) (make-type-env (lambda (var) (letrec ((loop (lambda (vars typas) (if (null? vars) (tlookup outer-tenv var) (if (same-variable? var (car vars)) (car typas) (loop (cdr vars) (cdr typas))))))) (loop vars typas))) generic-tvariable?-proc)) (define empty-type-environment (make-type-env (lambda (var) (error "Unbound variable: " (sym->sexp var))) (lambda (tvar) #t))) (define (tlookup tenv var) ((tenv-lookup tenv) var)) (define same-variable? eq?) (define (generic-tvariable? tvar tenv) ((tenv-generic? tenv) tvar)) ; Proving the correctness of this implementation of GENERIC-TVARIABLE? ; is tricky. ;;;---------------------------------------------------------------------------- ;;; TYPE VARIABLES ; A type variable is implemented as a record that contains a cell. The ; global substitution is realized as the collective contents of the ; cells for all type variables. (define-datatype tvariable (make-tvariable sym int (cellof type))) ; id gennum cell (define (tvariable-name tvar) (match tvar ((make-tvariable name _ _) name))) (define (tvariable-uid tvar) (match tvar ((make-tvariable _ uid _) uid))) (define (tvariable-cell tvar) (match tvar ((make-tvariable _ _ c) c))) (define tvariable-counter (cell 0)) (define (reset-tvariable-counter!) (:= tvariable-counter 0)) (define (new-tvariable id) (begin (:= tvariable-counter (+ (^ tvariable-counter) 1)) (make-tvariable id (^ tvariable-counter) (cell unknown-type)))) (define (tvariable-binding tvar) (^ (tvariable-cell tvar))) (define (extend-substitution! tvar binding) (begin (:= (tvariable-cell tvar) binding) #t)) (define (same-tvariable? tvar1 tvar2) (= (tvariable-uid tvar1) (tvariable-uid tvar2))) (define unknown-type (unknown->type)) (define (tvariable->symbol tvar) (string->symbol (string-append "?" (symbol->string (tvariable-name tvar)) "-" (number->string (tvariable-uid tvar))))) ;;;---------------------------------------------------------------------------- ;;; UNIFICATION ;;; ;;; Has side effects. ;;; Generates an error if there is no unification. (define (unify! type1 type2) (if (unify!-internal type1 type2) #u (error "Type clash: " (unparse-type type1) (unparse-type type2)))) (define (unify!-internal type1 type2) (let ((type1 (prune type1)) (type2 (prune type2))) ;; Now if a type is a variable, it will be unbound (match type1 ((tvariable->type v1) (match type2 ((tvariable->type v2) (if (same-tvariable? v1 v2) #t (extend-substitution! v1 type2))) (_ (if (occurs-in-type? v1 type2) #f ;Circularity (extend-substitution! v1 type2))))) ((base->type c1) (match type2 ((tvariable->type v2) (extend-substitution! v2 type1)) ((base->type c2) (same-name? c1 c2)) (_ #f))) ((compound->type con1 args1) (match type2 ((tvariable->type v2) (if (occurs-in-type? v2 type1) #f (extend-substitution! v2 type1))) ((compound->type con2 args2) (if (same-constructor? con1 con2) (unify!-list args1 args2) #f)) (_ #f))) ((moduleof->type names1 args1) (match type2 ((tvariable->type v2) (if (occurs-in-type? v2 type1) #f (extend-substitution! v2 type1))) ((moduleof->type names2 args2) (if (same-field-names? names1 names2) (unify!-list args1 args2) #f)) (_ #f)))))) (define (unify!-list types1 types2) (if (null? types1) (null? types2) (if (null? types2) #f (if (unify!-internal (car types1) (car types2)) (unify!-list (cdr types1) (cdr types2)) #f)))) ; Chase substitutions of tvariables until either a non-tvariable or an ; unbound tvariable is found. (define (prune type) (match type ((tvariable->type tvar) (match (tvariable-binding tvar) ((unknown->type) type) (other-type (prune other-type)))) (_ type))) ; Occurs check: prevent circular substitutions. (define (occurs-in-type? tvar type) (match (prune type) ((tvariable->type tvar2) ;; prune has guaranteed that tvar2 is unbound (same-tvariable? tvar tvar2)) ((compound->type c args) (letrec ((loop (lambda (args) (if (null? args) #f (or (occurs-in-type? tvar (car args)) (loop (cdr args))))))) (loop args))) ((moduleof->type names args) (letrec ((loop (lambda (args) (if (null? args) #f (or (occurs-in-type? tvar (car args)) (loop (cdr args))))))) (loop args))) (_ #f))) ;;;---------------------------------------------------------------------------- ;;; PARSING/UNPARSING -- old version with non-optimal strategy ; Parse a definition (define (parse-definition sexp) (match sexp ;; Allow Scheme-style definitions ... (`(define (,name ,@args) ,body) (make-definition (parse-formal name) (parse `(lambda ,args ,body)))) (`(define ,name ,value) (make-definition (parse-formal name) (parse value))) (_ (error "Invalid definition: " sexp)))) (define (parse-formal sexp) (match sexp ((sym->sexp name) (if ((member? eq?) name all-keywords) (error "Attempt to use reserved word as variable name" sexp) name)) (_ (error "Invalid variable name: " sexp)))) (define (parse-call operator operands) (call->exp (parse operator) (map parse operands))) (define (parse-binding-spec bspec) (match bspec (`(,name ,value) (make-definition (parse-formal name) (parse value))) (_ (error "Invalid binding specifier: " bspec)))) (define (syntax-error sexp) (error "Invalid expression syntax: " sexp)) ; Parse a single expression (define (parse sexp) ; sexp -> exp (match sexp ((unit->sexp) (unit->exp)) ((bool->sexp b) (boolean->exp b)) ((int->sexp n) (integer->exp n)) ((sym->sexp sym) (variable->exp sym)) ((string->sexp n) (string->exp n)) (`(,(sym->sexp head) ,@_) ((parser-for-keyword head) sexp)) ;; Procedure call is the default (`(,operator ,@operands) (parse-call operator operands)) (_ (error "Unrecognized expression " sexp)))) (define-datatype parser-table (make-parser-table (listof sym) (-> (sym) (-> (sexp) exp)))) ; Expressions of the form (reserved-word ...) (define keyword-table (letrec ((keywords (cell (null))) (lookup (cell (lambda (head) (lambda (sexp) ;; Procedure call is the default (match sexp (`(,operator ,@operands) (parse-call operator operands)) (_ (error "KEYWORD TABLE: This shouldn't happen!" ))))))) ;; DEFINE-KEYWORD is a function that defines a reserved word, ;; associating it with a function that can parse the named construct. (define-keyword (lambda (keyword parser) (let ((current-lookup (^ lookup))) (begin (:= lookup (lambda (head) (if (eq? head keyword) parser (current-lookup head)))) (:= keywords (cons keyword (^ keywords))) keyword))))) (begin ;; List of parsing functions. (define-keyword 'quote ; (quote Name) (lambda (sexp) (match sexp (`(quote ,(sym->sexp name)) (symbol->exp name)) (_ (syntax-error sexp))))) (define-keyword 'lambda ; (lambda (I*) E) (lambda (sexp) (match sexp (`(lambda (,@formals) ,body) (lambda->exp (map parse-formal formals) (parse body))) (_ (syntax-error sexp))))) (define-keyword 'if ; (if E1 E2 E3) (lambda (sexp) (match sexp (`(if ,test ,con ,alt) (if->exp (parse test) (parse con) (parse alt))) (_ (syntax-error sexp))))) (define-keyword 'primop ; (primop O E*) (lambda (sexp) (match sexp (`(primop ,op ,@args) ;; Assume valid primop -- type reconstruction will verify ;; number of args. (primop->exp op (map parse args))) (_ (syntax-error sexp))))) (define-keyword 'let ; (let ((I E)*) E0) (lambda (sexp) (match sexp (`(let (,@bspecs) ,body) (let->exp (map parse-binding-spec bspecs) (parse body))) (_ (syntax-error sexp))))) (define-keyword 'letrec ; (letrec ((I E)*) E0) (lambda (sexp) (match sexp (`(letrec (,@bspecs) ,body) (letrec->exp (map parse-binding-spec bspecs) (parse body))) (_ (syntax-error sexp))))) (define-keyword 'set! ; (set! I E) (lambda (sexp) (match sexp (`(SET! ,(sym->sexp id) ,sexp) (set!->exp id (parse sexp))) (_ (syntax-error sexp))))) (define-keyword 'begin ; (begin E1 E2) + sugars (lambda (sexp) (match sexp (`(BEGIN) (unit->exp)) (`(BEGIN ,sexp) (parse sexp)) (`(BEGIN ,sexp1 ,sexp2) (begin->exp (parse sexp1) (parse sexp2))) (`(BEGIN ,sexp1 ,sexp2 ,@rest) (begin->exp (parse sexp1) (parse `(BEGIN ,sexp2 ,@rest)))) (_ (syntax-error sexp))))) ;; Sugar ;; (and) ==> #t ;; (and E) ==> E ;; (and E0 E+) ==> (if E0 (and E+) #f) ;; (define-keyword 'and (lambda (sexp) (match sexp (`(and ,@exp-list) (parse (letrec ((recur (lambda (exps) (match exps ((null) `#t) (`(,exp) exp) ((cons first rest) `(if ,first ,(recur rest) #f)))))) (recur exp-list)))) (_ (syntax-error sexp))))) ;; (or) ==> #f ;; (or E) ==> E ;; (or E0 E+) ==> (if E0 #t (or E+)) ;; (define-keyword 'or (lambda (sexp) (match sexp (`(or ,@exp-list) (parse (letrec ((recur (lambda (exps) (match exps ((null) `#f) (`(,exp) exp) ((cons first rest) `(if ,first #t ,(recur rest))))))) (recur exp-list)))) (_ (syntax-error sexp))))) ;; (cond (E E)* (else E)) (define-keyword 'cond (lambda (sexp) (match sexp (`(cond) (syntax-error sexp)) (`(cond (else ,default)) (parse default)) (`(cond (,predicate ,consequent) ,@clauses) (parse `(if ,predicate ,consequent (cond ,@clauses)))) ))) ;; (list E*) ;; (define-keyword 'list (lambda (sexp) (match sexp (`(list ,@exp-list) (parse (letrec ((recur (lambda (exps) (match exps ((null) `(null)) ((cons first rest) `(cons ,first ,(recur rest))))))) (recur exp-list)))) (_ (syntax-error sexp))))) ;; Module stuff (define-keyword 'module ; (module (define I E)*) (lambda (sexp) (match sexp (`(module ,@fspecs) (module->exp (map parse-definition fspecs))) (_ (syntax-error sexp))))) (define-keyword 'with ; (with (I*) E1 E2) (lambda (sexp) (match sexp (`(with (,@formals) ,mod ,body) (with->exp (map parse-formal formals) (parse mod) (parse body))) (_ (syntax-error sexp))))) ;; Now return the whole parser table (make-parser-table (^ keywords) (^ lookup)) ))) (define parser-for-keyword (match keyword-table ((make-parser-table keywords parser-lookup) parser-lookup))) (define all-keywords (match keyword-table ((make-parser-table keywords parser-lookup) keywords))) ;;; Type expression parser (define (parse-type sexp) (match sexp ((sym->sexp sym) (base->type sym)) (`(-> (,@arg-types) ,result-type) (compound->type arrow-constructor (cons (parse-type result-type) (map parse-type arg-types)))) ;; **MODULES** (`(moduleof ,@fields) (parse-module-type fields)) ;; **MODULES** (`(,(sym->sexp name) ,@types) (compound->type name (map parse-type types))) (_ (error "Invalid type expression syntax " sexp)))) ;; **MODULES** (define (parse-module-type fields) (if (null? fields) (moduleof->type (null) (null)) (match (parse-module-type (cdr fields)) ((moduleof->type names typs) (match (car fields) (`(val ,id ,typ) (moduleof->type (cons id names) (cons (parse-type typ) typs))) (_ (error "Invalid syntax in moduleof field entry " (car fields))))) (_ (error "PARSE-MODULE-TYPE: this shouldn't happen!" fields))))) ;; **MODULES** ; Type expression unparser (define (unparse-type type) (match (prune type) ((base->type sym) (sym->sexp sym)) ((compound->type constructor operands) (if (same-constructor? constructor arrow-constructor) `(-> (,@(map unparse-type (cdr operands))) ,(unparse-type (car operands))) `(,(sym->sexp constructor) ,@(map unparse-type operands)))) ;; **MODULES** ((moduleof->type names operands) `(moduleof ,@(map2 (lambda (id t) `(val ,(sym->sexp id) ,(unparse-type t))) names operands))) ;; **MODULES** ((tvariable->type tvar) (sym->sexp (tvariable->symbol tvar))) ((unknown->type) '(*unknown*)))) ; Parse a type schema (generic (I*) T) (define (parse-schema sexp) (match sexp (`(generic (,@names) ,type) (let ((names (map (lambda (name) (match name ((sym->sexp name) name) (_ (error "Invalid type schema parameter: " name)))) names))) (let ((tvars (map new-tvariable names))) (make-schema tvars (substitute-for-names (map tvariable->type tvars) names (parse-type type)))))) (_ (make-schema (null) (parse-type sexp))))) ; substitute-for-names is a kludge, to be used only by initialization ; code. Other ways to do this: ; ; (1) change the type parser to take an environment argument; ; ; (2) generalize substitute-into-type so that it; can substitute for ; either names or tvars; ; ; (3) change the representation of schemas so that the generic variables ; in the type are not tvars but rather names. (define (substitute-for-names types names type) (match type ((tvariable->type _) type) ;shouldn't happen ((base->type name) (letrec ((loop (lambda (ts ns) (if (null? ts) type (if (same-name? name (car ns)) (car ts) (loop (cdr ts) (cdr ns))))))) (loop types names))) ((compound->type c args) (compound->type c (map (lambda (arg) (substitute-for-names types names arg)) args))) ;; **MODULES** ((moduleof->type fieldnames args) (moduleof->type fieldnames (map (lambda (arg) (substitute-for-names types names arg)) args))) ;; **MODULES** (_ (error "SUBSTITUTE-FOR-NAMES: This shouldn't happen! " (unparse-type type))))) (define (unparse-schema s) (match s ((make-schema tvars type) `(generic (,@(map sym->sexp (map tvariable->symbol tvars))) ,(unparse-type type))))) ;;;---------------------------------------------------------------------------- ;;; STANDARD TYPE ENVIRONMENT (define standard-type-bindings (list ; Arithmetic '(+ (-> (int int) int)) '(- (-> (int int) int)) '(* (-> (int int) int)) '(/ (-> (int int) int)) ; Relational '(= (-> (int int) bool)) '(/= (-> (int int) bool)) '(< (-> (int int) bool)) '(> (-> (int int) bool)) '(<= (-> (int int) bool)) '(>= (-> (int int) bool)) ; Logical '(and? (-> (bool bool) bool)) '(or? (-> (bool bool) bool)) '(not? (-> (bool) bool)) ; Symbols '(sym=? (-> (sym sym) bool)) ; Strings '(string=? (-> (sym sym) bool)) ; Lists '(cons (generic (t) (-> (t (list-of t)) (list-of t)))) '(car (generic (t) (-> ((list-of t)) t))) '(cdr (generic (t) (-> ((list-of t)) (list-of t)))) '(null (generic (t) (-> () (list-of t)))) '(null? (generic (t) (-> ((list-of t)) bool))) '(append (generic (t) (-> ((list-of t) (list-of t)) (list-of t)))) )) (define standard-type-environment (extend-by-schemas empty-type-environment (map (lambda (b) (match b (`(,(sym->sexp name) ,_) name))) standard-type-bindings) (map (lambda (b) (match b (`(,_ ,schema) (parse-schema schema)))) standard-type-bindings))) ;;;---------------------------------------------------------------------------- ;;; UTILITIES (define (member? pred) (lambda (elt lst) (letrec ((loop (lambda (lst) (if (null? lst) #f (if (pred elt (car lst)) #t (loop (cdr lst))))))) (loop lst)))) (define (map2 proc lst1 lst2) (if (or (null? lst1) (null? lst2)) (null) (cons (proc (car lst1) (car lst2)) (map2 proc (cdr lst1) (cdr lst2))))) ;;;---------------------------------------------------------------------------- ;;; TOP-LEVEL (define (recon sexp) (begin (reset-tvariable-counter!) (unparse-type (reconstruct (parse sexp) standard-type-environment))))