• No results found

Further Reading

Exercise 4. 14 [ ] What is wrong with this expression?

letrec ? even(? odd, ? x) = if zero? (x) then 1 else (odd sub1(x)) in letrec ? odd

(bool x) = if zero? (x) then 0 else (even odd sub1 (x)) in (odd 13)

An <optional-type-exp> is either a type expression or a ?. To use optional type expressions in ordinary expressions, we change the productions for proc-exp and letrec-exp to use <optional-type-exp>:

To deal with the ?'s, we add a new kind of type, called a type variable. A type variable stands for a type that is not yet known. Each type variable contains a serial number that identifies it uniquely, and a container, which is a vector of length 1. The vector's single element can be either (),

meaning that nothing is known about this type: empty, or else a type: full. The checker will fill the type variable when it deduces something about the type. Once a type variable is full, its contents will never be changed. Such a variable is sometimes called single-assignment or write-once. The procedures that deal with types treat a type variable as a placeholder for the type it contains (if any).

The procedures for manipulating type variables are shown in figure 4.14. The procedure fresh- tvar creates a fresh type variable, with a globally unique value for its counter, and with its vector initialized to (), meaning that nothing is known yet about this type.

Type variables should not be confused with the type identifiers of section 4.3. Type identifiers have lexical scope and are kept in type environments, but type variables are global and are kept in Scheme's heap.

We change all calls to the procedure expand-type-expression so that they instead call

expand-optional-type-expression. This change is necessary to match the grammar.

When the procedure expand-optional-type-expression encounters a type expression, it calls expand-type-expression; when it encounters a ?, it emits a type variable.

We next modify check-equal-type! to handle type variables. The new version of check-

equal-type! will perform a task that may be described as "check to see if the two types can be

With the new behavior for check-equal-type!, type-of-expression recursively walks through the program. As it walks through the program, it calls check-equal-type! to take careful note of how each symbol is used and to make whatever deductions are possible about the types.

This equality-centered approach can be used to simplify the code for type-of-application:

(define type-of-application (lambda (rator-type actual-

types rator rands exp) (let ((result-type (fresh-tvar))) (check- equal-type! rator-type (proc-type actual-types result- type) exp) result-type)))

This version makes a type variable result-type for the as-yet-unknown type of the entire application. It then checks to see that the operator is a procedure that accepts arguments of the same types as the operands and that produces a result that is the same as the type of the

application. As a result of this matching, some deductions will be made about result-type, and those deductions will be stored in result-type where they will be visible to everyone. The remainder of the code for type-of-expression and its auxiliary procedures can be used unchanged, since each subexpression is considered exactly once.

Before considering the details of check-equal-type!, let's see how we might do this process by hand.

As type-of-expression walks through the code, it introduces one type variable for each

formal parameter whose type is not declared, and one additional type variable for each application. For each node in the abstract syntax tree of the expression we get some equations between types and type variables.

and when typing an application (rator rand1 . . . randn) in tenv, it must be that

This says that at each application, the operator must be a procedure that maps the types of the operands to the type of the entire application.

Finally, when typing proc expression proc (x1 . . . xn) exp in tenv, we must have

where tenvbody is the type environment in which the body exp is to be typed.

So to deduce the type of an expression, we'll introduce a type variable for each bound variable and each application, and write out an equation for each compound expression using the rules above. Since we type each subexpression in exactly one type environment, we don't need to worry about the different values of tenv.

Then all we have to do is solve the resulting equations. The code solves these equations by calling

check-equal-type!, but we first consider how to solve these equations by hand.

As an example, consider proc (f,x) (f +(1,x) zero? (x)). Let's start by making a table of all the bound variables and applications in this expression, and assigning a type variable to each one:

Expression Type Variable

f tf x tx (f +(1,x) zero? (x)) t1 +(1,x) t2 zero? (x) t3

We know, by the procedure rule, that the type of the entire expression is (tf * tx -> t1). We must find the types tf,tx, and t1.

Now, for each compound expression (either an application or a conditional; in this example we have only applications), we can deduce a type equation:

Expression Type Equation

(f +(1,x) zero? (x))

tf = (t2 * t3 -> t1)

+(1,x) (int * int -> int) = (int * tx -> t2)

zero? (x) (int -> bool) = (tx -> t3)

The first equation says that the procedure f must be prepared to take a first argument of the same type as +(1,x) and a second argument of the same type as zero? (x), and its result must be of the same type as the application. The other equations follow similarly: in each case the left- hand side is the type of the operator, and the right-hand side is a type constructed from the types of the operands and the type of the application. The right-hand side is the type of those procedures that "fit" in this application.

We can fill in tf, tx, t1, t2, and t3 in any way we like, so long as they satisfy the three type equations:

tf = (t2 * t3 -> t1)(int * int -> int) = (int * tx -> t2)(int - > bool) = (tx -> t3)

We can solve such equations by systematic inspection. From the second equation, we conclude

tx = intt2 = int

Substituting these values into the remaining equations, we get

tf = (int * t3 -> t1)(int -> bool) = (int -> t3)

From the last equation, we deduce

t3 = bool

and substituting this into the first equation yields

We have now solved for all the type variables, except t1:

tf = (int * bool -> t1)tx = intt2 = intt3 = bool

This process of repeated inspection and substitution is called unification.

We conclude from this calculation that we could assign our original term proc(f,x) (f +(1,

x) zero?(x)) the type (tf * tx -> t1) or the type ((int * bool -> t1) *

int -> t1) for any choice of t1. This code will work for any type t1; we say it is

polymorphic in t1.

This is reasonable, since the first argument f must be a procedure of two arguments. Its first argument must be an int (because + always produces an int, and its second argument must be a

bool, but its output could be anything. The second argument x must be an int because it is used both as an argument to + and as an argument to zero?. The output from the entire procedure will be the same as the output from f.

Let us consider the same example, but with the + changed to a cons, with type (int *

(list int) -> (list int)). Then the equations would be

Expression Type Equation

(f cons(1,x) zero?(x))

tf = (t2 * t3 -> t1)

cons(1,x) (int * (list int) -> (list int)) =

(int * tx -> t2),

zero?(x) (int -> bool) = (tx -> t3)

From the second equation, we deduce

tx = (list int)t2 = (list int)

Substituting these values into the third equation, we get

(int -> bool) = ((list int) -> t3)

But there is no value for t3 that will make these types the same: for them to be equal, we must have int = (list int), which is false.

So this is an example where check-equal-type! reports an error. This is the correct

behavior, since the expression is inconsistent in its use of x: the first occurrence of x requires it to be a list of ints, and the second occurrence requires it to be an int. So the expression should be rejected.

Exercise 4.15 [ ] How can this approach be extended to do type inference by hand for a let expression?