Interactive Type Debugging - CiteSeerX

0 downloads 0 Views 279KB Size Report
\Most large computer programs are never completely understood; if they were, ... Some progress has been made towards meeting the ..... implemented and it is unclear how interactive program repair would be ... repair the program by himself.
Interactive Type Debugging Proposal

Bruce J. McAdam [email protected] Supervisor: Stephen Gilmore

Laboratory for Foundations of Computer Science June 1998

Abstract

This proposal introduces a pragmatically motivated project in the eld of type systems for programming languages. The practical concern which inspires this project is the diculty programmers experience when trying to debug programs. In particular debugging programs deemed unacceptable by the type system of the programming language. We will explore the background to the project: why we use strong static type systems, why debugging type errors is unlike debugging execution errors and what previous e orts have been made to solve the problem. Following this, the objectives of this project are de ned, both generally as a list of the principles to be applied and speci cally as a list of the features resulting software, an interactive type debugger, should have and the theorems which must accompany this software. Several pieces of work have already been produced as part of this project, these are described, along with the work still to be completed and an indication of the feasibility of the project.

1

Contents

1 Introduction 2 Background and motivation 2.1 2.2 2.3 2.4 2.5 2.6

Hindley-Milner type systems . . . . . . Current handling of type errors . . . . . Diculty with present systems . . . . . Inherent diculty with Hindley-Milner . Di erence from traditional debugging . Diculty in characterising errors . . . .

3 Review of background and related work 3.1 3.2 3.3 3.4 3.5

Hindley-Milner type system . . . . . ML family of languages . . . . . . . Other inference algorithms . . . . . . Explanation of types and type errors User interfaces . . . . . . . . . . . .

. . . . .

. . . . .

3 3

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

4 5 5 6 7 7

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. 8 . 8 . 8 . 9 . 10

8

4 Proposal

10

5 Progress so far

13

6 Plan for future work

16

7 Conclusions and summary

17

4.1 Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 4.2 Proposal | an interactive type debugger . . . . . . . . . . . . . . . . . . . . . . . 12 5.1 Symmetric type inference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 5.2 A new data structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 5.3 Implementations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

6.1 Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 6.2 Analysis and debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2

1 Introduction An increasing number of programming languages have sophisticated semantic features such as higher-order functions, polymorphism and sound type systems. These include the applicative languages Standard ML, Caml and Haskell. Such features allow programmers to write complex programs more easily than in traditional imperative languages such as C and Fortran, and with greater likelihood of correctness. Furthermore programmers can more easily analyse programs when using these programming languages, for example it is easier to prove correctness or to statically analyse the runtime of programs. Robin Milner writes of Standard ML (in the introduction to [FF98]) \Most large computer programs are never completely understood; if they were, they wouldn't go wrong so often and we would be able to describe what they do in a scienti c way. A good language should help to improve this state of a airs. \There are many ways of trying to understand programs. People often rely too much on one way, which [: : :] consists of running a partly-understood program to see if it does what expected. Another way, which ML advocates, is to install some means of understanding in the very programs themselves. \[: : :] People who program in a language with a strong type system like this one, often say their programs have fewer mistakes, and they understand them better." Despite the features which allow programmers to be more con dent of program correctness, human fallibility means that programmers do still make mistakes while writing programs. Andrew Appel writes in his critique of Standard ML [App92] \Programmers make mistakes [: : :] \So I must nd my mistakes and x them. Any help that the programming environment can give me in nding mistakes is most welcome. As a practical matter, I have found that the vast majority of my mistakes are found at compile time by the ML type checker" Appel quite fairly points out that most of his mistakes lead to type errors which are found by the type checker. He is assured by the type checker that once his program has been compiled it will never fail at run time with a `type' error (as can happen in a C program when, for example, the wrong type of value is read from a union type). Many other mistakes programmers make also lead to type errors, consequently the type checker can be of great help in locating these as well. Appel suggests (as have many other programmers and authors) that any help in nding and repairing mistakes is will be welcomed. The project proposed in this document will investigate this area of the pragmatics of programming languages. At the moment programmers frequently complain that the error messages produced by compilers when type errors are found are vague or even misleading. The aim of this project is twofold, rstly to nd a way of describing type errors, not just for giving error messages but also as an internal representation for compilers. The second aim is to use this information to actively help the programmer debug the program | to create an interactive type debugger. In the remainder of this proposal, you can nd background to the project and motivation in Section 2, to accompany this a review of related material is given in Section 3. Section 4 explains the proposed content of the project. Some progress has been made towards meeting the proposed objectives, this is described in Section 5. The remainder of the work required to meet the objectives is in Section 6, along with an indication of the time likely to be required. Finally, Section 7 summarises and concludes.

2 Background and motivation This proposal is related to a wide range of previous work in semantics and pragmatics of programming languages. This section describes the context in which the project will take place and, describes the motive of the project. 3

2.1 Hindley-Milner type systems

Once consequence of research into type theory is a clear understanding of a sound type-system, a set of rules which can say whether a program can be guaranteed free of type errors. Sound type-systems have been designed for a number of programming languages, notably Standard ML [MTH97]. The great advantage they give the programmer is that he can be sure a validly typed program will never crash due to a type error (though there may be problems, for example if the compiler is faulty). An in uential type system is the Hindley-Milner type system [Mil78], on which the type system of Standard ML is based. From the point of view of a programmer the type system has three key features Polymorphism allows programmers to write functions taking a range of types, for example the Standard ML length function has type 'a list -> int, where 'a is a type variable which can be instantiated with any type. Implicit typing means that programmers do not need to add type annotations to programs. This saves time when programming and means that more complex types (such as those of higher order functions) can be used without the extra complication of giving the types. Most programming languages using Hindley-Milner type systems allow type annotations for speci cations (such as Standard ML signatures). Type checking involves inference of type annotations. Purely static type systems ensure that running programs will never crash because of a type error. All the type checking takes place before the program is compiled. The existence of these features indicates that while the type-system is sound and can be implemented in a compiler, it does not force the programmer to be extremely terse or to program in a restrictive manner. In fact, programmers using Hindley-Milner typed languages often write more exible programs than programmers using monomorphic programming languages because they can be sure that the polymorphism and higher order features they adopt are safe. This is in contrast to programming in C, in which programmers have to give type annotations for types such as void * but these annotations are not sucient for the compiler to decide whether or not the program is save. Complex C programs often crash during run-time because of this de ciency of the type system. The type system is expressed as a set of rules for deriving typings. A typing, ? ` e :  , is a judgement asserting a type,  , which an expression, e, can take within a type environment (or assumption set), ?, (a discussion of the properties of typings can be found in [Jim95]). Figure 1 shows a mini-ML language and Hindley-Milner type system for it. The derivation rules are nondeterministic (for example, rule Taut in the gure allows us to instantiate the polymorphic type variables with any types). Thus for a particular expression and type environment, the expression may take a number of di erent types. All expressions do, however, have a principal type scheme under a particular environment. The programming language `let' construction allows an expression to be used polymorphically according to its principal type scheme. The inference algorithm makes use of the principal type property : every typeable expression has a type scheme which can be instantiated to create every possible type of the expression (and no other types). The standard algorithm takes a type environment, ?, and expression, e, and produces a type,  , and substitution, S , such that S ? ` e :  and  is the type in the principal type scheme of e under ?. The classical inference algorithm, W , is given in Figure 2. Note the way substitutions are used to convert a derived judgement into a more speci c one, and the use of uni cation.

4

Figure 1 A mini-ML language and type system Syntax e ::= x j e 0 e1 j x:e0 j let x = e0 in e1

Semantic objects  Type variables : : :  Base types t ::= int j  Types

:::  ::= j t j 0 ! 1

Type schemes  = 8 ^:  Type Environments ? = fx0 : 0 ; : : :g Type derivation rules 

Taut

App ? ` e0 : ? `!e e :? ` e1 :  0

(x : 8 ^: ); ? ` x :  [^ = ^]

0 1

0

x: `e: Abs ? ?`; x:e : ! 0

0

0

0

 ?; x : 8 ^: ` e1 :  Dec ? ` ?e0`: let x = e in e :  0

1

0

0

8 2 ^: 62 F V (?)

2.2 Current handling of type errors

Most compilers for Hindley-Milner languages use algorithm W [Mil78]. When presented with a program containing type errors, this algorithm will reject the program at some point. When this happens, the compiler generates an error message. Let us consider an example in Standard ML to see what the messages are like: fun f I = (I 3, I true)

(The programmer has tried to write a function, f, taking a polymorphic function, I, and applying it to 3 and true). When W starts processing the abstraction, it generates a type-variable 'a and assumption I : 'a. W then progresses to the application I 3 and infers the substitution 'a 7! int -> 'b. Type checking I true then fails because the function apparently has type int -> 'b but the argument has type bool. The compiler issues a message such as Type error on line 3 Cannot apply I : int -> 'b

to

true : bool

2.3 Diculty with present systems

One problem with the usual type inference is that it has a left-to-right bias. A failure to unify caused by a con ict between two subexpressions will be announced as a problem in the rightmost 5

Figure 2 The classical type inference algorithm W W (?; x) = (Id; ?(x)) W (?; e0 e1 ) = let (S0 ; 0 ) = W (?; e0 ) (S1 ; 1 ) = W (S0 ?; e1 ) V = U (S1 0 ; 1 ! ) ( is a new type variable)

in

W (?; x:e) =

let in

W (?; let x = e0

in e1) = let in

(V S1 S0 ; V ) (S;  ) = W (? + (x : ); e) (S; S !  ) (S0 ; 0 ) = W (?; e0 ) (S1 ; 1 ) = W (S ? + (x : close(0 ); e1 )) (Closure univerally quanti es tyvars not in S ?) (S1 S0 ; 1 )

subexpression. This is because information derived in the left hand side of an expression is used (as if it is correct) in the right hand side of the expression. Another diculty related to the left-to-right bias is that important parts of the program may not be examined until late in inference, and therefore the information in them will not be taken into account before an error is found. A particular example of this is ML signatures. A signature speci es the types of most values de ned in a structure, the connection between the signature and the structure occurs at the top of the syntax tree, hence types given in signatures are not used when an inconsistency is found inside a structure. The left-to-right bias and diculty of information not being taken into account are both consequences of the order in which type inference occurs. A second problem is that the phrasing of error messages are often vague or confusing. The perspective the compiler takes di ers from that of the programmer, so it is dicult to tell how to make changes at the syntax level such that the types can be changed and the program repaired.

2.4 Inherent diculty with Hindley-Milner

Recall that programmers see three main advantageous features of Hindley-Milner type systems. Of course, every advantage comes at a price and there are three drawbacks to Hindley-Milner typesystems, each resulting from one of the advantages. Polymorphism means that con icts may go undetected. An incorrectly derived polymorphic function may have an adequate type for some applications but not for others. This leads to an unusual pattern of error messages from which it is hard for the programmer to decide whether or not the de nition is correct. Implicit typing makes it harder to say which part of a program is incorrect. This is because there is no `standard' to mark against. Unlike in an explicitly typed language, errors are not detected as discrepancies between a speci cation (which is assumed to be correct) and an 6

type inferred from the use of an expression; but are detected as di erences between several pieces of inferred information. Purely static type inference makes the debugging process dicult. Programming environments do not allow programmers to run untypeable programs, so traditional debugging techniques cannot be applied. Programmers must learn to think about the problems in their programs without the bene t of seeing how they run and crash. The rst two of these drawbacks explain why it is easy to make and so dicult to locate mistakes when using Hindley-Milner type-systems. The third tells us that it is essential to overcome these problems as only reports from the compiler are available to help debugging, the programmer cannot discover the results of partially running the program. The aim of this project is to overcome these problems.

2.5 Di erence from traditional debugging

Programs in traditional imperative programming languages such as C can fail to compile or crash at run-time because of type-errors, for example in C any integer could be used incorrectly as a function pointer possibly resulting in a segmentation fault at run-time. The type-errors detected at compile time are usually obvious enough to repair given the compiler's error message, and the programmer's tactic for locating and repairing dynamic errors is to add trace messages or use a debugger. Current implementations of type checkers for Hindley-Milner type systems actually take an imperative approach to dealing with type errors. The compiler `runs' a program (under the static semantics) until it `crashes', then produces a message saying why it crashed. The problem with this is that the static semantics do not allow us to communicate any other information, such as trace messages. While the idea of allowing programmers to add some sort of trace message to type inference is intriguing, I do not think it would form a practical debugging technique. One may suggest that type errors may be debugged by allowing the program to run, and providing information about how it crashes to the programmer (thus allowing them to apply the techniques used by imperative programmers), this idea is however awed. Firstly, although a program may not be typeable, it may not crash either. For example the program let f = I:(I 3; I true) in f (i:i) end cannot be typed in the Hindley-Milner type system, but will execute without crashing. Even if a program could crash with a type error, it may be extremely dicult to set up a situation in which this happens. The second problem with using execution to debug static errors is that the dynamic semantics of the programming language frequently make little or no reference to types. An automated system for debugging statically detected errors must, therefore, be based on the static semantics.

2.6 Diculty in characterising errors

Consider the problem of explaining grammatical errors in English (ignoring the lack of a formal semantics and accepting convention as `correct'). The sentence \The type system are brilliant!" clearly contains a grammatical error. The subject is singular, but the verb is plural. Without knowing what meaning the writer intended, we cannot say anything more about the error or how to correct it. By analogy, however, a type checker would say that the verb should be singular because the subject is singular. Type checkers currently accept the rst information derived as correct and announce later con icting information as wrong. We can only say more about the sentence and how to correct it once we know whether the subject is intended to be singular or plural. We can only describe the error fully if we know exactly what was meant and what was actually said. If this information is not available, the next best option is to suggest possible corrections (as, for example, a spell checker does). Current compilers make the mistake of describing parts of programs as incorrect when in fact it is the interaction of the parts which makes the whole program incorrect. This project will attempt to come up with a proper characterisation of errors, i.e. what the correct version could be. 7

3 Review of background and related work This project will draw on a range of previous work. The signi cant material includes background on Hindley-Milner type systems, and other authors investigations into debugging type errors. This section reviews the relevant earlier work.

3.1 Hindley-Milner type system

A discussion of type systems in general can be found in [Car97]. The semantics of the Hindley-Milner type-system (and the inference algorithm W ) are introduced in Milner's paper [Mil78]. The operational presentation of the semantics is given in [DM82]. Damas's thesis, [Dam85], proves soundness and completeness of W . The proofs in my work on symmetric type inference, [McA98b], are based on the proofs in Damas's thesis. The thesis also describes variants of Hindley-Milner inference for imperative features of languages. Other variants of Hindley-Milner type systems include the class system of Haskell in [HHPW94]. Algorithms for Hindley-Milner type-inference are based on uni cation, for an overview of uses of and algorithms for uni cation see [Kni89].

3.2 ML family of languages

Milner and Damas's papers use a mini-ML language (a -with-let calculus) as an example programming language. This project will follow this technique, and also consider the extensions seen in Standard ML ([MTH97], [MTH90], [MT91]). Simple examples of how to implement algorithm W for a mini-ML language are given by Tofte in [Tof89] and Cardelli in [Car87]. Clear source code for an implementation of W for Standard ML can be found in the ML-Kit [BRTT93]. It is not certain at the moment whether the ML-Kit will be used as a basis for implementation in this project.

3.3 Other inference algorithms

A number of alternative type inference algorithms have been proposed some of which are designed from the pragmatic perspective of helping programmers debug incorrect programs. Lee and Yi [LY98] describe a top-down algorithm which rejects sooner than the standard algorithm (the algorithm has been used earlier than this in Leroy's Caml compiler, though this is the rst proof of its correctness). The authors suggest that this algorithm aids debugging as it su ers from less of a left-to-right bias than W | some type errors which W detects on the right of an expression are noticed by Lee and Yi's algorithm on the left. This algorithm is, however, not suited to the objectives of this project as the aim here is to avoid rejecting programs, instead as much information about inconsistencies throughout the entire program should be gathered. In Lee and Yi's algorithm whether or not a subexpression will be accepted is dependant on its context. Again this is against the philosophy of this project as expressions should be regarded as they stand alone before their relationships are studied, so that undetected mistakes in the context do not cause the appearance of internal errors in the subexpression. Another notable paper is by Bernstein and Stark [BS95] which introduces typings for open expressions. The paper is signi cant in two ways: it de nes the inference in an alternative set of operational semantics, and the inference is of open expressions without an environment. The general technique of the paper is to rst de ne an operational semantics for inference with assumption environments mapping free identi ers to the types of all their uses in an expression. First an equivalence between the conventional semantics with type environments and the new semantics are given. An inference algorithm is then given for the new semantics (given an expression it produces a type and an assumption environment, or it rejects). As an example of this form of inference, consider part of the earlier example: (I 3; I true). Bernstein-Stark inference derives an assumption environment, fI : int ! ; I : real ! g, for this expression. There is no type which can satisfy the requirements for I (a type scheme 8 : is 8

required), so I cannot be a function argument and I:(I 3; I true) is untypeable. The advantage of the assumption environment is that it allows us to see all the types I must be able to take. There is also no left-to-right bias as the di erent instances of I are not compared until the binding expression is considered. The idea of assumption environments ts extremely well with this project's notion of nding all the con icts in types | a con ict is characterised by all the necessary instantiations of some type. From the point of view of this project, however, Bernstein and Stark's method is de cient in two major aspects. While the aim of the paper is to help \Debugging type errors", the debugging paradigm suggested is extremely primitive, the information in the assumption environments is used to only inform the programmer and not to mechanically aid program repair. The second problem is that the type checker will reject programs with type errors rather than collecting the con ict information. This project requires a Bernstein and Stark style system but with multiple types for bound variables rather than free ones. Bernstein and Stark's other work includes an operational semantics for debugging the dynamic execution of programs in [BS94].

3.4 Explanation of types and type errors

A number of papers have described systems for explaining why terms have particular types or how a type error arose. My earlier work, [McA97a], gathered the types of all the identi ers in a program into a hierarchical data structure, a BigType. This data structure could be used by a programmer to see why a program has a particular type. Normally the types of subexpressions are not given and the programmer has to do all the work manually. The major problem with BigTypes is that they contain a large amount of information and it is dicult to nd the points of particular interest. It has already been suggested in this proposal that some intrinsic means for restricting the amount of information given to a programmer is required for an interactive type debugger to overcome this problem. An early study of failure of uni cation was given by Johnson and Walz in [JW86]. They propose a heuristic for deciding the most likely intended input when uni cation fails, based on

ow analysis of graphs. Given a set of terms (which are types in the case of type inference) which cannot be uni ed, a set of similar terms which can be uni ed can be created by analysing the graphs which the terms represent. My work (described in Section 5.2 and [McA98a]) treats terms as graphs and it is likely that this heuristic will be of use for generating corrected programs. Mitchell Wand observed that the location at which errors are detected is often a great distance from the source of the problem in [Wan86]. He presents a type checking algorithm which attempts to nd the point at which the mistake was actually made. While Johnson and Walz's work is not speci c to type inference, there has been some research into explaining how a type was arrived at. Soosaipillai [Soo90] implemented an \explanation based type checker". After having a program type checked, the programmer can ask \Why?" and be given an explanation of why the program has a particular type. The explanations are quite primitive, simply describing the types of sub-terms, and explanations can only be given for correctly typed terms (or correctly typed sub-terms of programs). While this system would help locate the error in programs, no assistance is given by the type checker to say which sub-terms are more likely to be correct and it is unclear how manageable the interactive user interface would be when dealing with large programs. Turner, in [Tur90], concentrated on explaining errors rather than explaining how types were inferred. His implementation slightly modi es algorithm W and bases error messages on `expected' and `actual' types. The intention is primarily to explain to the programmer how the error was found in more detail than current implementations. From the point of view of this project, the main de ciency in Turner's work is that there is no aid to the debugging process, only a di erent form of static error message. Like Turner, Beaven and Stansifer looked at explaining errors. [BS93] describes a system for generating textual explanations of the types inferred before an error was detected. It is suggested 9

that in the future it may be possible to create a system which \both explains deductions and suggests where the error may be found," that is part of the aim of this project. Duggan and Bent, [DB96], explain type inference by analysing the changes made to graphs (representing substitutions) during uni cation. The technique used here is to modify uni cation to record all the instantiations of type variables made. It is claimed that the approach mimics how people understand type inference. As the approach is based on uni cation, it is suggested that it would be useful in debugging logic programs as well. Emphasis is on explaining type inference, little consideration is given to explaining type errors or to a mechanical debugging process. In his draft proposal, Mikael Rittri [Rit93] has suggested an interactive type-debugger similar to that proposed here. The main suggestion is an interactive type explanation facility which can be tied to any inference algorithm. This would allow programmers to ask how a particular type constructor arose in the type of any subexpression of the program. None of this work has been implemented and it is unclear how interactive program repair would be achieved.

3.5 User interfaces

Another issue for this project is the user interface. It is likely that some sort of diagram or syntax based editor will be required for describing syntax transformations and highlighting areas of code. Several groups have studied interactive programming environments for ML. Whittle describes one for teaching students to program in SML in [WBL97]. The system provides a syntax editor, students learn to program by loading an existing program and transforming it to the required program. After every program transformation, the new version is type checked, this largely removes the need for complex error analysis as the most likely location for the mistake is the modi cation just made. The problem with this for general programming, however, is that it does not take into account that programmers often work from scratch and it is not until the program is almost nished that it is suitable for type checking. Rideau and Thery describe a syntax editor for general programming (not a pedagogic tool) in ML in [RT97]. Types play an important part in the programming environment. The environment can explain type errors, by highlighting the parts of the program which con ict. There is also an explanation facility based on a system for producing natural language explanations from proofs. The explanations look much like those in other papers mentioned here. While the environment expresses type errors as a con ict between pieces of code (and so avoids a left-to-right bias), it does not have any program repair facility.

4 Proposal The notion of an interactive type-debugger is motivated by both the practical concern of helping programmers and encouraging the use of higher-order typed programming languages, and by curiosity about how to represent errors (failures to derive typings) and manipulate these. We have already seen that previous research leaves the ultimate goal in this area untouched.

4.1 Principles

Let us start by summarising the observations about related work and seeing the principles on which this project will be based.

Observation 1 Previous research has concentrated on explanation of type inference, i.e. answering questions like \How was this type derived?". The main question in a programmers mind, however, is \Why was no type derived?". While type explanation is part of error explanation, it is not sucient in itself.

Principle 1 (Explain con icts ) It is not sucient to explain where a type came from in order to explain an error. Emphasis should be on explaining why inference failed.

10

Observation 2 We have seen that previous research has concentrated on explaining the source of

a type or of the failure to unify. Given an untypeable program, type-checkers based on this research (or using conventional ad hoc techniques) will fail type checking at some point and attempt to explain to the programmer why type checking failed at this point. The programmer is left to repair the program by himself.

Principle 2 (Repair) This project intends to create a mechanical aid for programming repair.

The objective is (in part) to establish the minimal changes which can be made to a program in order to repair it.

Observation 3 Most existing programming environments attempt to describe the part of the program which causes an error. This notion is awed as errors arise because of the relations between parts of the program within the whole.

Principle 3 (Holism) The whole program must be considered, and type errors should be described in terms of all parts of a program contributing to them.

Observation 4 The programmer should not be overwhelmed by information. A problem with my earlier work [McA97a] was that the amount of information produced is too much for a programmer to digest. Similar problems can be seen in other papers, for example Beaven and Stansifer (Figure 5 of [BS93]) show 47 lines of explanation for the type of a one line function de nition. The type checker deals with a `compilation unit', in Standard ML this is typically a functor de nition which may be several pages long. Even if the volume of information presented to the programmer at some point is equal to the volume fed into the type checker (i.e. one compilation unit) this will be too much to digest. Principle 4 (Conciseness) The type checker must have some intrinsic way of producing concise explanations of type con icts. It must be easy to give only relevant information without the need to use ad hoc techniques to elide parts of explanations. Observation 5 It is not possible to correct a program without understanding what it is intended to do. Only the programmer can contribute this understanding, so the compiler cannot repair programs alone. Principle 5 (Interactivity) The programmer and type checker should work together not only to

nd the source of the problem, but also to repair the program. Interactivity will go some way to solving the problem of overwhelming the user with too much information as it should allow the user to repeatedly request small pieces of information. The main aim of interactivity however is to allow the repair of the program, the user can suggest which parts of a program are known to be correct, which parts can be changed and experiment with di erent variations of the program. Ideally the compiler should be able to suggest modi cations which can be made to the program | these should be the minimal changes which can be made to remove some or all con icts.

Observation 6 Hindley-Milner type systems form a basis for type systems such as that of Haskell,

[HHPW94], and extensions suggested by various authors (such as [Ler93]). It is common practice when describing features of type systems or new inference algorithms, to give these for a miniML language and describe how they could be extended to cover the features present in actual programming languages.

Principle 6 (Portability) This project will concentrate on a mini-ML language and the extensions present in Standard ML. The results will, however, be relevant to other programming languages with more complex type systems because of their basis in Hindley Milner type systems and uni cation based algorithms. Results may also be relevant to logic programming (as Duggan and Bent suggest of their work [DB96]).

11

Observation 7 The problem of type checking correctly typed programs is well understood, this

project is concerned with incorrectly typed programs. Input to software produced as part of this project will be restricted to programs known, or thought, to contain a problem.

Principle 7 (Performance) Algorithms produced in this project need not be as ecient as current technology. This is because they will only be used when a program is thought to contain a problem and not for type checking normally.

4.2 Proposal | an interactive type debugger

The result of this project will be a design (and prototype implementation) for software which is likely to work as follows: Given a program thought to contain problems: Inference stage: Generate information on types and type errors in the program Analysis and Debugging stage: Repeatedly Explain con icts to the programmer Explain the types of any terms the programmer asks about Suggest changes to the programmer Make changes suggested (or accepted) by the programmer, and update the information on con icts accordingly A number of speci c objectives must be met in order to produce this product.

Objective 1 (Data structure) A single data structure capable of representing both valid typings and attempts to type untypeable programs is required. The relationship between typings and the data structure must be de ned, with a particular emphasis on the distinction between data structures which represent valid typings and those which do not. From the structure, it should be possible to see what types in the program con ict and how they arose during type inference and to generate concise type and type error descriptions. Objective 2 (Algorithm) An algorithm for generating data (in the above structure) from pro-

grams is required. Soundness and completeness with respect to the type system must be shown. In this case, soundness will mean that if there is no typing for a term then the data structure produced does not represent a typing. The algorithm must work for both correctly and incorrectly typed programs, but need not be as ecient as algorithms currently in use. This is because it is expected that the software will only be used when there is thought to be a problem with the code.

Objective 3 (Explanations) It must be possible to generate concise descriptions of con icts preventing type inference.

Objective 4 (Permutation) Many programming errors are caused by the programmer either

missing out simple values (such as the empty list) or putting parameters in the wrong order. It is usually clear from the types in the program, how to restructure it in order to repair the problem. The interactive type debugging should be capable of nding the correct permutation. Some sort of arbitrary distinction must be made between a reasonable permutation (such as reversing the elements of a tuple) and an unreasonable one (such as reversing the entire program). The de nition of the debugger should be exible enough to experiment with di erent heuristics.

To see the potential of permutations for debugging, consider the Standard ML program val sumOfList = List.foldl (op +) [5, 10, 112, 42] ;

12

the programmer intends to sum the four values in the list by folding the addition function across the list. The mistake is in forgetting the initial value 0. An interactive type debugger should be able to establish that the main function List.foldl requires 3 arguments: a function, a list and another value. It then tries to t the existing arguments and nds that the missing value should be of type int. Heuristics could be used to generate simple values with appropriate types (in this case the value required is probably 0 or 1).

Objective 5 (Interrogation) The programmer using the debugger should be able to ask ques-

tions, of the form \will this help repair the program?". There are two types of questions, abstract questions do not relate to a particular syntax transformation, they instead deal with concepts like whether a value is - or let-bound or whether it is ever applied to a value of a particular type. The programmer should also be able to ask concrete questions in which the modi ed syntax is explicitly given.

5 Progress so far Work so far on this project has concentrated on inference rather than analysis and debugging. Initially time was spent investigating the state of the art: the classical results by Milner and Damas ([DM82], [Mil78], [Dam85]); more novel forms of type inference ([LY98], [BS95]); and work related to mine (in explanation ([DB96], [BS93]), debugging ([Rit93]) and programming environments ([RT97], [WBL97])). In addition to studying related material, I have produced two technical reports and am working on a third paper which I hope to submit to a conference.

5.1 Symmetric type inference

One problem with Damas's algorithm W is that the point at which type inference fails is often not the point at which the programmer has made a mistake. Recall that when type checking the lambda calculus expression I:(I 3; I true), W rejects the expression and claims that there is a type error in I true because the argument of I must be an integer. W 's strategy result in a misleading message. There is no mistake in I true, there is a con ict between the two applications of I . Algorithm W has a left-to-right bias caused by its asymmetry | it regards assumptions generated on the left-hand side of an expression as true while type checking the right-hand side of that expression. To remove this bias, I modi ed W so that it became symmetric. This was achieved by performing the recursive calls on each side of an application independently and then unifying the substitutions which resulted. This algorithm is documented in the technical report [McA98b]. The main results are the algorithm for unifying substitutions, US , (with proofs of its correctness) and the modi ed version of W (with proofs of correctness). I also show how US can be applied to another type checking algorithm (from [LY98]) and suggest other uses for it. This illustrates one general way to remove a problem with type inference (the left-to-right bias). While in itself, it does not go far towards interactive type debugging, this work gave me an opportunity to study type inference algorithms in detail and to consider the ways in which they reject programs. Despite the immediate relevance to interactive type debugging, this was essential work in showing me the direction the rest of the project should take.

5.2 A new data structure

The critical step in most type inference algorithms is uni cation. Accordingly, type inference most commonly fails because of a failure to unify two types. If a type inference algorithm is to survive failure and to store a `reason' for why terms are untypeable, it will be necessary to consider how uni cation fails. The result of uni cation is a substitution mapping type variables to types. Uni cation can fail for two reasons 13

One type variable should be mapped to two distinct types (e.g. int and bool)  A type variable should map to a type containing a type variable in the domain of the substitution (this is called an occurs check error). For example, uni cation cannot produce a substitution containing the term 7! ! . One way, therefore, to stop uni cation failing is to allow substitutions which map one type variable to several types (or to a superposition of types) and to allow cyclic substitutions. This can only be done, however, if there is some easy way of detecting that an illegal substitution has been created | though the inference should not reject programs while type checking, it must still be able to say they are untypeable or it will become unsound. The data structure to be presented in [McA98a] is a more sophisticated version of this extended substitution. Rather than being based on a simple representation of substitutions (see [Tof89] for example implementation), the data structure is based on the implicit representation of substitutions seen in most implementations of type checkers. With implicit substitutions type variables are references which can refer to types. Uni cation updates the references, so an explicit representation does not need to be passed around the type checker. The references form a directed acyclic graph, and one in which every type variable node has only one edge coming from it. The graph data structure introduced in the paper is an explicit representation of this graph which also allows cycles and multiple edges from type variable nodes. Every edge of the graph is labelled by the inference algorithm (or generation algorithm as it is more accurately called) with the subexpression being examined when it was added. Thus it is possible to analyse the graph and see which subexpressions cause con icts and to derive more complex information. To perform type inference, rst use the generation algorithm to create a graph for the program, then examine it to see if it represents a valid typing. This is the `analysis' stage of interactive type debugging. If the graph does not represent a valid typing, the information in it can be used to debug the program. Figure 3 shows the graph generation for a simple -calculus expression, and Figure 4 shows the graph for the example type error in Section 2.2 

Figure 3 Generating a graph for the simple expression i:i. Graph for

i

Graph for

i

i:i

i:i i:i

! i:i

i:i

i

The con ict is indicated by the branch from a single node (let argument to I ) to the two distinct types int and bool. A graph can contain several type constructor nodes for any particular type constructor, each represents a di erent type (so, for example, both the type int ! int and real ! real can occur within the graph). Instead of having a uni cation algorithm, a procedure called `graph closure' is used to merge type constructor nodes whenever two instances of the same constructor are both reachable from some other node. The paper will describe how the graph can be used in general, show the operation used instead of uni cation and give a generation algorithm based on Bernstein and Stark's algorithms [BS95]. Analysis of the graph to discover the cause of type errors will be discussed along with suggestions of alternative generation algorithms.

14

Figure 4 Generating a graph for an expression with a type error I

3

 true

3

true

int

 I3

I

I3

I3

bool

 I true I I true

!

! I3

I true

3

 true

3

true

bool

int

 (I 3; I true)

 I:(I 3; I true)

(I 3;I true)

I:(I 3;I true)

 I

! (I 3;I true)

(I 3;I true)

 I3

I3 I true

I3

I:(I 3;I true)

 I true

 (I 3; I true) I:(I 3;I true)

I true

(I 3;I true)

 ! 

I3

 true

3

true

int



I true

3

I true

(I 3;I true)

I

 I3

I3 I true

I3

 I true I true

 ! 

bool

I3

I true

3

 true

3

true

int

5.3 Implementations

bool

In addition to studying the background information and designing and proving algorithms, the work has been accompanied by constant implementation and experimentation. Programs produced include implementations of Damas's algorithm W , my own symmetric uni cation algorithm and algorithms for dealing with the new data structure. Implementations have been for a miniML language rather than Standard ML, this saves programming time while still allowing all the essential features to be demonstrated and tested. As part of my aim is a type checker which does not fail with an exception when it fails to nd a type, some time was spent investigating ways of handling errors without using exceptions. The result of this is a technical report, [McA97b], describing functional programming techniques for simulating the use of exceptions (and other non-functional language features). Using the techniques described allows programming complex features while remaining in a subset of the programming language which allow relatively easy proof of properties. The techniques used in this paper are largely inspired by Norman Ramsey's report [Ram97]. 15

6 Plan for future work The structure of an interactive type debugger has already been split into two stages, generation of information and analysis and debugging based on this information. Conveniently, my work can be split in the same way.

6.1 Generation

Progress this term has concentrated on generation of information from the input program. There are a number of results still to be proven for the graph representation of typings (and non-typings).

Reading types from graphs I have still to de ne an algorithm for reading types from graph. In particular the relation between leaf nodes and type variables is required.

Uni cation and `graph closure' Once the relationship between graphs and types has been

de ned, the equivalence of my graph closure operation and uni cation of types must be proven. In particular it must be shown that if uni cation is not possible, the graph resulting from closure does not represent a type, and if graphs not representing types are closed together the result does not represent a type (these negative results will be required for soundness).

Reading Bernstein-Stark typings from the graph As well as reading types from graph, it is necessary to read typings.

Generation and Bernstein-Stark inference Given the results on uni cation and reading typ-

ings, it will be possible to show that the graph generation algorithm produces a graph representing a typing if and only if Bernstein-Stark inference can generate that typing. Once these results have been proven, the generation phase of research should be complete. It is expected that this phase of the project can be completed by October 1998 (though, extra unanticipated results may be added at a later date).

6.2 Analysis and debugging

To use the graph for interactive type debugging, a number of results will be required. These will concentrate on the resulting graphs when type inference has failed (unlike the results proven for generation, which concentrate on the graphs when inference succeeds).

Explaining the source of a type Much of the previous research described in Section 3 concentrated on explaining how a type was derived. The labels on edges of graphs mean that it should be easy to list the syntactic constructions which lead to a type. It should be possible to explain, not only the types of identi ers and sub-expressions but also where parts for types come from (because the graph carries so much information). Explaining the source of a con ict Related to explaining how types were derived is the issue

of explaining how ambiguities arise. It will be possible to list the syntactic constructions which lead to any branch or loop in the graph. Because of the resolution of the graphs, it will also be possible to indicate con ict in parts of types, for example where an integer list is used for a real list function.

16

Modifying the graph according to syntax transformations given a graph for an expression it should be possible to modify it to represent similar expressions. This will answer questions like \what would the type be if I was let-bound rather than -bound?" and \What is the type if I is not applied to 3?" Figuring out possible syntax transformations to remove con icts In order to help the programmer, it will be necessary to suggest syntax transformations which can repair the problem. The transformations to be considered involve rearranging expressions, inserting extra values and changing between functions and declarations. This concurs with the notion that the best way to explain a mistake is to demonstrate how it can be removed (cf. spell checkers). Communication with the user The above areas represent the computation of the interactive

type debugger. But we still need to actually communicate with the user. While this is vital for an implementation, my work will concentrate on calculating values rather than exactly how to communicate them to the user.

Implementation It will not be possible to con rm the e ectiveness of any design for an in-

teractive type debugger from theorems alone. Implementing ideas will be important both to the ongoing work and the nal result of the project. This phase of work represents the long term objectives of the project. Work will begin while the `generation' phase is still in progress.

7 Conclusions and summary We have seen that Hindley-Milner based type systems give many bene ts to programmers, but su er from the problem that programmers must debug their mistakes without being able to run the faulty program and on the basis of vague error messages. There are a number of problems associated with error messages produced when type inference fails, notably the left-to-right bias which makes it appear that mistakes have been made towards the end of a program, when in fact they may be near the start. Several authors have suggested ways to describe how types were inferred or how type inference fails, but none have suggested how to mechanically repair programs containing type errors. This proposal is for an interactive type debugger | a system which helps programmers to repair their programs. The interactive type debugger will make extensive use of a data structure which describes the types which terms must have in order for parts of the program to type check correctly. This data structure has been devised and work is progressing on a proof of its relationship with the operational semantics of the type system. After this proof, the remaining work is in formalising the analysis of the data structure and devising schemes for relating it to the necessary syntax transformations to debug the program. I already have an intuition of how this will be achieved. The nal product of this project will be software, an interactive type debugger for Standard ML, and theorems showing the meaning of its output in terms of the operational semantics. From the theoretic results it will be possible to extend the ideas to other programming languages and type systems.

17

References [App92]

Andrew W. Appel. A critique of Standard ML. Journal of Functional Programming, 1992. [BRTT93] Lars Birkedal, Nick Rothwell, Mads Tofte, and David N. Turner. The ML Kit (version 1). Technical Report 93/14, Department of Computer Science, University of Copenhagen, 1993. [BS93] Mike Beaven and Ryan Stansifer. Explaining type errors in polymorphic languages. ACM Letters on programming languages and systems, 2(1):17{30, March 1993. [BS94] Karen L. Bernstein and Eugene W. Stark. Operational semantics of a focusing debugger (full version). Technical report, State University of New York at Stony Brook, Computer Science Department, November 1994. [BS95] Karen L. Bernstein and Eugene W. Stark. Debugging type errors (full version). Technical report, State University of New York at Stony Brook, Computer Science Department, November 1995. [Car87] Luca Cardelli. Basic polymorphic type-checking. Science of Computer Programming, 8(2):147{172, 1987. [Car97] Luca Cardelli. The Handbook of Computer Science and Engineering, Allen B. Tucker Jr. (Ed.), chapter 103 Type Systems, pages 2208{2236. CRC Press, 1997. [Dam85] Luis Manuel Martins Damas. Type Assignment in Programming Languages. PhD thesis, Department of Computer Science, The University of Edinburgh, April 1985. CST{33{85. [DB96] Dominic Duggan and Frederick Bent. Explaining type inference. Science of Computer Programming, (27):37{83, 1996. [DM82] Luis Damas and Robin Milner. Principal type-schemes for functional programs. In Ninth Annual Symposium on Principles of Programming Languages, pages 207{212. Association of Computing Machinery, 1982. [FF98] Matthias Felleisen and Daniel P Friedman. The Little MLer. MIT Press, 1998. [HHPW94] Cordelia Hall, Kevin Hammond, Simon Peyton Jones, and Philip Wadler. Type classes in Haskell. Technical Report FP{94{04, Department of Computing Science, University of Glasgow, January 1994. [Jim95] Trevor Jim. What are principal typings and what are they good for? Technical Report MIT/LCS/TM{532, MIT, August 1995. [JW86] Gregory F. Johnson and Janet A. Walz. A maximum- ow approach to anomaly isolation in uni cation-based incremental type-inference. In ACM Symposium on Principles of Programming Languages, number 13, pages 44{57. ACM, ACM Press, 1986. [Kni89] Kevin Knight. Uni cation: A multidisciplinary survey. ACM Computing Surveys, 21(1):93{124, 1989. [Ler93] Xavier Leroy. Polymorphism by name for references and continuations. In Proceedings of the ACM Symposium no Principles of Programming Languages, number 20, pages 220{231. ACM, ACM Press, 1993. [LY98] Oukseh Lee and Kwangkeun Yi. Proofs about a folklore let-polymorphic type inference algorithm. ACM Transactions on Programming Languages and Systems, 1998. Accepted for publication. 18

[McA97a] Bruce J. McAdam. BigTypes in ML. In International Conference on Functional Programming. Association of Computing Machinery, June 1997. [McA97b] Bruce J. McAdam. That about wraps it up | Using FIX to handle errors without exceptions, and other programming tricks. Technical Report ECS{LFCS{97{ 375, Laboratory for Foundations of Computer Science, The University of Edinburgh, James Clerk Maxwell Building, The Kings Buildings, May eld Road, Edinburgh, UK, November 1997. [McA98a] Bruce J. McAdam. A data-structure for representing type-derivations. In progress, May 1998. [McA98b] Bruce J. McAdam. On the uni cation of substitutions in type-inference. Technical Report ECS{LFCS{98{384, Laboratory for Foundations of Computer Science, The University of Edinburgh, James Clerk Maxwell Building, The Kings Buildings, May eld Road, Edinburgh, UK, March 1998. [Mil78] Robin Milner. A theory of type polymorphism in programming. Journal of Computer and System Sciences, 17(3):348{375, 1978. [MT91] Robin Milner and Mads Tofte. Commentary on Standard ML. MIT Press, 1991. [MTH90] Robin Milner, Mads Tofte, and Robert Harper. The De nition of Standard ML. MIT Press, 1990. [MTH97] Robin Milner, Mads Tofte, and Robert Harper. The De nition of Standard ML (revised). MIT Press, 1997. [Ram97] Norman Ramsey. Eliminating spurious messages. Technical Report CS{97{06, Department of Computer Science, University of Virginia, 1997. [Rit93] Mikael Rittri. Finding the source of type errors interactively. In Proc. El Wintermote. Department of Computer Science, Chalmers University, 1993. [RT97] Laurence Rideau and Laurent Thery. Interactive programming environment for ML. Technical Report 3139, Inria, March 1997. [Soo90] Helen Soosaipillai. An explanation based polymorphic type-checker for Standard ML. Master's thesis, Department of Computer Science, Heriot-Watt University, 1990. [Tof89] Mads Tofte. Four lectures on Standard ML. Technical Report ECS{LFCS{89{73, LFCS, March 1989. [Tur90] David Turner. Enhanced error handling for ML. CS4 Project, Department of Computer Science, The University of Edinburgh, May 1990. [Wan86] Mitchell Wand. Finding the source of type errors. In ACM Symposium on Principles of Programming Languages, number 13, pages 38{43. ACM, ACM Press, 1986. [WBL97] Jon Whittle, Alan Bundy, and Helen Lowe. An editor for helping novices to learn Standard ML. In Proceedings of The First International Conference on Declarative Programming Languages in Education. Department of Arti cial Intelligence, The University of Edinburgh; Department of Computer Studies, Napier University, 1997.

19

Suggest Documents