6. FASE ANALÍTICA
6.2. ANÁLISIS DE DATOS ATENDIENDO A DIVERSAS CARACTERÍSTICAS DE LOS
1. proc (f,g,p,x) if (p (f x)) then (g 1 x) else add1 ((f x))
2. proc (x,p,f) if (p x) then add1 (x) else (f p x)
3. proc (x,p,f,g) if (p add1 (x)) then add1 ((f x)) else (g f x)
4. let x = 3 f = proc (x) add1 (x) in (f x)
Treat add1 as if it were a procedure of type (int -> int), and + as if it were a procedure of type
(int * int -> int).
How does check-equal-type! solve equations like the ones in the preceding examples? Instead of simply calling equal?, check-equal-type! will recursively traverse the type structures it is asked to equate. If it encounters a type variable that contains a type, it recurs on that type. If it encounters a type variable that is empty, then it fills the type variable with the other type. Figure 4.15 shows this algorithm at work on the example of page 157. In the initial equation, the left-hand side is the type variable tf, so check-equal-type! fills it by inserting a reference to the right-hand side (shown in the figure as a dashed line). The resulting data structure is shown in figure 4.15(a).
Figure 4.15(b) shows the data structure after processing the second equation. The equation is set up as shown. The type variable t2 is shared by the first and second equations. The procedure
check-equal-type! does a recursive traversal of the two trees. It observes that both sides are
2-argument procedure types, and both have first argument int. For the second argument, one side is int and the other is tx, so it fills tx with int. It then observes that the result type on one side is int and on the other is t2, so it fills t2 with int, yielding the structure shown in the figure. After processing the third equation, the data structure looks like figure 4.15(c). Again, check-
equal-type! observes that both sides are 1-argument procedure types. The argument on the
left side is int. The argument on the right side is also int, because the right-side argument is
tx, which has already been filled with int. Thus the step in the manual algorithm of substituting the new values into the remaining equations is unnecessary here because the substitution is done automatically in the data structure. Last, check-equal-type! observes that the result type is
left and t3 on the right, so it fills t3 with bool. Thus, check-equal-type! simulates the hand solution shown earlier and gets the same information.
Figure 4.15(d) shows the data structures built by check-equal-type! for the example on page 159. Here the first two equations have been processed, and check-equal-type! has begun to process the third equation. Comparing the types of the first argument, it discovers int
on the left, but tx which is (list int) on the right. Since there are no type variables in int
or (list int), there is no way to make these two types equal. Therefore check-equal-
type! reports that the equations cannot be solved.
Though both the checker of section 4.2 and the inferencer of this section use a recursive traversal of the program to be checked, they work very differently. The checker always computes the type of an expression from the type of its subexpressions. The type inferencer recursively walks
through the program, taking careful note of how each symbol is used and making deductions about the types whenever possible. In the manual system we have used above, the notes take the form of equations. In the implemented system, the note-taking is automated, and takes the form of new equations, introduced with check-equal-type!. Solving the equations consists of recursively walking through the equations and making substitutions as necessary. Setting the contents of a type variable effectively substitutes the new value for the type variable everywhere it appears. The code for check-equal-type! is shown in figure 4.16. The procedure checks each way in which t1 and t2 can be equal:
1. It first determines whether t1 and t2 are the same Scheme value. If so, it succeeds and returns an unspecified value.
2. If t1 is a type variable, it calls the procedure check-tvar-equal-type! on t1 and t2, passing exp for error-reporting purposes.
3. Symmetrically, if t2 is a type variable, it calls check-tvar-equal-type! on t2 and t1. 4. If t1 and t2 are atomic types, it determines whether they have the same name; if not, they cannot be equal, and an error is reported.
5. If t1 and t2 are both procedure types, it determines whether they have the same number of arguments. If so, it recurs on each of the argument types and on the result type.
(a) after processing first equation
(b) after processing second equation
(c) after processing third equation
(d) about to discover an unsatisfiable equation
(define check-equal-
type! (lambda (t1 t2 exp) (cond ((eqv? t1 t2)) ((tvar-
type? t1) (check-tvar-equal-type! t1 t2 exp)) ((tvar-type? t2) (check- tvar-equal-type! t2 t1 exp)) ((and (atomic-type? t1) (atomic-
type? t2)) (if (not (eqv? (atomic-type- >name t1) (atomic-type->name t2))) (raise-type- error t1 t2 exp))) ((and (proc-type? t1) (proc-
type? t2)) (let ((arg-types1 (proc-type->arg- types t1)) (arg-types2 (proc-type->arg- types t2)) (result-type1 (proc-type->result- type t1)) (result-type2 (proc-type->result- type t2))) (if (not (= (length arg-
types1) (length arg-types2))) (raise-wrong-number-of- arguments t1 t2 exp) (begin (for-
each (lambda (t1 t2) (check-equal-
type! t1 t2 exp)) arg-types1 arg-types2) (check- equal-type! result-type1 result-
type2 exp))))) (else (raise-type-error t1 t2 exp)))))(define check- tvar-equal-type! (lambda (tvar ty exp) (if (tvar-non-
empty? tvar) (check-equal-type! (tvar-
>contents tvar) ty exp) (begin (check-no-
occurrence! tvar ty exp) (tvar-set-contents! tvar ty)))))
Figure 4.17 Creating a circular type
The procedure check-tvar-equal-type! deals with the case of equating a type variable
tvar and a type ty. If tvar contains a type, then we recur on its contents, calling check-
equal-type! to equate that type to ty.
If tvar is empty, we would like to set the contents of tvar to ty, thus making them equal. However, we have one more important detail to address: check-equal-type! recurs on the structure of its arguments. So if the contents of the type variables create a cyclic structure,
check-equal-type! might fail to terminate. So we first call check-no-occurrence! to
make sure that the type variable tvar does not occur within the type ty. For example, consider the equation
t1 = (int -> t1)
If we filled in t1, as shown in figure 4.17, we would get a cycle, which would cause check-
equal-type! to loop the next time it encountered t1.
After first saving ty for error-reporting purposes, check-no-occurrence! recurs on the structure of ty. If ty is an atomic type, then tvar cannot occur in it. If ty is itself a type
variable, then the code checks to see if it is the same variable as tvar; if it is, an error is reported. Last, if ty is a procedure type, then we recur on the argument types and the result type. (See figure 4.18.)
There is only one more place in the inferencer where we need to be concerned about type variables. That is in type-to-external-form (figure 4.19). If type-to-external- form is given a type variable, then if the variable is empty, it should produce a suitable symbol; if the variable contains a type, the result should be obtained by recurring on that type.