The definition of types is unchanged from section 4.2, but we modify expand-type-
expression to take a type environment and expand the bindings of any type identifiers it sees
(define expand-type-expression (lambda (texp tenv) (cases type- exp texp (tid-type-exp (id) (find-typedef tenv id)) (int-type- exp () (atomic-type 'int)) (bool-type-exp () (atomic-
type 'bool)) (proc-type-exp (arg-texps result-texp) (proc-
type (expand-type-expressions arg-texps tenv) (expand-type- expression result-texp tenv))))))(define expand-type-
expressions (lambda (texps tenv) (map (lambda (texp) (expand- type-expression texp tenv)) texps)))
Figure 4.11 Expanding type expressions
Every use of expand-type-expression is now modified to take the type environment as a parameter. For example, we write:
(define type-of-proc-exp (lambda (texps ids body tenv) (let ((arg- types (expand-type-expressions texps tenv))) (let ((result-
type (type-of-expression body (extend-
tenv ids arg-types tenv)))) (proc-type arg-types result-type)))))
The procedure type-of-lettype-exp works like type-of-letrec-exp, except that when it checks the procedure declarations, it does so in an environment where the type identifier is bound to its representation, and when it checks the body, it does so in an environment where the type identifier is bound to a new atomic type.
Recall that a typical lettype expression looks like
To check this expression, we build two type environments. The type environment tenvimplementation
is used as a basis for checking the procedure bodies ei that form the implementation of the data
type. The type environment tenvclient is used for checking body, which forms the client or user of
the data type.
We must also bind each ordinary identifier to its type according to the usual scoping rules. To do this, we proceed by analogy with letrec. As with letrec, the procedure body is checked in an environment in which the procedure's formal parameters and all the letrec-bound procedure names are bound to their declared types. Furthermore, the type expressions should be expanded using tenvimplementation, because the procedure body ei is inside the abstraction boundary, and so the
representation of the type tid as t should be visible. Hence the type environment for ei should be
where t* means the expansion of the type expression t in tenvimplementation.
Similarly, the type environment for the body of the lettype should be
where t† denotes the expansion of the type expression t in tenvclient. This is the correct expansion,
and therefore should see tid as an atomic type, on which the only available operations are the pi.
Every time we extend a type environment, we do so with a type expression that is expanded in the same type environment. Therefore we define the auxiliary procedures
(define extend-tenv-with-typedef-exp (lambda (typename texp tenv) (extend-tenv- with-typedef typename (expand-type-expression texp tenv) tenv)))
(define extend-tenv-with-type-exps (lambda (ids texps tenv) (extend- tenv ids (expand-type-expressions texps tenv) tenv)))
The code is shown in figure 4.12. We proceed much as we did for type-of-letrec-exp. The procedure first extracts the various portions of the lettype. The variable rhs-texps is bound to the list of type expressions associated with the procedures. We must use type expressions here, rather than types, because these type expressions will be expanded differently in the procedure bodies than in the body of the lettype.
The type environments tenv-for-implementation, tenv-for-client, tenv-for-proc, and
tenv-for-body are then built. In tenv-for-client, the type name is bound to a fresh atomic type. This code uses fresh-type, which creates a new type with a name similar to its argument:
(define fresh-
type (let ((counter 0)) (lambda (s) (set! counter (+ counter 1)) (atomic- type (string->symbol (string-append (symbol-
>string s) (number->string counter)))))))
Successive evaluations of (fresh-type 'xx) will return (atomic-type xx1), (atomic-type xx2), etc.
Once the various type environments are constructed, the type of each of the procedure bodies is computed and compared to the specified result type,
(define type-of-lettype-exp (lambda (type-name texp result- texps proc-names arg-texpss idss bodies lettype-
body tenv) (let ((the-new-type (fresh-type type-name)) (rhs- texps (map proc-type-exp arg-texpss result-
texps))) (let ((tenv-for-implementation (extend-tenv- with-typedef-exp type-name texp tenv)) (tenv-for- client (extend-tenv-with-typedef type-name the- new-type tenv))) (let ((tenv-for-proc (extend-tenv- with-type-exps proc-names rhs-texps tenv- for-implementation)) (tenv-for-body (extend- tenv-with-type-exps proc-names rhs-texps tenv-for-
client))) (for-each (lambda (ids arg-texps body result- texp) (check-equal-type! (type-of-
expression body (extend-tenv-with-type- exps ids arg-texps tenv-for-
proc)) (expand-type-expression result- texp tenv-for-proc) body)) idss arg-
texpss bodies result-texps) (type-of- expression lettype-body tenv-for-body))))))
lettype myint = int myint zero () = 1 myint succ (myint x) = add1
(x) myint pred (myint x) = sub1(x) bool iszero? (myint x) = zero? (-(x, 1)) in (succ (zero))
type: myint8lettype myint = int myint zero () = 1 myint succ (myint x) = add1 (x) myint pred (myint x) = sub1(x) bool iszero? (myint x) = zero? (-(x, 1)) in add1((zero))types didn't match: int != myint9 in(app-exp (var-exp zero) ()) lettype ff = (int -> int) ff zero-ff () = proc (int k) 0 ff extend-
ff (int k, int val, ff old-ff) = proc (int k1) if zero? (-
(k1, k)) then val else (apply-ff old- ff k1) int apply-ff (ff f, int k) = (f k)in let ff1 = (extend-ff 1 11 (extend- ff 2 22 (zero-ff))) in (apply-ff ff1 2)type: intlettype ff = (int -
> int) ff zero-ff () = proc (int k) 0 ff extend-ff (int k, int val, ff old- ff) = proc (int k1) if zero? (-
(k1, k)) then val else (apply-ff old- ff k1) int apply-ff (ff f, int k) = (f k)in let ff1 = (extend-ff 1 11 (extend- ff 2 22 (zero-ff))) in (ff1 2)rator not a proc type:(var-exp ff1)
had rator type ff117
using tenv-for-proc, which extends tenv-for-implementation. If all of these tests are passed, then the type of the lettype body is computed in tenv-for-body, which extends
tenv-for-client, and is returned as the type of the entire expression.
The results of this system on the examples from the beginning of the section are shown in figure 4.13. Each attempt to break the abstraction boundary by performing an illegal operation is detected as a type error.