Applicative Programming and Speci cation - Semantic Scholar

2 downloads 0 Views 646KB Size Report
it for proofs, are Avra Cohn, Jacek Leszczylowski, David Schmidt, Larry Paulson and. Brian Monahan. ...... 48 Andrew Appel and David MacQueen but only if thisĀ ...
Applicative Programming and Speci cation Stephen Gilmore October 1993

These are the lecture notes for a seventeen-lecture Master of Science course to be given in the Department of Computer Science at the University of Edinburgh in 1993. The course lecturer is Stephen Gilmore (stg@dcs, Room 1608, JCMB) and the course tutor is Chris Owens (cao@dcs, Room 1404, JCMB). The lectures are on Tuesday at 9:00 in Room 3317 and on Thursday at 12:00 in Room 6206. Starting in the third week, there are two tutorials per week for the course. These are held in Room 6207 on Tuesdays and Thursdays at 14:00. The author wishes to thank the following people for reading these notes and nding errors or suggesting improvements: Anthony Bailey, Jon Hanson and Dan Russell.

Author's address

Laboratory for Foundations of Computer Science Department of Computer Science The James Clerk Maxwell Building The King's Buildings University of Edinburgh Edinburgh EH9 3JZ, UK Email: [email protected] Tel: (+44) 031-650-5189

Contents

1 Introduction

1

2 How ML Evolved

7

1.1 The Standard ML programming language 1.2 The Extended ML wide-spectrum language 1.3 Reading lists

2 3 4

3 Simple applicative programming

12

4 Higher-order programming

18

5 Varieties of recursive functions

24

3.1 3.2 3.3 3.4 3.5 3.6

4.1 4.2 4.3 4.4

5.1 5.2 5.3 5.4

Types, values and functions De ning a function by cases Implementing a simple formula Local de nitions and scope De ning a recursive function Proof by induction

12 13 14 14 15 16

Higher-order functions Self-application Curried functions Function composition

18 20 20 23

Linear recursion Non-linear recursion Mutually recursive functions Non-termination and xed-points

24 25 26 27

i

ii

6 Types and type inference

31

7 Principal type-schemes for functional programs

40

8 Exceptions

47

9 Datatypes and structural induction

50

10 List processing

55

6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9

7.1 7.2 7.3 7.4 7.5 7.6 7.7

8.1 8.2 8.3 8.4

9.1 9.2 9.3 9.4 9.5

10.1 10.2 10.3 10.4 10.5 10.6

The importance of types Type inference Pairs and record types Function types and type abbreviations De ning datatypes Polymorphism Ill-typed functions Overloading Computing types Introduction The language Type Instantiation Semantics Type Inference The type assignment algorithm W Completeness of W Declaring exceptions Raising and handling exceptions An example Exception constructors The tree datatype Induction for trees The list datatype Induction for lists Converting trees to lists

Checking for membership Testing for null lists Searching by key Selecting from a list Sorting lists List functionals

31 32 33 33 34 35 37 39 39 40 41 42 42 43 44 46 47 47 48 49 50 51 51 52 54 55 55 56 56 57 59

iii

11 Lazy applicative programming

61

12 Imperative programming

67

13 Abstract data types

74

14 Modules

77

15 Formal speci cation

82

16 Structured speci cations

88

A Derived forms

93

11.1 11.2 11.3 11.4 11.5

12.1 12.2 12.3 12.4 12.5 12.6 12.7 12.8

Evaluation strategy Lazy operators Making an eager function lazy Lazy datatypes An example: Computing e

References Assignment Sequential composition Iteration Types and imperative programming Arrays Memoisation Input/output

13.1 An abstract data type for sets 14.1 Structures 14.2 Signatures 15.1 15.2 15.3 15.4 15.5 15.6

Principles Aims Requirements for a speci cation language Features of a speci cation language Extended ML Some sample speci cations

16.1 Using axioms in signatures 16.2 Using axioms in structures 16.3 Using axioms in functors

A.1 Expressions and patterns A.2 Bindings and declarations

Bibliography Index

61 62 63 64 65 67 68 68 69 70 72 72 73 74 77 80

82 82 83 83 84 84 88 90 91

95 95

97 105

Chapter 1

Introduction Stephen Gilmore

Applicative, or functional, programs have many virtues. They are concise, secure and elegant. Applicative programs are easier to understand and easier to prove correct than programs written in imperative programming languages. Applicative languages relieve the programmer of many of the diculties which, regrettably, occupy much of the attention of a programmer working in an imperative programming language. These range from the problem of implementing re-usable, generalpurpose routines to the complexities of conserving and managing used and unused memory space to the problem of mastering the details of how data values are represented in the machine's memory and subsequently ensuring that the data values which are stored are inspected and updated in a manner which is consistent with the data representation. Once the diculties of managing the machine's primary memory have been comprehended, there are still the diculties of transferring data from primary memory to secondary memory and the complications of disk access mechanisms and input/output libraries. A functional programmer can transcend these matters. Functional languages have secure polymorphic type systems which simplify the task of writing re-usable, generalpurpose routines. Functional programmers delegate the task of memory management to `garbage collection' routines which need only be written once for the implementation of the language; not every time that another program is written in the language. Functional programming languages hide the machine representation of data, making it impossible to write programs which are sensitive to the byte order of the underlying machine or to introduce other unintended dependencies. Input is simpli ed by the interactive nature of functional languages which allows data to be entered with the minimum of fuss. Output is simpli ed by allowing the user to export the environment bindings as easily as checkpointing a database. However, functional languages have some shortcomings. It may be said that some programming problems appear to be inherently state-based. The uniformity of representation which functional programming languages o er then becomes a handicap. Although a functional implementation of a solution to such a problem would be possible it might 1

2 Stephen Gilmore be an unnatural or obscure encoding of the solution. Another possible worry is that the functional implementation might be inecient when compared with a straightforward imperative implementation of the solution to the problem. This would be a shame; a programming language should not make it dicult for a programmer to write ecient programs. Imperative programming languages also have their strengths. They often provide explicit support for the construction of programs on a large scale by providing simple, robust modules which allow a large programming task to be decomposed into selfcontained units which may be implemented in isolation. If these units are general and well designed they may be included in a library, facilitating their re-use in other programs. Modules also enable programs to be eciently recompiled by avoiding the need to recompile parts of the program which have not changed. Is there then a way to combine the virtues of functional programming with the virtues of imperative programming?

1.1 The Standard ML programming language The Standard ML programming language consists of a core language for small-scale programming and a module system for large-scale programming.

1.1.1 Using the Standard ML core language Standard ML is a higher-order procedural language with a functional subset. For most of these notes the focus will be on using the functional subset of the language so we will spend some time discussing functions, in particular recursive functions, and giving veri cation techniques for these. In the early sections of these notes the examples are all small integer functions. Later, Standard ML's sophisticated type system is presented. The design of the Standard ML programming language enables the type of any value to be computed, or inferred, by the Standard ML system. Thus, the language has a strong, safe type system but does not force the programmer to state the type of every value before use. The type system enables the detection of programming errors before a program is even executed. John Hughes in [Hug89] suggests that one of the advantages of functional programming is the ability to glue programs together in many di erent ways. For this, we require the ability to manipulate functions as data. We will pass them as arguments to other functions and even return them as the results of functions. In order for such a powerful mechanism to be exploited to the fullest, the language must provide a means for functions to be de ned in a general, re-usable way which allows values of di erent types to be passed to the same function. Standard ML provides such a feature without compromising the security of the type system of the language. The mechanism by which applicative programs are evaluated is discussed. This leads

Introduction 3

into the consideration of alternative evaluation strategies one of which is so-called lazy evaluation. This strategy has a profound e ect on the style of programming which must be deployed. The other elements of the Standard ML core language which are discussed are the mechanism used to signal that an exceptional case has been detected during processing and no consistent answer can be returned and the imperative features such as references and interactive input and output.

1.1.2 Using the Standard ML modules language The core language o ers a comfortable and secure environment for the development of small programs. However, a large Standard ML program would contain many de nitions (of values, functions or types) and we begin to require a method of packaging together related de nitions; for example, a type and a useful collection of functions which process elements of this type. Standard ML has modules called structures. Structures facilitate the division of a large program into a number of smaller, independent units with wellde ned, explicit connections. A large programming task may then be broken up so that several members of a programming team may independently produce structures which are assembled to form a single program. Another reason for tidying collections of de nitions into a structure is that we can pass structures as arguments and return them as results. In Standard ML the entity which plays the role of a function mapping structures to structures is called a functor. Finally, we may wish to take the opportunity when collecting de nitions into a structure to hide some of the declarations which played a minor role in helping the implementor to construct the major de nitions of the structure. Thus, we require a interface for our structure. The Standard ML term for such an interface is a signature.

1.2 The Extended ML wide-spectrum language These notes will also address the early part of the program development cycle: the commissioning of a software system and the gradual, disciplined construction which takes place before the nal realisation as a program. The initial commissioning of a software system represents a contract between the software developer and the commissioner. Such a contract will ultimately give rise to a signi cant amount of taxing software development and the contract should describe with as much precision as possible the nal behaviour of the desired software system. Even when a contract has been signed, how can the software developer ever show that the contracted software development was complete? This question is much more dicult than one might at rst suspect. Even extremely short computer programs have so many possible input values that checking each of them would take an impossibly long time. How then is the correctness of a computer program ever to be established?

4 Stephen Gilmore

Standard ML

State Assignment

Extended ML Expressions Values and de nitions Functions and types Modules Signatures and structures

Speci cations Axioms

Figure 1.1 The Standard ML language and the Extended ML language The Extended ML program development framework provides a speci cation language which is used to express the contract between the software developer and the client. A formal development method provides rules which allow the software developer to decide whether or not a proposed development step is correct. The Extended ML language will be used here to specify problems whose solution is to be implemented in Standard ML. The Extended ML language can be used to express preliminary, non-executable descriptions of systems which are not yet built. Extended ML can also be used to describe the nal, eciently executable program using that part of the language which agrees precisely with Standard ML. The language can also be used to express all the intermediate designs and fabrications between the rst speci cation and the nal implementation. This wide range of uses for Extended ML leads it to be termed a wide spectrum language. Most frequently, use of Extended ML is framed in relation to the module sublanguage of Standard ML and decomposition of large programs into units takes place along the module boundaries. Figure 1.1 depicts the two languages and the modules sublanguage and shows their interrelation.

1.3 Reading lists There are many excellent journal articles and textbooks on the topics of functional programming and the formal speci cation of computer programs. A bibliography is given at the end of these notes.

Introduction 5

1.3.1 Applicative programming reading list The recommended textbook to accompany these notes is Larry Paulson's \ML for the Working Programmer". Of particular interest are the chapters devoted to the module system of Standard ML and the material on proving the correctness of a function with respect to a description of its behaviour. The book contains many exercises but specimen answers are not provided. Equally good are Stefan Sokolowski's \Applicative High-Order Programming" and Chris Reade's \Elements of Functional Programming". These also contain exercises but they do provide specimen solutions. Robert Harper's \Introduction to Standard ML" is published as a University of Edinburgh technical report and gives a concise description of the Standard ML core language and the modules system. Material on the veri cation of functions is not included. There are many exercises with specimen answers. Another Edinburgh technical report which concentrates primarily on modules is Mads Tofte's \Four lectures on Standard ML". This could not be regarded as an introductory text. Ryan Stansifer's \An ML Primer" is a short book which does not treat the module system of Standard ML. Some exercises are included but specimen answers are not provided. Textbooks suitable for someone with no programming experience are  Ake Wikstrom's \Functional Programming Using Standard ML" and \Programming with Standard ML" by Colin Myers, Chris Clack and Ellen Poon. Both of these contain exercises and specimen solutions. Wikstrom's book is slightly dated and some of the code fragments which appear in the book will no longer be accepted by Standard ML because of revisions to the language. A textbook which gives another perspective on using Standard ML is Rachel Harrison's \Abstract Data Types in Standard ML". The de nitive de nition of the Standard ML programming language is given in \The De nition of Standard ML" by Robin Milner, Mads Tofte and Robert Harper. This is not a reference manual for the language: rather it is a formal de nition giving rules which de ne the simple constructs of the language in terms of familiar mathematical objects and de ne the complex constructs of the language in terms of the simpler ones. The de nition is complemented by Milner and Tofte's \Commentary on Standard ML" which relates the de nition to the user's view of the language.

1.3.2 Formal development reading list There are several journal articles and Edinburgh technical reports which describe the Extended ML formal development framework. Most have been written by Don Sannella and Andrzej Tarlecki. An introductory report on the speci cation and veri cation of Standard ML programs is Don Sannella's 1986 report, \Formal speci cation of ML programs". This may be followed by his 1989 report, \Formal program development

6 Stephen Gilmore in Extended ML for the working programmer". Having read these reports, the reader will be well placed to appreciate two reports by Sannella and Tarlecki, \Toward formal development of ML programs: foundations and methodology" from 1989 and \Extended ML: past, present and future" from 1991.

Chapter 2

How ML Evolved Robin Milner

ML is one among many functional programming languages. But not many were designed, as ML was, for a more-or-less speci c task. The point of this note is to summarise the process by which we were guided to ML, as it now is, by the demands of the task. We (at least I) feel that to nd a good metalanguage for machine assisted proof, which was the task, we could hardly have gone in an essentially di erent direction; the task seemed to determine the language|and even made it turn out to be a general purpose language. The context in 1974 (when the Edinburgh LCF project began) was our experience with the Stanford LCF proof assistant, developed there in 1971{72 by Richard Weyrauch, Malcolm Newey and myself. The development of ML as a metalanguage for interactive proof was the work of several people; in chronological order they were (besides me) Malcolm Newey, Lockwood Morris, Michael Gordon, and Christopher Wadsworth. Other people, who joined the LCF project after the language was more-or-less xed, and used it for proofs, are Avra Cohn, Jacek Leszczylowski, David Schmidt, Larry Paulson and Brian Monahan. In the following summary of the development process, the actual logic involved (PPLAMBDA) is rather irrelevant, and it now seems that the same principles apply to any formal deductive system. Consider the activity of goal-directed proof. If we have a goal A, a logical formula to be proved, then it is sound to replace it by subgoals B1 ; : : :; Bn, provided we know how to construct, from achievements of all the Bi , an achievement of A. We may call any subgoaling method a tactic; it is a valid tactic, then, if it also provides|for each set f Bi g of subgoals produced from a goal A|a way of extending achievements of the Bi into an achievement of A, and this \way" we call a validation. Of course, only valid tactics are useful! Now, given a xed repertoire of tactics, a natural proof assistant would be one which maintains a goal tree, with the user always working at a leaf. The Stanford LCF system was like this. After some subgoaling, the tree might look thus: This chapter was written by Robin Milner in November 1982 as an article for Polymorphism. It is reproduced with permission.

7

8 Robin Milner

D1

C1

C2

D2

C3

B2

B1

A Here, we suppose that the ringed subgoals have (somehow) been achieved; the little black boxes sitting at the non-leaf nodes are the validations waiting to be applied to the (achieved) sons to achieve their father. So, after achieving D2 the proof assistant would collapse the tree to:

B1

B2

A Clearly, under this rigid discipline of tactical tree walking, the only way of proving the main goal is to return to the root, and the only doubt that the theorem has been correctly proved is in the correctness of the built-in programs for the basic repertoire of tactics and for treewalking. But in a more exible system, a user should at least be allowed to compose tactics into more powerful ones. For example, he should be allowed to compose the three tactics

How ML Evolved 9

which were applied to goals A, B1 and C3 to produce the tree of our rst diagram; this composite tactic applied to goal A would yield a atter tree

C1

C2

D1

D2

B2

A Note that the validation produced by the composite tactic is a particular combination of those produced by the separate tactics. (Of course, as in our rst diagrams, some of the subgoals may then be somehow achieved; we have omitted the rings here). Well, to program this composition the user must be allowed to hold in his hand as objects (or: be allowed to bind as values to metavariables) both goals and validations; moreover, to put them together properly already suggests the need for structureprocessing power of the kind found in LISP and other functional programming languages. What kind of object is a validation? It is a \way of extending the achievements of subgoals into an achievement of the goal". But an achievement is (in our case) a proof, or the theorem which is the last step of a proof; so the validation for a tactic which produces subgoals f Bi g from goal A could perhaps be represented by the theorem ` B1      Bn  A, and to apply the validation is perhaps just to apply Modus Ponens repeatedly to this theorem and the achievements B1 ; : : :; Bn to produce A. So perhaps validations are just theorems, proved somehow at the time that the tactic is applied? To see that this is wrong, consider the tactic which converts a goal formula 8x: B (x) into a single subgoal formula, B (x) (this is the common method of proving that something holds for all x by proving that it holds for arbitrary x). According to the above suggestions then, the validation should be the theorem ` B (x)  8x: B (x), and there is no such theorem ! In fact the validation should not be a theorem, but a function from theorems to theorems, i.e. a (primitive or derived) inference rule; in our case, it is the rule of generalisation GEN

` B(x) ` 8x: B(x)

(This is why we called the tactic GENTAC). We immediately see that a tactic is a function producing function; when applied to a goal it produces, as well as a subgoal list, a function which is the validation. So our metalanguage must express second order functions. Further:

10 Robin Milner 1. when the validation function depends (as it may in general) upon the properties of the goal attacked, these properties will be bound into the validation at the time of tactic application, and the natural way of doing this critically requires the static binding convention, now normally accepted in preference to the dynamic binding of LISP. (In Landin's terms, the validation is a closure, i.e. an expression paired with an environment). 2. Since the user is to be allowed to compose tactics (second order functions), his compositions will be third order functions; clearly a metalanguage which expresses functions of arbitrarily high order is the only natural choice. 3. Since the user is to be allowed to hold validations (and, in general, any primitive or derived inference rule) in his hand, it is critically important that he is only allowed to apply them to theorems, not to other objects (such as formulae) which look so like theorems that in a moment of misguided inspiration he may mistake one for the other! So the metalanguage must be rigorously typed, in a way which at least distinguishes theorems from other things. 4. For a given tactic T , there are usually goals for which it makes no sense to apply T (Example: it makes no sense to apply GENTAC to a goal formula which is not universally quanti ed). It would be vastly inconvenient to test a goal by some separate predicate before applying a tactic, so the tactic itself must assume the task of detecting inapplicability, and respond in some suitable way. But in a typed language, every result of applying a tactic must be a goal list paired with a validation, and it is irksome to have to construct a correctly typed but spurious `result' when the application makes no sense; so the only alternative in this case is to have no result at all. Hence it is natural to have an escape or failure mechanism, under which senseless applications may avoid producing a result, but instead be detected for alternative action. In LISP this response could be to return the result NIL, for example. 5. In a conversational typed functional language, it soon appeared intolerable to have to declare|for example|a new maplist function for mapping a function over a list, every time a new type of list is to be treated. Even if the maplist function could possess what Strachey called \parametric polymorphism" in an early paper, it also appeared intolerable to have to supply an appropriate type explicitly as a parameter, for each use of this function. (Perhaps this latter is rendered more acceptable if types can be suitably abbreviated by names, but note that even simple list constructors and destructors|CONS, CAR, CDR or whatever|would need explicit type parameters) so a polymorphic type discipline, with rigorous type checking, emerges as the most natural solution. Note that it emerges as such on purely practical considerations; it is a gift from the Gods that this discipline happens to have a simple semantic theory, and that the type checking has an elegant implementation based upon uni cation (Robinson). We only discovered afterwards that the proper lineage for this type checking is from Curry's functionality, through Roger Hindley's principal type schemes. This discussion was somewhat simpli ed w.r.t. LCF (for example, LCF goals are not

How ML Evolved 11

simply logical formulae to be proved). But it is, in essence, the process by which we arrived almost unavoidably at the metalanguage ML as it now exists. It shows why ML is a higher order functional programming language with rigorous polymorphic type discipline and an escape mechanism (and, of course, static binding). Perhaps the development of ML has been made to seem too clean and trouble-free, from the above discussion. There still remain, however, some big problems and question marks. It is important to mention some of them; in doing so, we also place ML in context with other languages|both applicative and imperative. 1. It is natural to recover, within ML, the simple treewalking proof methodology with which we started. But this global tree is a changing structure; as such, it cannot really be implemented without strain in a purely applicative language. (Or so it appears to me; this is a challenge to applicative language devotees who wish to rule out state change completely). So ML has an assignment statement! But this does not sit so easily beside the polymorphic type discipline. Recent work by Luis Damas (forthcoming Ph.D.) shows that the somewhat over-rigid treatment of the types of assignable variable in ML (viz. that they may not be polymorphic) can be relaxed; but the purity and obviousness of the discipline is inevitably lost. 2. Even without assignment, the type discipline forced us to adopt a restrained form of escape mechanism in ML; it is not allowed to escape (or fail) with a value of arbitrary type, but only with a token. This problem has to do with the dynamic nature of the escape-trapping mechanism; the trap for each escape is not textually determined, and it appeared to us most useful that it should not be so. A clean solution to this problem|probably easier than that for assignable variables|would be an important development. 3. ML does not adopt the clausal form of function de nition, which is found so convenient by users of HOPE and PROLOG. How can we get a semantically rigorous form of this clausal de nition, in which the constructor-patterns in formal parameters can involve not only primitive constructors, but also the constructors of user-de ned abstract types? The problem is to know that these constructors are constructors, in the sense of being uniquely decomposable (or else to admit nondeterminism into the language). If I understand Rod Burstall right, this is partly why HOPE is called HOPE; the answer may eventually become CLEAR. 4. ML does not use lazy evaluation; it calls by value. This was decided for no other reason than our inability to see the consequences of lazy evaluation for debugging (remember that we wanted a language which we could use rather than research into), and the interaction with the assignment statement, which we kept in the language for reasons already mentioned. In fact, this sharpens the challenge mentioned in (1) above; is there a good language in which lazy evaluation and controllable state-change sit well side-by-side? John Reynolds has for a long time worried about such possible incompatabilities between applicative and imperative languages (cf. his \Syntactic Control of Interference", which exposes the problem with great honesty).

Chapter 3

Simple applicative programming

Stephen Gilmore

Standard ML is an interactive language. Expressions are entered, compiled and then evaluated. The result of evaluation is displayed and the next expression may then be entered. This interactive style of working combines well with Standard ML's type inference mechanism to empower the programmer to work in a exible, experimental way, moving freely from de ning new functions to trying the function on some test data and then either modifying the function or moving on to de ne another. The fact that types are assigned by the compiler also has the favourable consequence that Standard ML functions are usually shorter than comparable routines implemented in languages in which the types of variables must be supplied when the variable is declared. We will begin to investigate applicative programming by inspecting values, expressions and functions. The functions de ned are short and we will not spend much time describing the task to be computed since the meaning of each function will often be obvious.

3.1 Types, values and functions A pre-de ned type in Standard ML is the type of truth values, called bool. This has exactly two values, true and false. Another simple type is string: there is no type \char" for single characters. Strings are enclosed in double quotes (as in \Hello") and strings can be joined by the \ " operator. As expected, \Hello" \ there" evaluates to \Hello there". The numeric types in Standard ML are the integers (the type int) and the real numbers (the type real). The integers are represented in decimal notation as usual with the oddity that \~" is used as unary minus. Reals must have either a fraction part (as in 4000.0) or an exponent part (as in 4E3) or both. Operators such as +, {, *, div and mod are provided for the integers: +, {, * and / are provided for the reals. Relational operators =, , = are provided for both types. Standard ML provides two standard functions, oor and real, to convert between 12

Simple applicative programming 13

integers and reals. If n is an integer and r a real then we always have:

oor (real (n)) real ( oor (r))



= x+1. As might be expected, the function application (fn x => x+1) 4 evaluates to 5. Function application associates to the left in Standard ML so an expression which uses the successor function twice must use parentheses, as in (fn x => x+1) ((fn x => x+1) 4). Since unary minus is a function the parentheses are necessary in the expression ~ (~ x). Of course, a mechanism is provided for binding names to values and the declaration val succ = fn x => x+1 binds the name succ to the successor function and we may now write succ (succ 4) as an abbreviation for (fn x => x+1) ((fn x => x+1) 4).



Standard ML is case-sensitive. Reserved words of the language must be in lower case and occurrences of a program identi er must use capitalization consistently.

3.2 De ning a function by cases Standard ML provides a mechanism whereby the notation which introduces the function parameter may constrain the type or value of the parameter by requiring the parameter to match a given pattern (so-called \pattern matching"). The following function, day, maps integers to strings. val day

=

fn | | | | | |

0 => \Monday" 1 => \Tuesday" 2 => \Wednesday" 3 => \Thursday" 4 => \Friday" 5 => \Saturday" _ => \Sunday"

The nal case in the list is a catch-all case which maps any value other than those listed above it to \Sunday".



Be careful to use double quotes around strings rather than single quotes. Single quote characters are used for other purposes in Standard ML and you may receive a strange error message if you use them incorrectly.

14 Stephen Gilmore

3.3 Implementing a simple formula Armed with our knowledge about integers and reals and the useful \pattern matching" mechanism of Standard ML we are now able to implement a simple formula.

3.3.1 Zeller's Congruence Reverend Zeller discovered a formula which calculates the day of the week for a given date. If d and m denote day and month and y and c denote year and century with each month decremented by two (January and February becoming November and December of the previous year) then the day of the week can be calculated according to the following formula. (b2:61  m ? 0:2c + d + y + y  4 + c  4 ? 2  c) mod 7 This is simple to encode as an ML function, zc, with a four-tuple as its argument. val zc = fn (d, m,

y, c) => ( oor (2.61 * real (m) { 0.2) + d + y + y div 4 + c div 4 { 2*c) mod 7

Now we may use the pattern matching mechanism of ML to perform the adjustment required for the formula to calculate the days correctly. val zeller

=

fn | |

(d, 1, y) (d, 2, y) (d, m, y)

=> => =>

zc (d, 11, y mod 100 { 1, y div 100 + 1) zc (d, 12, y mod 100 { 1, y div 100 + 1) zc (d, m { 2, y mod 100, y div 100 + 1)

3.4 Local de nitions and scope Although the zeller function correctly calculates days of the week its construction is somewhat untidy since the zc function has the same status as the zeller function although it was intended to be only a sub-component. We require a language construct which will bundle the two functions together, making the inferior one invisible. In Standard ML the local .. in .. end construct is used for this purpose. local val zc = fn (d, m, in

y, c) => ( oor (2.61 * real (m) { 0.2) + d + y + y div 4 + c div 4 { 2*c) mod 7

val zeller

end

=

fn | |

(d, 1, y) (d, 2, y) (d, m, y)

=> => =>

zc (d, 11, y mod 100 { 1, y div 100 + 1) zc (d, 12, y mod 100 { 1, y div 100 + 1) zc (d, m { 2, y mod 100, y div 100 + 1)

Simple applicative programming 15 Exercise 3.4.1 The polynomial in x, ax2 + bx + c, has two solutions given by the following formula. p ?b  b2 ? 4ac 2a Implement this formula using only the Standard ML constructs introduced thus far and the pre-de ned sqrt function. Exercise 3.4.2 (Braghamupta's problem) Write a Standard ML function to nd the smallest positive values for x and y such that x2 ? 92y 2 = 1.

3.5 De ning a recursive function Consider the simple problem of summing the numbers from one to n. If we know the following equation then we can implement the function easily using only our existing knowledge of Standard ML. 1 + 2 +    + n = n(n2+ 1) (3.3) The function is simply fn n => (n * (n+1)) div 2. We will call this function sum. But what if we had not known the above equation? We would require an algorithmic rather than formulaic solution to the problem. We would have been forced to break down the calculation of the sum of the numbers from one to n into a number of smaller calculations and devise some strategy for recombining the answers. We would immediately use the associative property of addition for this purpose. (3.4) 1 + 2 +    + n = (|   ((1) + 2){z+    + n ? 1)} + n Sum of 1 to n ? 1 We now have a trivial case (when n is one) and a method for decomposing larger cases into smaller ones. These are the essential ingredients for a recursive function. ( if n is one; 0 (3.5) sum (n) = 1n + sum0 (n ? 1) otherwise : We must mark our de nition as being recursive in character using the Standard ML keyword rec. val rec sum

=

1 => 1 n => n + sum (n { 1) It may seem somewhat worrying that the sum function is being de ned in terms of itself but there is no trickery here. The equation x = 5x ? 20 de nes the value of x precisely through reference to itself and the de nition of the sum function is as meaningful. (Of course, a recursive equation can be vacuous: consider val rec f = fn x => f x. This has no more content than \Let x = x.") fn |

16 Stephen Gilmore We would assert for positive numbers that sum and sum will always agree but at present this is only a claim. Fortunately, the ability to encode recursive de nitions of functions comes complete with its own proof method for checking such claims. The method of proof is called induction.

3.6 Proof by induction We wish to construct a convincing argument that sum and sum will always agree for positive numbers. The approach most widely in use in programming practice to investigate such a correspondence is to choose a set of numbers to use as test data and compare the results of applying the functions to the numbers in this set. This testing procedure may uncover errors if we have been fortunate enough to choose one of the positive integers (if there are any) for which sum and sum disagree. One of the advantages of testing programs in this way is that the procedure is amenable to automation. After some initial investment of e ort, di erent versions of the program may be executed with the same input and the results compared mechanically. However, in this problem we have very little information about which might be the likely values to uncover di erences. Sadly, this is often the case when attempting to check the tness of a computer program for a given purpose. Rather than stepping through a selection of test cases, we will construct a general argument for the correspondence underlying the supposed equivalence of the two functions. We will show that the equivalence holds for the smallest allowable value (one in this case) and then show that if the equivalence holds for n it must also hold for n + 1. The rst step in proving any result, even one as simple as this, is to state the result clearly. So we will now do that. Proposition 3.6.1 For every positive n, we have that 1 + 2 +    + n = n(n + 1)=2. Proof: Considering n = 1, we have that lhs = 1 = 1(1 + 1)=2 = rhs. Now assume the proposition is true for n = k and consider n = k + 1. lhs = 1 + 2 +    + k + (k + 1) = k(k + 1)=2 + (k + 1) = (k2 + 3k + 2)=2 = (k + 1)(k + 2)=2 = (k + 1)((k + 1) + 1)=2 = rhs It would be very unwise to appeal to the above proof and claim that the functions sum and sum are indistinguishable. In the rst place, this is simply not true since they return di erent answers for negative numbers. It may not even be true for positive integer arguments since physical computers are limited in the amount of memory they contain and in the size of the largest integer they can represent. What we may claim

Simple applicative programming 17

based on the above proof is that when both functions return a result for a positive integer, it will be the same result. More information on using induction to prove properties of functions may be found in [MNV73]. Exercise 3.6.1 Consider the following function, sq.

1 => 1 n => (2 * n { 1) + sq(n { 1) Prove by induction that 1 +    + (2n ? 1) = n2 , for positive n. val rec

sq =

fn |

Chapter 4

Higher-order programming Stephen Gilmore

One of the de ning characteristics of functional programming languages is that they give the programmer the ability to manipulate functions as easily as manipulating any other data item (such as an integer or a real). Functions may be passed as arguments or returned as results. Functions may be composed in order to de ne new functions or modi ed by the application of higher-order functions.

4.1 Higher-order functions In the previous chapter two small functions were de ned which performed very similar tasks. The sum function added the numbers between 1 and n. The sq function added the terms 2i ? 1 for i between 1 and n. We will distill out the common parts of both functions and see that they can be used to simplify the de nitions of a family of functions similar to sum and sq. The common parts of the two functions are:

   

a contiguous range with a lower element and upper element; a function which is applied to each element in turn; an operator to combine the results; and an identity element for the operator.

We will de ne a function which takes a ve-tuple as its argument. Two of the elements in the ve-tuple are themselves functions and it is for this reason that the function is termed higher-order. Functions which take functions as an argument (or, as here, as part of an argument) are called higher-order functions or functionals. The function we will de ne will be called reduce . Here is the task we wish the reduce function to perform.

reduce (g, e, m, n, f)



g(f(m), g(f(m+1), ??? g(f(n), e) ??? )) 18

Higher-order programming 19

The function g may be extremely simple, perhaps addition or multiplication. The function f may also be extremely simple, perhaps the identity function, fn x => x. In order to implement this function, we need to decide when the reduction has nished. This occurs when the value of the lower element exceeds the value of the upper element, m > n. If this condition is met, the result will be the identity element e. If this condition is not met, the function g is applied to f (m) and the value obtained by reducing the remainder of the range. We would like the structure of the implementation to re ect the structure of the analysis of termination given above so we shall implement a sub-function which will assess whether or not the reduction has nished. The scope of this de nition can be restricted to the expression in the body of the function through the use of Standard ML's let .. in .. end construct. This di ers from local .. in .. end because let makes a declaration local to an expression whereas local makes a declaration local to another declaration. val rec reduce = fn (g, e, m, n, f) => let val nished = fn true | false in nished (m > n) end

=> =>

e g (f (m), reduce (g, e, m+1, n, f))

Exercise 4.1.1 Would it be straightforward to rewrite the reduce function using local rather than let? What would be the diculty?

The Standard ML language has the property that if all occurrences in a program of value identi ers de ned by nonrecursive de nitions are replaced by their de nitions then an equivalent program is obtained. We shall apply this expansion to the occurrence of the nished function used in the reduce function as a way of explaining the meaning of the let .. in .. end construct. The program which is produced after this expansion is performed is shown below. val rec reduce (fn |

= fn (g, e, m, n, f) => true => e false => g (f (m), reduce(g, e, m+1, n, f))) (m > n) The two versions of reduce implement the same function but obviously the rst version is much to be preferred; it is much clearer. If the nished function had been used more than once in the body of the reduce function the result after expansion would have been

even less clear. As promised earlier, we shall now rede ne the sum and sq functions in terms of reduce .

n => reduce (fn (x, y) => x+y, 0, 1, n, fn x => x) n => reduce (fn (x, y) => x+y, 0, 1, n, fn x => 2*x{1) Note that the function fn (x, y) => x+y which is passed to the reduce function does val val

sum = sq =

fn fn

nothing more than use the prede ned in x addition operation to form an addition function. Standard ML provides a facility to convert in x operators to the corresponding

20 Stephen Gilmore pre x function using the op keyword. Thus the expressions (op +) and fn (x, y) => x+y denote the same function. We will use the op keyword in the de nition of another function which uses reduce, the factorial function, fac. The factorial of n is simply the product of the numbers from 1 to n.



val

fac = fn n => reduce (op *, 1, 1, n, fn x => x)

If parenthesized, \ (op *)" must be written with a space between the star and the bracket to avoid confusion with the comment delimiter \ *)". Exercise 4.1.2 The semifactorial of a positive integer is 1  3  5      n if n is odd and 2  4  6     n if n is even. Use the reduce function to de ne a semifac function which calculates semifactorials.

4.2 Self-application Now that we have discovered that functions may have functions as arguments we are tempted to ask whether a function could take itself as an argument. Some functions certainly can: the identity function, fn x => x could be applied to itself. The application would be written as (fn x => x) (fn x => x) and would evaluate to fn x => x. However, this mild example does not convey the diculty inherent in the notion of a function which may be applied to itself. Consider the following example due to Joseph Stoy [Sto82]. First de ne a function a as shown below: ( if x = 0; (4.1) a(b; x) = 1x  b(b; x ? 1) otherwise : Now we may de ne the factorial function through reference to the function a. fac (y ) = a(a; y ) (4.2) Notice that neither the function fac nor the function a are de ned through recursion, even indirectly. The ability to pass the function a to itself as a parameter has allowed us to de ne the factorial function by a circuitous route which does not involve any recursion. Self-applying functions are dicult to comprehend and it is perhaps reassuring to know that Standard ML does not allow us to de ne functions such as the function a above.

4.3 Curried functions The other question which arises once we have discovered that functions may take functions as arguments is \Can functions return functions as results?" (These functions are called curried functions after Haskell B. Curry.) Curried functions seem perfectly reasonable tools for the functional programmer to request and we can encode any curried function using just the subset of Standard ML already introduced, e.g. fn x => fn y => x.

Higher-order programming 21

This tempting ability to de ne curried functions might leave us with the diculty of deciding if a new function we wish to write should be expressed in its curried form or take a tuple as an argument. Perhaps we might decide that every function should be written in its fully curried form but this decision has the unfortunate consequence that functions which return tuples as results are sidelined. However, the decision is not really so weighty since we may de ne functions to curry or uncurry a function after the fact. We will de ne these after some simple examples.

4.3.1 A simple curried function A simple example of a function which returns a function as a result is a function which, when given a function f , returns the function which applies f to an argument and then applies f to the result. Here is the Standard ML implementation of the twice function. val twice

= fn f => fn x => f (f (x))

For idempotent functions, twice simply acts as the identity function. The integer successor function fn x => x+1 can be used as an argument to twice. val addtwo

= twice (fn x => x+1)

In fact, twice is one of the functions which may be usefully applied to itself. Call the function (twice twice) \fourtimes". Now we may de ne further functions using fourtimes. An obvious example is the addfour function, simply fourtimes (fn x => x+1).

4.3.2 De ning iteration The functions twice and fourtimes are only special cases of a more general function which applies its function argument a number of times. We will de ne the iter function for iteration. It is a curried function which returns a curried function as its result. The function will have the property that iter 2 is twice and iter 4 is fourtimes. Here is the task we wish the iter function to perform.

iter n f x



f n (x)



f| (f ({z   }f (x)    )) n times

In the simplest case we have f 0 = id, the identity function. When n is positive we have that f n (x) = f (f n?1(x)). We may now implement the iter function in ML. val rec iter = fn 0 | n

=> =>

(fn f => fn x => x) (fn f => fn x => f (iter (n{1) f x))

22 Stephen Gilmore

4.3.3 After-the-fact currying As promised above, we now de ne two higher-order, curried Standard ML functions which, respectively, transform a function into its curried form and transform a curried function into tuple-form. val val

curry = uncurry =

fn f => fn fn f => fn

x => fn y => f (x, y) (x, y) => f x y

If x and y are values and f and g are functions then we always have:

(curry f) x y (uncurry g) (x, y)

 

f (x, y) gxy

(4.3) (4.4)

A natural companion for the curry function is the function eval, de ned below. val eval

= fn (f, x) => f x

The relationship between curry, eval and id, the identity function, can be documented in terms of a picture. Suppose that g is a function which, when given a pair containing a left element of type C and a right element of type A returns a result of type B . Since g takes a pair as an argument then curry (g) is well de ned and, when given an element of type C , returns a function from A to B . (Such functions may be denoted B A ). Of course, given a pair where the left element is a function from B A and the right element is of type A then eval returns an element of type B simply by applying the given function to the given argument. If we have been careful to preserve the same element of type A throughout (using the identity function) then it does not matter whether we: 1. curry g and then apply it, and then use eval; or 2. apply g directly.

BA  A

eval

-B 

6

(curry (g ); id)

C A

g

Higher-order programming 23

4.4 Function composition Let us now investigate a simple and familiar method of building functions: composing two existing functions to obtain another. The function composition f  g denotes the function with the property that (f  g )(x) = f (g (x)). This form of composition is known as composition in functional order. Another form of composition de nes g ; f to be f  g . This is known as composition in diagrammatic order. Standard ML has a semicolon operator which does not behave as described above. In fact, for two functions g and f we have instead that g ; f  f. Notice that function composition is associative, that is: f  (g  h) = (f  g)  h: (4.5) Obviously the identity function is both a left and a right identity for composition. id  f = f  id = f (4.6) Notice also the following simple correspondence between function iteration and function composition. f n = (|f  f {z    f )} (4.7) f occurs n times Function composition in functional order is provided in Standard ML by the prede ned operator \o" (the letter O in lower case). If f and g are ML functions then f o g is their composition. However, we will de ne a compose function which is identical to (op o).



val compose

= fn (f, g) => fn x => f (g (x))

Exercise 4.4.1 Which, if either, of the following are well de ned? (1) compose (compose, uncurry compose) (2) compose (uncurry compose, compose) Exercise 4.4.2 Use the prede ned Standard ML version of function composition to de ne a function, iter , which behaves identically to the function iter given earlier.

Chapter 5

Varieties of recursive functions

Stephen Gilmore

In this chapter we will investigate further ways in which functions may be de ned and mechanisms for deriving new functions from old without requiring any knowledge of the construction of the existing function. We will continue to de ne small integer functions and defer investigation of ML's rich type system.

5.1 Linear recursion The recursive functions so far in these notes have a simple, appealing property: the recursion is linear. That is, each recursive application gives rise to one new application. There is a special case of linear recursion which has the desirable property that it allows functions expressed in this way to be eciently executed. The special case is called tail recursion.

5.1.1 Tail recursion An implementation of a recursive function is said to be tail recursive if the result of a recursive invocation of the function is returned directly as the result of the previous invocation. An example will help to illustrate this: consider the sum function given below. val rec sum

=

fn |

1 n

=> =>

1 n + sum (n { 1)

This function is not in tail-recursive form since the result of the recursive call to sum must be stored so that it may later be added to the current value of n. The result of that addition is returned as the result of the previous invocation of the function. In fact, in order to be able to compute the sum of the numbers from 1 to n, the function must be able to store n integers. For such a small example, storing the integers is not a signi cant diculty but with a larger example, passing larger items of data as the parameter, this 24

Varieties of recursive functions 25

redundant storage might exhaust the memory capacity of our computer, causing it to fail to compute the desired result. This is the motivation for expressing functions in tail-recursive form. We will now express the sum function in a tail recursive form, trsum, through the use of an auxiliary function, trsum . Our strategy will be to equip the trsum function with an accumulation parameter. As the name suggests, the result is accumulated in this location and this value will nally be used to compute the result of the function. val rec trsum = fn m => (fn 1 => m + 1 | n => trsum

(m + n) (n { 1))

The trsum function is then simply (trsum 0). Notice that we cannot forget to supply an initial value for the accumulation parameter. Such small oversights are a common cause of error in other programming styles. Here, ML will not allow us to use the trsum function until we give the initial value for the accumulation parameter. Exercise 5.1.1 Rewrite the iter function as a tail recursive function.

5.2 Non-linear recursion Not all recursive functions are expressed within the linear recursion scheme explained above: consider the following function, fusc , due to Edsger Dijkstra [Dij82]. fusc (1) = 1 (5.1) fusc (2n) = fusc (n) (5.2) fusc (2n + 1) = fusc (n) + fusc (n + 1) (5.3) Edsger Dijkstra named his function \fusc " since if distinct n and m sum to a power of 2 then fusc (n) and fusc (m) are relatively prime. How the function achieves this is obfusc ated. This function may be expressed in ML using only the constructions we have met so far but we will rst re-express the de nition by cases. 8 > if n is 1; fusc (n  2) : fusc (n  2) + fusc (n  2 + 1) otherwise: This may then be transliterated directly into ML as shown below. val rec fusc = fn 1 => 1 | n => let val neven = fn true | false in neven (n mod 2 = 0) end

=> =>

fusc (n div 2) fusc (n div 2) + fusc (n div 2 + 1)

26 Stephen Gilmore Exercise 5.2.1 Prove by induction that fusc (n)  n for all positive n. (This property guarantees that the fusc function can never fail by causing numeric over ow. Why is fusc unlikely to fail by exhausting the machine's memory capacity?)

Exercise 5.2.2 Find a function with the linear recursion property described above which computes the same values as fusc for all positive arguments.

5.3 Mutually recursive functions The functions which we have met so far in the course have been de ned in terms of the primitives of Standard ML and, occasionally, in terms of themselves. With our present state of knowledge about the language, we could not simultaneously de ne two agreeable functions each of which was de ned in terms of the other. Standard ML imposes a declare-before-use rule upon declarations. Because of this, we require another construct in the language to introduce two values simultaneously. The and keyword of ML is used for this purpose. Mutually recursive functions are not simply toy functions, they are used to match the structure of a function to the structure of the data which the function processes. Mutually recursive data structures arise naturally in the description of formal languages. We will illustrate the de nition of mutually recursive functions here using Euclid's algorithm for computing the greatest common divisor of two numbers.

Algorithm 5.3.1 (Greatest Common Divisor) If one of the numbers is zero then the greatest common divisor is the other. If neither is zero then, without loss of generality, assume x  y. Using the remainder theorem for the natural numbers we may replace x by (y  (x  y )) + (x mod y ). Clearly, y divides y  (x  y ) and we can take x mod y and y as our next approximation and use the fact that 0  x mod y < y to re-order the numbers.

Assume that we had implemented a function, gcd1 say, which only deals with the case when x  y . We would wish to pass the reduced parameters, x mod y and y , to a function gcd2 which only deals with the case when x < y . When this function has suitably reduced to the two parameters it will wish to return the values to the gcd1 function. This is the source of the mutual recursion in the function. Euclid's greatest common divisor algorithm is implemented below.

Varieties of recursive functions 27 local val rec

gcd1

=

and

gcd2

=

in

fn | fn |

val gcd = fn (x, y) => let val xgreater

end

in end

(x, 0) (x, y) (0, y) (x, y) =

xgreater (x >= y)

fn |

x gcd2 (x mod y, y) y gcd1 (x, y mod x)

=> => => =>

true false

=> =>

gcd1 (x, y) gcd2 (x, y)

Exercise 5.3.1 The above function serves only to illustrate the mechanism for introducing two value de nitions simultaneously. Euclid's GCD algorithm can be coded much more directly as a single ML function. Construct this function. Exercise 5.3.2 Consider a faulty implementation of Standard ML where the mod operator is known to produce incorrect results. Construct a simpler version of the gcd function which computes the same results as the version above but does not use the mod operator. Exercise 5.3.3 It is possible to introduce two values at once by introducing a pair with the values as the elements, e.g. val (x, y) = (6, 7). Why is it not possible to get around the need to use the keyword and by de ning functions in pairs as shown below? val

(odd, even) = (fn 0 => false fn 0 => true

| |

n => even (n { 1) , n => odd (n { 1))

5.4 Non-termination and xed-points Earlier, we stated that a function de nition such as val rec f = fn x => f (x) did not de ne an interesting function. However, if we attempt to compute the result value for an application of this function, say f(3), what will be the result? We could not even determine the type of the result, much less determine a particular value for f(3) but we could represent it by a symbol, say ?, pronounced \bottom". Standard ML is a strict programming language in the sense that for any function g, we have that g(?) is ?. The value ? is the heaviest computational value we can construct and drags down all other computations.

5.4.1 Fixed points of recursive functions Although ? is not a useful computational value, it is extremely useful in reasoning about computations. We may de ne the meaning of a recursive function in relation to this

28 Stephen Gilmore completely unde ned value. We will use a partial order v, pronounced \approximates", with the property that f v g if, whenever f (x) returns a de ned value then g (x) will return the same value. Obviously, ? v f . We may de ne the meaning of a recursive function to be the limit of a chain of approximations to the result. Consider the following function.

1 iseven 1 iseven (n mod 2) The iseven function terminates (with answer 1) when n is even and diverges when n is val rec

iseven =

fn | |

0 1 n

=> => =>

odd. Let p0; p1; p2;    be the chain of approximations to the meaning of this function. The rst of these approximations is ?, the completely unde ned function. The subsequent approximations in the chain are: ( if a < i and even; pi (a) = ?1 otherwise (5.5) : The least upper bound of this chain is the function p1 , de ned below. ( if a is even; (5.6) p1(a) = ?1 otherwise : The function de ned by p1 will be a xed point of the de nition of the iseven function but because the function is de ned recursively, we cannot assume that it is the only xed point. In fact, it is not. The alwaysone function de ned below is another xed point. val

alwaysone = fn n => 1

To see that this is a xed point we need only check that the three de ning equations of the iseven function are satis ed.

alwaysone 0 = 1 alwaysone 1 = alwaysone 1 alwaysone n = alwaysone (n mod 2)

These are obviously satis ed. So why will the function compute the pessimistic p1 rather than the optimistic alwaysone? The answer is that the p1 function is the least xed point of the function in the sense that there is no function lower than it in the approximation ordering which is still a xed point of the equation which de nes the iseven function. Although there may be many other xed points for equations which de ne objects recursively|in particular, greatest xed points exist and are of interest| these will not be the xed points which are computed by executing the function. De nition 5.4.1 (Fixed point) The xed point of a function, f is any value, x for which f (x) = x. A recursive function can be expressed as the xed point of a higher order function, eg.

Varieties of recursive functions 29

fac

= = =

fn n => (... fac ...) (fn f => (fn n => (...

f ...))) fac H fac where H = fn f => (fn n => (... f ...)). We then have that fac is a xed point of H.

5.4.2 Checking a xed point The xed point of a recursive function has the property that it may be substituted into the function de nition to replace the function being de ned without changing the meaning of the de nition. Consider McCarthy's famous function, given below. ( ? 10 x > 100; (5.7) mac = fn x ) xmac (mac (x + 11)) x  100: This has the following least xed point. ( x ? 10 x > 100; 0 (5.8) mac = fn x ) 91 x  100: Let us rewrite the above least xed point as mac0 = x: x > 100; x ? 10; 91. We can now substitute this everywhere mac occurs in equation 5.7. The result is shown below. x: x > 100 8 ; x ? 10; 91 = > x ? 10 > < whenever x > 100; > ( x: x > 100 ; x ? 10; 91) ((x: x > 100; x ? 10; 91) (x + 11)) > : whenever x  100: We may break this down further since the result of the inner application of mac0 will be x + 1 if x + 11  100 and 91 otherwise. 8 > x ? 10 x > 100; < x: x > 100; x ? 10; 91 = > (x: x > 100; x ? 10; 91) (x + 1) 89  x  100; (5.9) : (x: x > 100; x ? 10; 91) (91) x < 89: The last case in the list will always evaluate to 91 since 91 < 100. The middle case splits into two cases for simpli cation: x = 100 and 89  x < 100. In the latter case the value of x + 1 will never be greater than 100 and the result will be 91 as before. 8 > x > 100; < x ? 10 (5.10) x: x > 100; x?10; 91 = > (x: x > 100; x ? 10; 91) (101) x = 100; : 91 x < 89 or 89  x < 100: However, the middle case simpli es to 101 ? 10 which is 91 again. This shows that mac0 is the xed point of equation 5.7, as claimed.

30 Stephen Gilmore

5.4.3 Computing xed points We have said that an advantage of using Standard ML is the ability to manipulate functions as though they were data. If this is so, can we calculate least xed points of functions using a Standard ML function? Indeed we can. The following function performs this task. val rec FIX

= fn f => fn x => f (FIX f) x

How is this function used? Consider the following function. val facbody

= fn f =>

fn |

0 x

=> =>

1 x * f (x {1)

If this function were to be given the factorial function as the argument f then it would produce a function as a result which was indistinguishable from the factorial function. That is, the following equivalence would hold.

fac



facbody (fac)

But this is just the equivalence we would expect to hold for a xed point. What would then be the result if we de ned the fac function as shown below. val fac

= FIX (facbody)

The fac function will then compute the factorials of the integers which it is given as its argument. Notice that neither the declaration of fac nor the declaration of facbody were recursive declarations; of the three functions which were used only FIX is a recursive function. The FIX function is the most complex function we have seen so far. It takes a function, f, as its argument and returns the result of applying f to the result of applying FIX to f. What demands are being placed on f by this process? It seems that only a certain kind of function could be treated in this way: facbody is suitable but fac is not. How could we characterise all the functions which are suitable arguments for FIX? We seem now to have a reached a point where we could not proceed further without studying Standard ML's type system. That is the subject of the next chapter.

Chapter 6

Types and type inference Stephen Gilmore

Standard ML is a strongly and statically typed programming language. However, unlike many other strongly typed languages, the types of literals, values, expressions and functions in a program will be calculated by the Standard ML system when the program is compiled. This calculation of types is called type inference. Type inference helps program texts to be both lucid and succinct and also serves as a mechanism which can assist the programmer to detect errors before the program has ever been executed.

6.1 The importance of types Standard ML's type system allows the correct used of typed data in programs to be checked at the time that the program is compiled. This is in contrast to the approach taken in many other programming languages which generate checks to be tested while the program is running. Lisp is an example of such a language. Other languages serve the software developer even less well than this since they neither guarantee to enforce typecorrectness when the program is compiled nor when it is running. The C programming language is an example of a language in this class. The result of not enforcing type correctness is that data becomes corrupted and the unsafe use of pointers causes obscure errors. The approach of checking type correctness as early as possible has several clear advantages: no extra instructions need be generated to check the types of data during program execution; and there are no insecurities when the program is executed. Thus Standard ML programs can be executed both eciently and safely. Standard ML programs never `dump core' no matter how inexperienced the author of the program might have been. The design of the language ensures that this can never happen. We emphasized the pragmatic reasons for wishing a programming language to be strongly typed. It would be regrettable if the presence of strong typing made reasoning about programs in the language more dicult. Happily, it does not: the ability to rely on the type correctness of programs makes reasoning about them easier. Consider the 31

32 Stephen Gilmore alternative: a variant of Standard ML which did not enforce the correct use of typed data. In this make-believe language, any function x can be applied to itself. If x denoted a set, by being a function of which returned a boolean result, then the expression x (x) would represent the test which determines whether or not the set x is a member of itself. We might negate this and then abstract to denote all the sets which are not members of themselves, as shown below. fn x =>

not (x (x))

In an application of the function above to itself we would have encoded a version of Bertrand Russell's famous paradox for set theory. The presence of logical paradoxes such as these would be a heavy burden and would throw into question the validity of reasoning about functions, expressions and other terms of the language.

6.2 Type inference We have stated that Standard ML supports a form of polymorphism. Before going further, we should clarify the precise nature of the polymorphism which is permitted. It is sometimes referred to as \let-polymorphism". This name derives from the fact that in this system the term let val Id

= fn x => x

in

(Id 3, Id true)

end

is a well-typed term whereas the very similar

(fn Id => (Id 3, Id true)) (fn x => x) is not. The let .. in .. end construct is not just syntactic sugar for function application, it is essential to provide the polymorphism without compromising the type security of the language. This polymorphic type system has a long history, it was rst discovered by Roger Hindley [Hin69] and then re-discovered and enhanced by Robin Milner [Mil78]. We can distinguish two kinds of bound variables: those bound by fn and those bound by let. The distinction is this:

 all occurrences of a fn-bound identi er must have the same type; whereas  each occurrence of a let-bound identi er may have a di erent type provided it is a instance of the principal type or most general type inferred for that identi er.

We need to make clearer what we mean by \instance" and what we mean by \principal type". This will be done in the next chapter. For the moment we will proceed by informally introducing the notation and main concepts.

Types and type inference 33

6.3 Pairs and record types We have used pairs and tuples without stating their type. A pair with an integer as the left element and a boolean as the right has type \int * bool". Note that int * bool * real is neither (int * bool) * real nor int * (bool * real). Pairs and tuples are themselves simply records with numbered elds. The label for a eld can also be a name such as age. Each eld has an associated projection function which retrieves the corresponding value. The projection function for the age eld is called # age. The following record value has type { name : string, age : int, male : bool }. val person

= { name = \Stephen", age = 31, male = true } Then # name (person) is \Stephen" and # male(person) is true as expected.

6.4 Function types and type abbreviations The following function converts between Celsius and Fahrenheit temperatures. val convert

= fn t => t * 1.8 + 32.0 The type which Standard ML will compute for this function is real  real. This type acts as important documentation for the function, recording the type of argument accepted and the type of result returned. However, the documentation is de cient in that it does not record whether it is the argument or the result which is in Celsius. This de ciency can be corrected by the use of a type abbreviation.

6.4.1 Type abbreviations Standard ML provides a mechanism for giving another name to an existing type. The following declaration will introduce the type celsius and allow all the real arithmetic operations to be applied to a value of that type. type celsius =

real

Obviously we could also have a similar de nition for the type fahrenheit . We may now give a de nition of the convert function which will have type celsius  fahrenheit . val convert :

celsius -> fahrenheit = fn t => t * 1.8 + 32.0

6.4.2 Parameterised type abbreviations Earlier we mentioned the possibility of representing sets by functions from the type of the elements of the set to the booleans. These functions have the obvious behaviour that

34 Stephen Gilmore the function returns true when applied to an element of the set and false otherwise. If we are using functions in this way it would be reasonable to expect to be able to document the fact that these functions represent sets. The complication here is that a family of type abbreviations are being de ned; integer sets, real sets, booleans sets and others. A type variable is used to parameterise the type abbreviation.



type 

set =  -> bool

Someone has stupidly left all the Greek letters out of the ASCII character set so  is actually entered as a primed identi er, a.

Exercise 6.4.1 Write functions union and intersection of type ( set *  set)   set.

6.5 De ning datatypes 6.5.1 Simple datatypes The type mechanism cannot be used to produce a fresh type: only to re-name an existing type. A Standard ML programmer can introduce a new type, distinct from all the others, through the use of datatypes.

colour = red | blue | green This introduces a new type, colour, and three constructors for that type, red, blue and green. Equality is de ned for this type with the expected behaviour that red = red and red green. No signi cance is attached to the order in which the constructors are listed datatype

in the de nition of the type and no ordering is de ned for the type. The pre-de ned type bool behaves as if de ned by datatype bool = true | false. Constructors di er from values because constructors may be used to form the patterns which appear in the de nition of a function by pattern matching, as in the following function: (fn red => 1 | blue => 2 | green => 3).

6.5.2 Recursive datatypes The Standard ML type abbreviation mechanism cannot be used to describe recursive data structures since the type name is not in scope on the right-hand side of the de nition. This is the real reason why we need another keyword, \datatype", to mark our type de nitions as being (potentially) recursive just as we needed a new keyword, rec, to mark our function de nitions as being recursive. One recursive datatype we might wish to de ne is the datatype of binary trees. If we wished to store integers in the tree we could use the following de nition. datatype

inttree = empty | node of int * inttree * inttree

Types and type inference 35

Note the use of yet another keyword, \of ". The declaration introduces the empty binary tree and a constructor function which, when given an integer n and two integer trees, t1 and t2 , builds a tree with n at the root and with t1 and t2 as left and right sub-trees. This tree is simply node (n, t1 , t2 ). The reason for the use of the term \constructor" now becomes clearer, larger trees are really being constructed from smaller ones by the use of these functions. The constructors of the colour datatype are a degenerate form of constructors since they are nullary constructors. The mechanism for destructing a constructed value into its component parts is to match it against a pattern which uses the constructor and, in so doing, bind the value identi ers which occur in the pattern. This convenient facility removes the need to implement `destructors' for every new type and thereby reduces the amount of code which must be produced, enabling more e ort to be expended on the more taxing parts of software development.

6.5.3 Parameterised datatypes Since Standard ML type abbreviations may de ne families of types, it would seem natural that the datatypes of the language should be able to de ne families of datatypes. A datatype de nition with a type parameter may be used to build objects of di erent types. The following tree de nition generalises the integer trees given above.

tree = empty | node of  *  tree *  tree We see that the node constructor is of type ( * ( tree) * ( tree))  ( tree). datatype 

There is a peculiar consequence of allowing datatypes to be de ned in this way since we might make the type increase every time it is passed back to the type constructor thus making a so-called \stuttering" datatype. datatype 

ttree = empty | node of  * ( * ) ttree * ( * ) ttree

There is no way to write a Standard ML function which can process these trees so we might say that allowing their de nition has created an imbalance in the language but it is not a serious aw with the design of the language. Exercise 6.5.1 This exercise is due to Bruce Duba of Rice University. De ne the constructors, Nil and Cons, such that the following code type checks.

Nil => 0 Cons (_, x) => 1 + length (x) val heterogeneous = Cons (1, Cons (true, Cons (fn x => x, Nil))) val rec

length =

fn |

6.6 Polymorphism Many functions which we have de ned do not need to know the precise type of their arguments in order to produce meaningful results. Such functions are thought of as having many forms and are thus said to be polymorphic.

36 Stephen Gilmore Perhaps the simplest example of a polymorphic function is the identity function, id, de ned by val id = fn x => x. Whatever the type of the argument to this function, the result will obviously be of the same type; it is a homogeneous function. All that remains is to assign it a homogeneous function type such as X  X. But what if the type X had previously been de ned by the programmer? The clash of names would be at best quite confusing. We shall give the id function the type    and prohibit the programmer from de ning types called , , and so on. Exercise 6.6.1 De ne a di erent function with type   .

The pairing function, pair, is de ned by val pair = fn x => (x, x). This function returns a type which is di erent from the type of its argument but still does not need to know whether the type of the argument is int or bool or a record or a function. The type is of course   ( * ). Given a pair it may be useful to project out either the left-hand element of the pair or the right-hand element of the pair. The functions fst and snd which are de ned by val fst = fn (x, y) => x and val snd = fn (x, y) => y will achieve this. The functions have types ( * )   and ( * )   respectively. Exercise 6.6.2 The function fn x => fn y => x has type   (  ). Without giving an explicit type constraint, de ne a function with type   (  ).

Notice that parentheses cannot be ignored when computing types. The function paren below has type   ((  )  ) whereas the function paren has type   .

paren = fn n => fn g => g n paren = fn n => (fn g => g) n Exercise 6.6.3 What is the type of fn x => x (fn x => x)? val val

Standard ML will compute the type    for the following function. val rec loop = fn

x => loop (x)

The type    is the most general type for any polymorphic function. In contrasting this with   , the type of the polymorphic identity function, it is simple to realise that nothing could be determined about the result type of the function. This is because no application of this function will ever return a result. In assigning this type to the function, the Standard ML type system is indicating that the execution of the loop function will not terminate since there are no interesting functions of type   . Detecting (some) non-terminating functions is an extremely useful service for a programming language to provide. Of course, the fact that an uncommon type has been inferred will only be a useful error detection tool if the type which was expected is known beforehand. For this reason, it is usually very good practice to compute by hand the type of the function which was written then allow Standard ML to compute the type and then compare the two types

Types and type inference 37

for any discrepancy. Some authors (e.g. Myers, Clack and Poon in [MCP93]) recommend embedding the type information in the program once it has been calculated, either by hand or by the Standard ML system, but this policy means that the program text can become rather cluttered with type information which obscures the intent of the program. However, in some implementations of the language the policy of providing the types for the compiler to check rather than requiring the compiler to infer the types may shorten the compilation time of the program. This might be a worthwhile saving for large programs.

6.6.1 Function composition For the Standard ML function composition operator to have a well-de ned type it is necessary for the source of the rst function to be identical to the target of the second. For both functions, the other part of the type is not constrained. Recall the de nition of the compose function which is equivalent to (op o). val compose

= fn (f, g) => fn x => f (g (x)) We can calculate the type of compose as follows. It is a function which takes a pair so we may say that it is of the form (  ) ! . We do not wish to restrict the argument f and thus we assign it a type ! since this is the worst possible type it can have. This forces g to be of the form ! and we have (( ! )  ( ! )) ! as our current type for compose. Of course, there is no reason to restrict the type of g either so we assign it the type ! and thus calculate (( ! )  ( ! )) ! ( ! ) as the type of the compose function. Exercise 6.6.4 Without using a type constraint, de ne a function with type (( ! )  ( ! )) ! ( ! ). Exercise 6.6.5 What is the type of curry? What is the type of uncurry ?

6.7 Ill-typed functions Any type discipline must reject certain programs. Sometimes these are obviously meaningless but there are complications. Mads Tofte writes in [Tof88]: At rst it seems a wonderful idea that a type checker can nd programming mistakes even before the program is executed. The catch is, of course, that the typing rules have to be simple enough that we humans can understand them and make the computers enforce them. Hence we will always be able to come up with examples of programs that are perfectly sensible and yet illegal according to the typing rules. Some will be quick to say that far from having been o ered a type discipline they have been lumbered with a type bureaucracy.

38 Stephen Gilmore We will now consider some programs which the type discipline of Standard ML will reject. We have already noted above that the function (fn g => (g 3, g true)) is not legal. As we have seen previously, functions can be de ned via pattern matching using a match of the form p1 ) e1 j p2 ) e2 j    j pn ) en . It is important to realise that the expressions e1 ; e2; : : :; en must be of the same type. The patterns p1 ; p2; : : :; pn must also represent values of the same type. The functions below cannot be de ned in Standard ML since it is not possible to nd a single choice for the target in the rst case and for the source in the second case. val

bad =

fn |

1 n

=> =>

true \Hello"

val

nasty =

fn |

1 true

=> =>

1 1

6.7.1 Subtyping Standard ML does not support a notion of subtyping. For example, no relation holds between tuples which are not identical: int * real * bool and int * real are not related. This has the consequence that it is impossible to de ne a function such as the function below, which attempts to project out the third element of a tuple which contains at least three elements. val badthird

= fn x => # 3(x)

Although the following function is legal. val third

= fn (x :  *  * * ) => # 3(x)

The di erence between the two is that the second function speci es exactly how many elds will be in the tuple and the rst does not.

6.7.2 Absorbing functions We saw earlier, (p. 20), that it was not possible to de ne certain kinds of self-applying functions in Standard ML. Other pathological functions also cannot be de ned in Standard ML. Consider the \absorb" function. val rec

absorb = fn x => absorb

This function is attempting to return itself as its own result. The underlying idea is that the absorb function will greedily gobble up any arguments which are supplied. The arguments may be of any type and there may be any number of them. Consider the following evaluation of an application of absorb.

absorb true 1 \abc"

   

(((absorb true) 1) \abc") ((absorb 1) \abc") (absorb \abc") absorb

Types and type inference 39

Such horrifying functions have no place in a reasonable programming language. The Standard ML type system prevents us from de ning them.

6.8 Overloading In Standard ML programs, types are almost always inferred. There are only a few cases where additional information must be supplied by the programmer in order for the system to be able to compute the type of an expression or a value in a declaration. These cases arise because certain pre-de ned operators in the language are overloaded . For example, \+" and \{" are overloaded since they are de ned for both the type int and the type real. The makestring function is also overloaded; it is de ned for integers, reals and booleans. Overloading occurs when an identi er has more than one de nition and these de nitions have di erent types. Overloading is not polymorphism: there is no way for the Standard ML programmer to de ne overloaded operators. This has the consequence that the following simple square function is illegal in Standard ML since the type of the instance of \*" cannot be determined uniquely. val square

=

fn

x => x * x

Although it would be possible to add user-de ned overloading to Standard ML| [Per91] introduces an extension of the Hindley/Milner system for this purpose|there is little motivation to do so. User-de ned overloading is only a convenience and the consequences of introducing overloading in terms of the execution eciency of the type inference algorithm could be quite severe [Per91, Hen93].

6.9 Computing types Perhaps we seem to have made too much of the problem of computing types. It may seem to be just a routine task which can be quickly performed by the ML system. In fact this is not true. Type inference is a computationally hard problem [KTU90] and there can be no algorithm which guarantees to nd the type of a value in a time proportional to its \size". Types can increase exponentially quickly and their representations soon become textually much longer than an expression which has a value of that type. Fortunately the worst cases do not occur often in ML programs. Fritz Henglein states in [Hen93],

: : : in practice, programs have \small types", if they are well typed at all, and

Milner-Mycroft type inference for small types is tractable. This, we think, also provides insight into why ML type checking is usable and used in practice despite its theoretical intractability. Exercise 6.9.1 Compute the type of y  y if y is x  x and x is pair  pair.

Chapter 7

Principal type-schemes for functional programs Luis Damas and Robin Milner

7.1 Introduction This paper is concerned with the polymorphic type discipline of ML, which is a general purpose functional programming language, although it was rst introduced as a metalanguage (whence its name) for conducting proofs in the LCF proof system [GMW79]. The type discipline was studied in [Mil78], where it was shown to be semantically sound, in a sense made precise below, but where one important question was left open: does the typechecking algorithm|or more precisely, the type assignment algorithm (since types are assigned by the compiler, and need not be mentioned by the programmer)| nd the most general type possible for every expression and declaration? Here we answer the question in the armative, for the purely applicative part of ML. It follows immediately that it is decidable whether a program is well-typed, in contrast with the elegant and slightly more permissive type discipline of Coppo [Cop80]. After several years of successful use of the language, both in LCF and other research and in teaching to undergraduates, it has become important to answer these questions|particularly because the combination of

exibility (due to polymorphism), robustness (due to semantic soundness) and detection of errors at compile time has proved to be one of the strongest aspects of ML. The discipline can be well illustrated by a small example. Let us de ne in ML the function \map", which maps a given function over a given list|that is, This is a reprint of a paper by Luis Damas and Robin Milner which appeared in the conference record of the Ninth Annual Symposium on Principles of Programming Languages, Albuquerque, New Mexico, 1982. Permission to copy without fee all or part of this material is granted provided that the copies are not made or distributed for direct commercial advantage, the ACM copyright notice and the title of the publication and its date appear, and notice is given that copying is by permission of the Association for Computing Machinery. To copy otherwise, or to republish, requires a fee and/or speci c permission.

c 1982 ACM 0-89791-065-6/82/001/0207 $00.75

40

Principal type-schemes for functional programs 41

map f [x1,: : :,xn]

The required declaration is



val rec map = fn f => fn s => if null s then nil else cons (f(hd

[f(x1),: : : ,f(xn)]

s)) (map f (tl s))

The type-checker will deduce a type-scheme for \map" from existing type-schemes for \null", \nil", \cons", \hd" and \tl"; the term \type-scheme" is appropriate since all these objects are polymorphic. In fact, from null : 8 ( list ! bool) nil : 8 ( list) cons : 8 ( ! ( list ! list)) hd : 8 ( list ! ) tl : 8 ( list ! list) will be deduced map : 8 8 (( ! ) ! ( list ! list)): Types are built from type constants (bool, : : :) and type variables ( , , : : :) using type operators (such as in xed ! for functions and post xed \list" for lists); a type-scheme is a type with (possibly) quanti cation over type variables at the outermost. Thus, the main result of this paper is that the type-scheme deduced for such a declaration (and more generally, for any ML expression) is a principal type-scheme, i.e. that any other type-scheme for the declaration is a generic instance of it. This is a generalisation of Hindley's result for Combinatory Logic [Hin69]. ML may be contrasted with ALGOL 68, in which there is no polymorphism, and with Russell [DD79], in which parametric types appear explicitly as arguments to polymorphic functions. The generic types of Ada may be compared with type schemes. For simplicity, our de nitions and results here are formulated for a skeletal language, since their extension to ML is a routine matter. For example, recursion is omitted since it can be introduced by simply adding the polymorphic xed-point operator fix : 8 (( ! ) ! ) and likewise for conditional expressions.

7.2 The language Assuming a set Id of identi ers x, the language Exp of expressions e is given by the syntax e ::= x j e e0 j x:e j let x = e in e0

42 Luis Damas and Robin Milner (where parentheses may be used to avoid ambiguity). Only the last clause extends the -calculus. Indeed, for type checking purposes every let expression could be eliminated (by replacing x by e everywhere in e0 ), except for the important consideration that in on-line use of ML declarations let x = e are allowed, whose scope (e0) is the remainder of the on-line session. As illustrated in the introduction, it must be possible to assign type-schemes to identi ers thus declared. Note that types are absent from the language Exp. Assuming a set of type variables and of primitive types , the syntax of types  and of typeschemes  is given by  ::= j  j  !   ::=  j 8  A type-scheme 8 1 : : : 8 n  (which we may write 8 1 : : : n  ) has generic type variables 1 ; : : :; n. A monotype  is a type containing no type variables.

7.3 Type Instantiation If S is a substitution of types for type variables, often written [1= 1 ; : : :; n= n] or [i= i ], and  is a type-scheme, then S is the type-scheme obtained by replacing each free occurrence of i in  by i, renaming the generic variables of  if necessary. Then S is called an instance of ; the notions of substitution and instance extend naturally to larger syntactic constructs containing type-schemes. By contrast, a type scheme  = 8 1 : : : m  has a generic instance  0 = 8 1 : : : n 0 if  0 = [i = i] for some types 1; : : :; m , and the j are not free in . In this case we shall write  >  0 . Note that instantiation acts on free variables, while generic instantiation acts on bound variables. It follows that  >  0 implies S > S 0.

7.4 Semantics The semantic domain V for Exp is a complete partial order satisfying the following equations up to isomorphism, where Bi is a cpo corresponding to primitive type i : V = B0 + B1 +    + F + W (disjoint sum) F = V !V (function space) W = fg (error element) To each monotype  corresponds a subset of V , as detailed in [Mil78]; if v 2 V is in the subset for , we write v : . Further, we write v :  if v :  for every monotype instance  of  , and we write v :  if v :  for every  which is a generic instance of . Now let Env = Id ! V be the domain of environments  . The semantic function E : Exp ! Env ! V is given in [Mil78]. Using it, we wish to attach meaning to assertions of the form A j= e : 

Principal type-schemes for functional programs 43

where e 2 Exp and A is a set of assumptions of the form x :  0, x 2 Id. If the assertion is closed, i.e. if A and  contain no free type variables, then the sentence is said to hold i , for every environment  , whenever  [ x] :  0 for each member x :  0 of A, it follows that E [ e]  : . Further, an assertion holds i all its closed instances hold. Thus, to verify the assertion x : ; f : 8 ( ! ) j= (fx) : It is enough to verify it for every monotype  in place of . This example illustrates that free type-variables in an assertion are implicitly quanti ed over the whole assertion, while explicit quanti cation in a type scheme has restricted scope. The remainder of this paper proceeds as follows. First we present an inference system for inferring valid assertions. Next we present an algorithm W for computing a typescheme for any expression, under assumptions A. We then show that W is sound, in the sense that any type-scheme which it yields is derivable in the inference system. Finally we show that W is complete, in the sense that derivable type-scheme is an instance of that computed by W .

7.5 Type Inference From now on we shall assume that A contains at most one assumption about each identi er x. Ax stands for the result of removing any assumption about x from A. For assumptions A, expression e and type-scheme  we write A`e: if this sentence may be derived from the following inference rules: TAUT: A`x: (x :  in A) INST: GEN: COMB:

A`e: A ` e : 0 A`e: A ` e : 8  A ` e :  0 !  A ` e0 :  0 A ` (e e0 ) : 

ABS:

Ax [ f x :  0 g ` e :  A ` (x:e) :  0 ! 

LET:

A ` e :  Ax [ f x :  g ` e 0 :  A ` (let x = e in e0 ) : 

( >  0) ( not free in A)

The following example of a derivation is organised as a tree in which each node follows from those immediately above it by an inference rule.

44 Luis Damas and Robin Milner i : 8 ( ! ) ` i : 8 ( ! ) INST

i : 8 ( ! ) ` i : 8 ( ! )

i : 8 ( ! ) ` i : ( ! ) ! ( ! ) INST i : 8 ( ! ) ` i : ! x: `x: ABS

` x:x : ! GEN

COMB

i : 8 ( ! ) ` i i : !

` x:x : 8 ( ! ) LET

(let i = x:x in i i) : ! The following proposition, stating the semantic soundness of inference, can be proved by induction on e. Proposition 1 (soundness of inference) If A ` e :  then A j= e :  .

We will also require later the two following properties of the inference system. Proposition 2 If S is a substitution and A ` e :  then SA ` e : S . Moreover if there is a derivation of A ` e :  of height n then there is also a derivation of SA ` e : S of height less or equal to n. Proof by induction on n.

2

Lemma 1 If  >  0 and Ax [ fx :  0g ` e : 0 then also Ax [ fx :  g ` e : 0 . Proof: We construct a derivation of Ax [fx :  g ` e : 0 from that of Ax [fx :  0g ` e : 0 by substituting each use of TAUT for x :  0 with x :  followed by an INST step to derive x :  0. Note that GEN steps remain valid since if occurs free in  then it also occurs free in  0 . 2

7.6 The type assignment algorithm W The type inference system by itself does not provide an easy method for nding, given A and e, a type-scheme  such that A ` e : . We now present an algorithm W for this purpose. In fact, W goes a step further. Given A and e, if W succeeds it nds a

Principal type-schemes for functional programs 45

substitution S and a type  , which are most general in a sense to be made precise below, such that SA ` e :  To de ne W we require the uni cation algorithm of Robinson [Rob65]. Proposition 3 (Robinson) There is an algorithm U which, given a pair of types, either returns a substitution V or fails; further

1. If U (;  0) returns V , then V uni es  and  0 , i.e. V  = V  0 . 2. If S uni es  and  0 , then U (;  0) returns some V and there is another substitution R such that S = RV . Moreover, V involves only variables in  and  0. We also need to de ne the closure of a type  with respect to assumptions A;

A( ) = 8 1 : : : n  where 1 ; : : :; n are the type-variables occurring free in  but not in A.

Algorithm W W (A; e) = (S;  ) where 1. If e is x and there is an assumption x : 8 1 : : : n  0 in A then S = Id and  = [ i= i] 0 where the i's are new. 2. If e is e1 e2 then let W (A; e2) = (S1 ; 2) and W (S1A; e2) = (S2; 2) and U (S21; 2 ! ) = V where is new; then S = V S2S1 and  = V . 3. If e is x:e1 then let be a new type variable and W (Ax [ fx : g; e1) = (S1; 1); then S = S1 and  = S1 ! 1 . 4. If e is let x = e1 in e2 then let W (A; e1) = (S1 ; 1) and W (S1Ax [ fx : S1 A(1)g; e2) = (S2; 2); then S = S2 S1 and  = 2 . NOTE: When any of the conditions above is not met W fails. The following proposition proves that W meets our requirements. Proposition 4 (Soundness of W ) If W (A; e) succeeds with (S;  ) then there is a derivation of SA ` e :  .

46 Luis Damas and Robin Milner Proof By induction on e using proposition 2.

2

It follows that there is also a derivation of SA ` e : SA( ). We refer to SA( ) as a type-scheme computed by W for e under SA.

7.7 Completeness of W Given A and e, we will call P a principal type-scheme of e under assumptions A i 1. A ` e : P 2. Any other  for which A ` e :  is a generic instance of P . Our main result, restricted to the simple case in which A contains no free type-variables, may be stated as follows: If A ` e :  , for some  , then W computes a principal type scheme for e under A. This is a direct corollary of the following general theorem, which is a stronger result suited to inductive proof: Theorem (Completeness of W ) Given A and e, let A0 be an instance of A and  a type-scheme such that A0 ` e :  Then

1. W (A; e) succeeds 2. If W (A; e) = (S;  ) then, for some substitution R, A0 = RSA and RSA( ) >  . 2 In fact, from the theorem one also derives as corollaries that it is decidable whether e has any type at all under assumptions A, and that, if so, it has a principal type scheme under A. The detailed proofs of results in this paper, and related results, will appear in the rst author's forthcoming PhD Thesis.

Chapter 8

Exceptions

Andrew Appel and David MacQueen

In some cases, no appropriate result can be found to be returned as the result of a function. For these exceptional cases, ML provides an exception mechanism.

8.1 Declaring exceptions Because of their unusual nature, exceptions cannot be de ned using an ML datatype declaration. Instead they are introduced using the keyword exception. Exceptions have constructors just as datatypes do and may be used to pass values. The following exception contains storage for an integer value. exception TooSmall of

int

Thus TooSmall is a constructor of type int ! exn and TooSmall(~100) is an exception value of type exn.

8.2 Raising and handling exceptions The rough and ready rule for understanding how exceptions are handled is as follows. If an exception is raised by a raise expression raise E

(exp)

which lies in the textual scope of a declaration of the exception constructor E , then it may be handled by a handling rule handle E

(pat) => exp0

This chapter is adapted with permission from \The Standard ML Reference Manual" by Andrew Appel and David MacQueen.

47

48 Andrew Appel and David MacQueen but only if this handler is in the textual scope of the same declaration. Any exception, regardless of scope, is handled by a wildcard or variable pattern, as handle _ =>

exp0

This rule is perfectly adequate for exceptions declared at top level. Exercise 8.2.1 Evaluation of tuples in ML either takes place from left-to-right or from right-to-left. Using exceptions, construct a tuple expression to determine the order of evaluation.

8.3 An example To illustrate the generality of exception handling, suppose we have declared some exceptions as follows: exception Badint of int and

Badstring of string

and that a certain integer expression exp may raise either of these exceptions and also runs the risk of dividing by zero. The handler in the following handle expression would deal with these expressions:

exp

handle | | | |

Badint 1 Badint x Badstring \ " Badstring s Div

=> => => => =>

0 4 div x 0 size (s) { 1 10000

Note that the whole expression is well-typed because in each handling rule the type of the match-pattern is exn, and because the result type of the match is int, the same as the type of exp. Note also that the last handling rule will handle Div exceptions raised by exp, but will not handle the Div exception that may be raised by 4 div x in the third handling rule. Finally, note that a universal handling rule |

_

=>

50000

at the end would deal with all other exceptions raised by exp. Exercise 8.3.1 Rewrite the gcd function from page 27 so that it raises an exception NoGCD for negative numbers and for gcd (0, 0).

Exceptions 49

8.4 Exception constructors For an exception constructor E , the expression E (exp) evaluates the expression exp, producing value v , and then applies the constructor E to it, yielding the value E (v ), whose type is exn. The raise keyword may be applied to any expression of type exn, and has the e ect of \raising" that exception value. The innermost (dynamically) enclosing expression e = e1 handle m1 is found; all further evaluation of the expression e1 (and its subphrases) is aborted; and the match m1 is applied to the exception value, yielding the result of the expression e. If the match in a handler fails, then the exception value is re-raised, and another enclosing handler is found. Exception constructors may be nullary (have no associated value), in which case the exp and pat in the previous discussion are omitted. Exceptions may be constructed independently of raising them: exception A of int val e = A (6) val x = raise e

Handlers may be abstracted from the handle keyword. We show this in two forms below; on the left without using derived forms and on the right with derived forms. The additional parentheses are obligatory around the arguments formed by an application of the exception constructor A. val

h=

fn | |

A (0) A_ v

=> => =>

\zero" \nonzero" raise v

fun | |

h (A (0)) = \zero" h (A _) = \nonzero" h (v) = raise v

The use of such an abstracted handler is illustrated below.



f (x) handle e => h (e)

Note that it is advisable in this case to have a default clause in the function h, since the default for a handle match (re-raising the exception) is di erent from the default for a fn match (raising the Match exception). The ordinary wildcard pattern _ will handle any exception when it is used in a pattern, as will any pattern consisting solely of a variable. These should be used with some care, bearing in mind that they will even handle interrupts. Nullary exception names, when misspelled, appear to the compiler to be variables; these will then match any exception. For this reason we recommend the convention that exception names (and other constructors) be written beginning with an uppercase character, and variables be written beginning with a lowercase character. The compiler may remind the programmer of this convention when it is violated.

Chapter 9

Datatypes and structural induction Stephen Gilmore

The use of datatypes such as trees might make reasoning about an ML function seem more dicult since we would be unable to use the familiar induction principle for the natural numbers without re-formulating the function to be an equivalent function which operated on integers. The amount of work involved in the re-formulation would be excessive and it is preferable to have an induction principle which operates on datatypes directly. This is the basis of structural induction, due to Rod Burstall [Bur69].

9.1 The tree datatype We will introduce a few important de nitions for trees as de ned by the parameterised

tree datatype which was given earlier (p. 35). De nition 9.1.1 (Nodes) The nodes of an tree are the objects of type which it contains. We can easily de ne a function which counts the number of nodes in a tree.

nodes (empty) = 0 nodes (node (n, t1, t2 )) = 1 + nodes (t1 ) + nodes (t2) De nition 9.1.2 (Path) A path (from tree t1 to its subtree tk ) is the list t1 ; t2; : : :; tk of trees where, for all 1  i < k, either ti = node (n; ti+1; t0) or ti = node (n; t0; ti+1). De nition 9.1.3 (Leaf) A tree t is a leaf if it has the form node (n; empty; empty ). fun |

De nition 9.1.4 (Depth) We will now describe two Standard ML functions which measure the depth of a tree. They both have type tree ! int.

maxdepth (empty) = 0 maxdepth (node (n, t1 , t2 )) = 1 + max (maxdepth (t1 ), maxdepth (t2 )) Above, max is the integer maximum function. The mindepth function is similar. De nition 9.1.5 (Perfectly balanced) A tree t is said to be perfectly balanced if maxdepth (t) = mindepth (t). fun |

50

Datatypes and structural induction 51

9.2 Induction for trees In order to prove using structural induction some properties of functions which process trees we must rst give an induction principle for the tree datatype. Such a principle can be derived directly from the de nition of the datatype. Induction Principle 9.2.1 (Trees)

P (empty)

(P (l) ^ P (r)) ) P (node (n; l; r)) 8t: P (t)

Proposition 9.2.1 A perfectly balanced tree of depth k has 2k ? 1 nodes. Proof: The empty tree is perfectly balanced and we have nodes (empty ) = 0 and maxdepth (empty) = 0 and 0 = 20 ? 1 as required. Now consider a perfectly balanced tree t of depth k + 1. It is of the form node (n; l; r) where the depth of l is k and the depth of r is also k. By the induction hypothesis we have that nodes (l) = 2k ? 1 and nodes (r) = 2k ? 1. lhs = nodes (node (n; l; r))

= = = = =

1 + nodes (l) + nodes (r) 1 + (2k ? 1) + (2k ? 1) 2k + 2 k ? 1 2k+1 ? 1 rhs

2

9.3 The list datatype A pleasant datatype found in most programming languages is the datatype of lists. These are ordered collections of elements of the same type. The pre-de ned ML type constructor list is a parameterised datatype for representing lists. The parameter is the type of elements which will appear in the list. Thus, int list describes a list of integers, real list describes a list of reals and so on. The list which contains no elements is called nil and if h is of type and t is of type list then h :: t is also of type list and represents the list with rst element h and following elements the elements of t in the order that they appear in t. Thus 1 :: nil is a one-element integer list; 2 :: 1 :: nil is a two-element integer list and so on. De nition 9.3.1 (Head) An empty list has no head. This is an exceptional case. The head of a non-empty list is the rst element.

52 Stephen Gilmore exception Hd fun |

hd (nil) = raise Hd hd (h :: t) = h

De nition 9.3.2 (Tail) An empty list has no tail. This is an exceptional case. The tail of a non-empty list is that part of the list following the rst element. exception Tl fun |

tl (nil) = raise Tl tl (h :: t) = t

De nition 9.3.3 (Length) The rst function we will implement is the simple length function for lists. It is of type list ! int. The empty list has length zero; a list with a head and a tail is one element longer than its tail.

length (nil) = 0 length (h :: t) = 1 + length (t) De nition 9.3.4 (Append) The append function is of type ( list  list) ! list. In fact this is a pre-de ned associative operator in ML. If l1 and l2 are two lists of the same type then l1 @ l2 is a list which contains all the elements of both lists in the order they occur. We will write an append function which is identical to op @. fun append (nil, l2 ) = l2 | append (h :: t, l2 ) = h :: (append (t, l2 )) De nition 9.3.5 (Reverse) Using the append function (or the pre-de ned ML operator) we can easily de ne the function which reverses lists. This function is of type list ! list. (As with append, the rev function is pre-de ned but we will give a de nition fun |

here which is identical to the pre-de ned function.) The base case for the recursion will be the empty list which reverses to itself. Given a list with head h and tail t then we need only reverse t and append the single-element list [ h ] (equivalently, h :: nil). fun |

rev (nil) = nil rev (h :: t) = (rev (t)) @ [ h ]

9.4 Induction for lists We will now introduce an induction principle for lists. As before, this principle is derived directly from the de nition of the list datatype. P (nil) P (t) ) P (h :: t) Induction Principle 9.4.1 (Lists) 8l: P (l) Proposition 9.4.1 (Interchange) The rev function and the append operator obey an interchange law since the following holds for all lists l1 and l2 .

rev (l1 @ l2 ) = rev (l2 ) @ rev (l1 )

Datatypes and structural induction 53 Proof: The proof is by induction on l1 . The initial step is to show that this proposition holds when l1 is the empty list, nil. Using properties of the append operator, we conclude rev (nil @ l2) = rev (l2) = rev (l2 ) @ nil = rev (l2 ) @ rev (nil) as required. Now assume rev (t @ l2 ) = rev (l2 ) @ rev (t) and consider h :: t. lhs = rev ((h :: t) @ l2 )

= = = = = =

rev ([ h ] @ t @ l2) rev (h :: (t @ l2)) (rev (t @ l2 )) @ [ h ] rev (l2 ) @ rev (t) @ [ h ] rev (l2 ) @ rev (h :: t) rhs

2 Exercise 9.4.1 Prove by structural induction that for all lists l1 and l2

length (l1 @ l2 ) = length (l1 ) + length (l2 ). Proposition 9.4.2 (Involution) The rev function is an involution, i.e. it always undoes its own work, since rev (rev (l)) = l. Proof: The initial step is to show that this proposition holds for the empty list, nil. From the de nition of the function, rev (rev (nil)) = rev (nil) = nil as required. Now assume that rev (rev (t)) = t and consider h :: t. lhs = rev (rev (h :: t))

= = = = =

rev ((rev (t)) @ [ h ]) (rev [ h ]) @ (rev (rev (t))) [h] @ t h :: t rhs

2 Exercise 9.4.2 Write a tail recursive version of rev called trrev . Your function should have type ( list  list) ! list and should also have the property that trrev (nil, l) = rev (l) for all lists l. Exercise 9.4.3 Prove by structural induction that trrev (nil, l) = rev (l) for all lists l.

54 Stephen Gilmore

9.5 Converting trees to lists There are many ways to convert a tree into a list. These are termed traversal strategies for trees. Here we will look at three: preorder, inorder and postorder. De nition 9.5.1 (Preorder traversal) First visit the root, then traverse the left and right subtrees in preorder. De nition 9.5.2 (Inorder traversal) First traverse the left subtree in inorder, then visit the root and nally traverse the right subtree in inorder. De nition 9.5.3 (Postorder traversal) First traverse the left and right subtrees in postorder and then visit the root. Exercise 9.5.1 De ne tree ! list functions, preorder, inorder and postorder . Exercise 9.5.2 Can you de ne a function reforest such that reforest (preorder (t)) = t for all trees t? Exercise 9.5.3 Prove by structural induction that nodes (t) = length (inorder (t)).

Chapter 10

List processing Stephen Gilmore

Lists are a pre-de ned Standard ML datatype. Two lists can be joined by the in x append operator, \ @ ". Lists are either empty, nil (also written [ ]), or one element long, h :: nil (also written [ h ]), or more generally h :: t (equivalent to [ h ] @ t). The double colon operator, pronounced \cons" associates to the right, thus 1 :: 2 :: nil is 1 :: (2 :: nil) and may also be written [1, 2].

10.1 Checking for membership The following simple function tests for membership in a given list:

member (x, nil) = false member (x, h :: t) = x = h orelse member (x, t) We would like this function to have type  *  list  bool but unfortunately it does fun |

not. This cannot be a fully polymorphic function since we make an assumption about the values and lists to which it can be applied: we assume that equality is de ned upon them. The Standard ML terminology for a type which admits equality is an equality type. When equality type variables are printed by the Standard ML system they are printed with two leading primes, i.e.

a *

a list  bool, which is the type of the member function. Types which do not admit equality in Standard ML include function types and structured types which contain function types, such as pairs of functions or lists of functions. The consequence is that a function application such as member (sin, nil) will be rejected as being incorrectly typed.

10.2 Testing for null lists Equality types arise in one slightly unexpected place, when testing if a list is empty. Here since we are using an equality on a value of a polymorphic datatype, the type system of Standard ML will assume that an equality exists for the elements of the list also. Thus 55

56 Stephen Gilmore fun

null s = s = nil

will assign to null the quali ed polymorphic type

a list  bool whereas the declaration fun |

null nil = true null _ = false

will assign to null the fully polymorphic type a list  bool.

10.3 Searching by key Searching for an element by a key will allow us to retrieve a function from a list of functions. The retrieve function has type

a * (

a * ) list  . exception Retrieve fun retrieve (k1 , nil) | retrieve (k1 , (k2 ,

= v2) :: t) =

raise Retrieve if k1 = k2 then v2 else retrieve

(k1, t)

10.4 Selecting from a list It is useful to have functions which select elements from a list. The selection criteria might be quite simple: 1. select the rst n elements; or 2. select all but the rst n elements. Call the function which implements the rst criterion take and the function which implements the second drop. The selection could be slightly more exacting if we supply a predicate which characterises the required elements. The selection might then be: 1. select the leading elements which satisfy the criterion; or 2. select all but the leading elements which satisfy the criterion. Call the function which implements the rst criterion takewhile and the function which implements the second dropwhile. We will implement take and takewhile.

10.4.1 The take function There are two base cases for this function. Either the number of elements to be taken runs out (i.e. becomes zero) or the list runs out (i.e. becomes nil). In either case the result of the function is nil. In the recursive call we attach the head to the result of taking one less element from the tail.

List processing 57 fun | |

take (0, l) = nil take (n, nil) = nil take (n, h :: t) = h :: take (n { 1, t)

Exercise 10.4.1 Construct a drop function with the same type as take. Your drop function should preserve the property for all lists l and non-negative integers n not greater than length (l) that take (n, l) @ drop (n, l) = l.

10.4.2 The takewhile function The base case for this function occurs when the list is empty. If it is not then there are two sub-cases. 1. The head element satis es the predicate. 2. The head element does not satisfy the predicate. If the former, the element is retained and the tail searched for other satisfactory elements. If the latter, the selection is over. fun |

takewhile (pred, nil) = nil takewhile (pred, h :: t) = if

then else

pred (h) x :: takewhile (pred, t) nil

Exercise 10.4.2 Construct the analogous dropwhile function. Exercise 10.4.3 Construct a lter function which returns all the elements of a list which satisfy a given predicate.

10.5 Sorting lists The implementation we will develop is simple insertion sort. There are two parts to this algorithm. The rst is inserting an element into a sorted list in order to maintain the ordering; the second is repeatedly applying the insertion function.

10.5.1 Inserting an element into a sorted list This operation has a simple base case. Inserting an element into the empty list gives us a singleton (one-element) list. All singleton lists are sorted. For non-empty lists we compare the new element with the head. If the new element is smaller it is placed at the front. If larger, it is inserted into the tail.

58 Stephen Gilmore fun |

insert (x : int, nil) = [ x ] insert (x : int, h :: t) = if

then else

x 

We could then try to label integer expressions as being delayed and thereby turn them into functions of type \unit  int". If we need the integer value which they would compute

Lazy applicative programming 63

then we can force the evaluation of the integer expression by applying the function to (). We will now attempt to de ne the functions which force evaluation and delay evaluation.

11.2.3 The functions force and delay The force function has type  delayed  . This is a simple function to implement. val force : 

delayed ->  = fn d => d () The function delay has type    delayed. It should be the inverse of force and for all expressions exp we should have that force (delay (exp)) evaluates to the same value as exp itself. We will now attempt to de ne the delay function. val delay :  ->  delayed = fn d => (fn () => d) This function has the correct type and has achieved the aim that force (delay (exp)) evaluates to the same value as exp for all expressions but it has not achieved the e ect we wanted. The additional requirement was that it should delay the evaluation of an expression. However, consider the evaluation of a typical application of delay using Standard ML's call-by-value semantics.

delay (14 div 2)

(fn d => (fn () => d)) (14 div 2) (fn d => (fn () => d)) (7) fn () => 7 This is not the desired e ect since we wished to have the expression delay (14 div 2) be identical to (fn () => 14 div 2). It is not possible to implement the delay function in Standard ML since the expression   

will always be evaluated and the resulting value passed to the function. We will write \fn () => exp" from now on but continue to pronounce this as \delay exp".

11.3 Making an eager function lazy Now that we have all the machinery available to construct lazy functions we may ask whether a lazy variant can always be found for an existing eager function. This question can be answered positively and we will sketch out a recipe for constructing the lazy variant of given eager function. Consider a function f with the following form: fun f

x = ??? x ??? x ???

and assume that this function has type X  Y. We can provide a lazy version of the function which has type X delayed  Y where every occurrence of x is replaced by force (x):

f x = ??? (force (x)) ??? (force (x)) ??? nally replace any applications of the function, f (exp), by f (fn () => exp). fun

64 Stephen Gilmore

11.4 Lazy datatypes Although any eager function can be made lazy the chief interest in lazy programming is the ability to create lazy datatypes such as in nite sequences. Consider the datatype of in nite sequences of integers. This can be described as a Standard ML datatype with a single constructor, cons. datatype seq

= cons of int * (unit -> seq)

The functions to return the head and tail of an in nite sequence are much simpler than those to return the head and tail of a list. Since the sequence can never be exhausted there is no exceptional case behaviour. However, note that the tail function is partial since the evaluation of t () may fail to terminate. fun head

(cons (h, _)) = h fun tail (cons (_, t)) = t ()

Exercise 11.4.1 Write a function to give a list of the rst n integers in a sequence.

We can construct a simple function which returns the in nite sequence which has the number one in every place. fun ones

() = cons (1, ones)

Unfortunately the cons constructor has the property that it does not compose since cons ( rst, cons (second, tail)) is not well-typed. Life is much easier if we de ne a lazy version of cons which does compose. fun lcons (h,

t) () = cons (h, t)

We may now easily de ne the in nite sequence which has one in rst place and in every other odd place with every other digit being zero. fun oneszeroes

() = cons (1, lcons (0, oneszeroes))

In general we may de ne more interesting sequences by thinking of de ning a whole family of sequences simultaneously. For example, the sequences which start at n and move up in steps of one. fun from

n () = cons (n, from (n + 1))

Using the from function we may de ne the sequence of all natural numbers quite easily. These are simply all the numbers from zero upwards. val nats

= from 0 ()

Lazy applicative programming 65

Given a sequence constructed in this way we could produce another in nite sequence by supplying a function which can be applied to each element of the sequence, thereby generating a new sequence. The function tentimes, when applied to a sequence s, will return a sequence where the elements are the corresponding elements of s multiplied by ten. fun tentimes

(cons (h, t)) = cons (10 * h, tentimes o t)

Using this function we may de ne tens as the result of the composition (tentimes o ones) and hundreds as the result of the composition (tentimes o tentimes o ones). Exercise 11.4.2 Given the following de nitions, what is next (zeroes ())? fun fun

zeroes () = cons (0, zeroes) next (cons (h, t)) = cons (h + 1, next o next o t)

11.5 An example: Computing e (This example is taken from [Tur82].) As an example of a program which uses in nite sequences, consider the problem of computing the transcendental number e. We would like to calculate as many digits of e as we wish. Notice that the decimal expansion of e is an in nite sequence of integers. (Each integer being a single decimal digit.) We could then use in our implementation the in nite sequence datatype just de ned. The number e can be de ned as the sum of a series. The terms in the series are the reciprocals of the factorial numbers. 1 1 X i=0 i! = 0!1 + 1!1 + 2!1 + 3!1 +    = 2:7182818284590 : : : (base 10) a base in which the ith digit has weight 1=10i?1 = 2:1111111111111 : : : a base in which the ith digit has weight 1=i!

e =

Both the decimal expansion and the expansion in the funny base where the ith digit has weight 1=i! can be expressed as in nite integer sequences. The problem is then to convert from this funny base to decimal. For any base we have:

 take the integer part as a decimal digit;

66 Stephen Gilmore

 take the remaining digits, multiply them all by ten and renormalise (using the appropriate carry factors);  repeat the process with the new integer part as the next decimal digit.

Note: The carry factor from the ith digit to the (i ? 1)th digit is i. I.e. when the ith digit is  i we add 1 to the (i ? 1)th digit and subtract i from the ith digit. fun and

carry (i, cons (x, u)) = carry' (i, x, u ()) carry' (i, x, cons (y, v)) = cons (x+y div i, lcons (y mod i, v));

fun and and

norm (i, cons (x, u)) () = norm' (i, x, u ()) norm' (i, x, cons (y, v)) = norm" (i, y+9 (update(ans, i, f (compute ans) i) ; sub (ans, i)) | v => v

compute ans

The function which is to be memoised is the bonacci function. This is presented as a \lifted" variant of its usual (exponential running time) version. The lifted function is called mf. fun | |

mf b 0 = 1 mf b 1 = 1 mf b n = b (n { 1) + b (n { 2)

The memoised version of bonacci is then simply memo mf.

12.8 Input/output The nal imperative features of Standard ML which we will present are the facilities for imperative input and output which are available in the language. A familiar C programming metaphor for processing les may be easily implemented in Standard ML. The function below simulates the behaviour of the UNIX cat command. fun

cat (s) =

let val f = open in (s) and c = ref \" in while (c := input (f, 1) ; !c \") do end

output (std out, !c); close in (f)

The pre-de ned streams are std_in of type instream and std_out of type outstream. A new input stream can be created by using the function open_in of type string  instream. A new output stream can be created by using the open_out function of type string  outstream. There are close_in and close_out functions as well. The functions for input and output of text are input of type instream * int  string and output of type outstream * string  unit. Two auxiliary input functions are also provided: lookahead and end_of_stream.

Chapter 13

Abstract data types Stephen Gilmore

Thus far our programs have been small and simple and it would have seemed excessive to have structured them into modular units. When we come to write larger programs we will want to encapsulate some functions together with a datatype in order to control the access to the elements of the datatype. The construction we use for this purpose is abstype .. with .. end.

13.1 An abstract data type for sets We will implement an abstract data type for sets. These are unordered collections of values. A membership test is provided. Duplications aren't signi cant: there is no way to test how many times a value occurs in a set. The problem is then to provide a way to construct sets and test for membership without giving away other information such as the number of times a value appears in the set. The following abstract data type introduces a type constructor, set, a value emptyset and two functions, addset and memberset. The constructors nullset and insertset are hidden, they are not visible. abstype

a set = nullset | insertset of

a *

a set with val emptyset = nullset val addset = insertset fun memberset (x, nullset) = false | memberset (x, insertset(v,s)) = x = v orelse memberset end

(x, s)

It might seem somewhat futile to hide the names nullset and insertset and then provide emptyset and addset. The point is that in so doing, we take away the constructor status of nullset and insertset and that means that they cannot be used in pattern matching to destruct the constructed value and see inside. 74

Abstract data types 75

But it would seem that nothing we have described could not be achieved with the features of the Standard ML language which we knew already, albeit in a slightly more complicated de nition. local datatype

a set = nullset | insertset of

a *

a set in type

a set =

a set val emptyset = nullset val addset = insertset fun memberset (x, nullset) = false | memberset (x, insertset(v,s)) = x = v orelse memberset end

(x, s)

So in what sense is the abstract data type more abstract than the type which is de ned here? The problem is that equality is available on the sets which are de ned using the second form of the de nition and the equality which is provided is not the one we want. No equality test is permitted if we use an abstype de nition. If we want to allow equality we must implement it ourselves. abstype

a set = nullset | insertset of

a *

a set with val emptyset = nullset val addset = insertset fun memberset (x, nullset) = false | memberset (x, insertset(v,s)) = x = v orelse memberset (x, s) local fun subset (nullset, _) = true | subset (insertset(x, s1 ), s2 ) = memberset (x, s2) andalso subset in fun equal (s1, s2 ) = subset (s1 , s2 ) andalso subset (s2 , s1) end end

(s1 , s2)

We have made the equal function available but not the subset function which we used in its implementation. Abstract data types are rst-class values in Standard ML because they may be passed to functions as arguments, as shown below. fun allmembers ([ ], _) | allmembers (h :: t,

= true s) = memberset(h,s) andalso allmembers (t, s) This function has type (

a list *

a set)  bool. They may also be returned from functions as results. The following function has type (

a list *

a set) 

a set.

76 Stephen Gilmore fun |

addmembers ([ ], s) = s addmembers (h :: t, s) = addset (h, addmembers (t, s))

Chapter 14

Modules

Andrew Appel and David MacQueen

Standard ML provides a powerful module system which can be used to partition programs along clean interfaces.

14.1 Structures In its simplest form, a module is (syntactically) just a collection of declarations viewed as a unit, or (semantically) the environment de ned by those de nitions. This is one form of a structure-expression: struct dec end. For example, the following structure-expression represents an implementation of stacks: struct datatype  stack = Empty | Push of  *  stack exception Pop and Top fun empty (Empty) = true | empty _ = false val push = Push fun pop (Push (v,s)) = s | pop (Empty) = raise Pop fun top (Push (v,s)) = v | top (Empty) = raise Top end

Structure-expressions and ordinary expressions are distinct classes; structure-expressions may be bound using the structure keyword to structure-names, while ordinary expressions are bound using val to value-variables. The form of a structure binding is as follows: structure name = structure-expression Thus, we might make a structure Stack using the structure-expression shown above: This chapter is reproduced with permission from \The Standard ML Reference Manual" by Andrew Appel and David MacQueen.

77

78 Andrew Appel and David MacQueen structure Stack

=

struct datatype ... exception ... end

...

The environment E that binds the identi ers stack, Pop, empty, etc. is now itself bound to the structure-identi er Stack. To refer to names in E , quali ed identi ers must be used. A quali ed identi er consists of a structure-name, a dot, and the name of a structure component, e.g. Stack.empty (a value), Stack.stack (a type), Stack.Pop (an exception), etc. Structure closure: In order to isolate the interface between a structure and its context, a struct phrase is not allowed to contain global references to types, values, or exceptions, except for pervasive primitives of the language like int, nil, etc. It can, however, contain global references to other structures, signatures, and functors, including quali ed names referring to components (values, types, etc.) of other structures. There are three forms of structure-expression: 1. An environment enclosed in struct ... end (as above), 2. An identi er that has been previously bound in a structure declaration, and 3. A functor application F(str), where F is the name of a functor and str is a structure expression. Thus, the declaration structure Pushdown = Stack binds the name Pushdown to the same structure that Stack is bound to; here, Stack is an example of the second kind of structure expression.

14.1.1 Accessing structure components The bindings making up a structure de ne named components of the structure, as in a record. To refer to such components we use quali ed names, which are formed by appending a period followed by a component name to the name of a structure. For instance, Stack.empty refers to the function empty de ned in the structure Stack. If the quali ed name designates a substructure of a structure, then it too has components; e.g. A.B.x denotes the component x of the substructure B of a structure A. Quali ers can be attached only to names; they do not apply to other forms of structure expressions. Quali ed names are treated as single lexical units; the dot is not an in x operator. Direct access to the bindings of a structure is provided by the open declaration, which is analogous to the \with" clause of Pascal. For example, in the scope (determined in the usual way) of the declaration open Stack

Modules 79

the names stack, empty, pop, etc. refer to the corresponding components of the Stack structure. It is as though the body of the structure de nition had been inserted in the program at that point, except that the bindings are not recreated, but are instead simply \borrowed" from the opened structure. open declarations follow the usual rules for visibility, so that if A and B are two structures containing a binding for x (of the same avour, of course), then after opening both A and B with the declaration open A open B

the unquali ed identi er x will be equivalent to B.x. The x component of A can still be referred to as A.x, unless B also contains a substructure named A. Quali ed identi ers do not have in x status. If + is declared in x in a structure A, the quali ed identi er A.+ is not an in x identi er. However, when an identi er is made visible by opening a structure, it retains its in x status, if any. The declaration open A B C is equivalent to open A; open B; open C.

14.1.2 Evaluating structure expressions The evaluation of a structure expression str depends on its form, and assumes a current structure environment SE that binds structures and functors to names. Informally, evaluation proceeds as follows: 1. If str is an encapsulated declaration (i.e. struct...end), then the body declarations are evaluated relative to SE and the pervasive value, exception, and type environments of ML (that is, the environments binding the built-in primitives of the language). The resulting environment is packaged as a structure and returned. The evaluation of value bindings may have an e ect on the store (the mapping of references to contents); the new store is returned as well, to be used in subsequent expression evaluations. 2. If str is a simple name, then its binding in SE is returned. If it is a quali ed name, then it is used as an access path starting with SE and the designated substructure is returned. 3. If str is a functor application F( str0 ), where the functor F is declared by functor F (A) = body, the parameter structure str0 is evaluated in SE yielding structure s1 ; then the \body" of the de nition of M, which is a structure expression, is evaluated in SE + fA 7! s1 g. In other words, functor applications are evaluated in a conventional call-by-value fashion.

14.1.3 Evaluating structure declarations To evaluate a simple structure declaration, one evaluates the de ning structure expression in the current environment SE and returns the binding of the name of the left hand side

80 Andrew Appel and David MacQueen to the resulting structure. If evaluation of a structure expression raises an (untrapped) exception, then the declaration has no e ect.

14.1.4 Structure equivalence For certain purposes, we must be able to determine whether two (references to) structures are equal or \the same." Here structures are treated somewhat like datatypes; each evaluation of an encapsulated declaration or functor application creates a distinct new structure, and all references to this structure are considered equal. Thus after the following declarations: structure S1 structure S2 structure S3 structure S4

= = = =

struct ... end

S1

struct val x = struct val x =

4 end 4 end

the names S1 and S2 refer to the same structure and are \equal," whereas S3 and S4 are di erent structures and are not equal, even though the right-hand-sides are identical.

14.2 Signatures It is often useful to explicitly constrain a structure binding to limit the visibility of its elds. This is done with a signature, which is to a structure binding as a type constraint is to a value binding. For example, we might write a signature for the Stack module as sig

end

type  stack exception Pop and Top val Empty :  stack val empty :  stack -> bool val push :  *  stack ->  stack val pop :  stack ->  stack val top :  stack -> 

The signature mentions the structure components that will be visible outside the structure. Signatures may be bound to identi ers by a signature declaration, signature sig-Id =

sig-expr

where sig-Id is an identi er and sig-expr is a signature expression|either a sig ... end phrase or a previously bound signature identi er. Thus, the signature above could be bound to the identi er STACK by the declaration

Modules 81 signature STACK

=

sig

end

type  stack exception Pop and Top

...

A signature can be used to constrain a structure by including it in a structure declaration: structure str-id : sig-expr = str For example, we could write structure Stack1 :

STACK = Stack

Now the constructor Push is not a visible component of the Stack1 structure, since it doesn't appear in the signature; the quali ed identi er Stack1.Push is erroneous. Furthermore, since stack is mentioned in the signature only as a type constructor and not as a datatype constructor, the identi er Stack1 .stack is usable as a type but not a datatype. Finally, since the constructor Empty is mentioned as a val in the signature, but not as a constructor (i.e. as part of a datatype speci cation), then Stack1 .Empty may be applied as a function but not matched in a pattern.

14.2.1 Signature matching There are many signatures that can match the structure Stack. One of the \broadest" is structure Stack2

:

sig

=

Stack

end

datatype  stack = Empty | Push of  exception Pop and Top val empty :  stack -> bool val push :  *  stack ->  stack val pop :  stack ->  stack val top :  stack -> 

*  stack

and the \narrowest" is structure Stack3 : sig end =

Stack

Now, the structure Stack2 is equivalent to Stack; it is the \same" structure, and all the same elds are visible. The structure Stack3 has no components; there are no quali ed identi ers beginning with Stack3. However, Stack3 is the \same" for structureequivalence purposes as Stack, Stack1 , and Stack2 ; signature constraints do not change the identity of a structure, just which elds are visible.

Chapter 15

Formal speci cation Stephen Gilmore

In software development, expending e ort in the timely production of a clear and precise initial speci cation of the problem description is a wise investment of e ort. Many of the most costly errors in software systems can be traced back to omissions and carelessness in early statements of the software development which is to be undertaken. Other errors are introduced in moving from one phase in the software development to the next.

15.1 Principles For the purposes of producing descriptions which may be understood by a group of software developers collaborating on a software project it may be desirable to express the speci cations in a formal language. This has the favourable consequence that the speci cations may be machine-checked at least for scope and type consistency. Some of the graphical notations currently used for the speci cation of computer systems have a sound mathematical foundation (e.g. Petri Nets and Statecharts [Har87]) but these seem to be the exception rather than the rule. Many of the graphical notations are not formal (i.e. there is no grammar for the language) and it is therefore not possible to check even the syntactic correctness of a supposed speci cation. Even if a grammar is described for a graphical language, reasoning in any rigorous way about graphical descriptions is not natural for many software developers. In contrast, rigorous reasoning about an algebraic formula is familiar to all software developers.

15.2 Aims After selecting a formal, textual language as a suitable medium for the speci cation an immediate question presents itself: should the speci cation itself be a program, albeit perhaps an inecient prototype. There seem to be some reasons why a formal speci cation need not always be a 82

Formal speci cation 83

program. A program describes how a task is to be performed; a speci cation describes what task is to be performed. Programs are written for machines; speci cations are written for people. Further, the use of a programming language as a speci cation language forces overcommitment in some cases. It will be necessary to program an algorithm for the speci cation to be executable. Is this algorithm then intended to be part of the speci cation or is it just a tool used to describe what is to be solved rather than how it is to be solved? This topic of whether a speci cation should be executable is discussed at greater length by Ian Hayes and Cli Jones [HJ89].

15.3 Requirements for a speci cation language We now turn to what is required of a speci cation language. A rst requirement is applicability. If the speci cation language is to be used for a given application area (perhaps protocol design or database description) then it should contain elements which support the description of typical problems in these areas (for the former, communication primitives and for the latter data structuring facilities). A further requirement which we should place upon a speci cation language is that of naturality. The techniques required to use the formal speci cation language well should be an extension of existing good practice. A third requirement is clarity. The purpose of a formal speci cation for a program is to record clearly all the properties of the program which a user may rely upon. Another requirement is a facility for deduction. It would seem desirable to have these properties documented in a way which allows deductions to be made. This last point is of great importance. If we cannot execute our program speci cation then there must be another means by which we can check its tness for the use intended. One such way will be to conjecture a property which should always be preserved by the system and then attempt to prove that this is preserved. Another way will be to refute properties which should not hold.

15.4 Features of a speci cation language Speci cation languages which are used to describe programs have been an object of study in computer science for the past decade. Over this period some consensus has been reached about the features which a speci cation language should possess. First, a logical sublanguage which allows simple assertions to be recorded about the program variables. For example, the following assertion records the fact that x and y have di erent values but neither states the value of x nor the value of y. axiom

x y

This assertion can then be taken as an axiom which can be used in later deduction.

84 Stephen Gilmore The logical sublanguage should be rich enough to allow the expression of relationships which hold for all values of a particular type. Often this is captured via a universal quanti cation, as below. axiom forall

l => length (rev (l)) = length (l)

Logical sentences may be nested. Notice that the order in which the quanti ers appear is signi cant.

l

axiom exists => forall

l => length (l) >= length (l )

The logical sublanguage is not sucient in itself to be a convenient vehicle for describing programs which manipulate structured data. For this reason, the logical sublanguage is usually complemented by a data structure language which describes which data structures may be assumed by the speci cation author. Frequently, these are datatypes with appealing logical properties (for example, functions and lists). To both of these, speci cations languages frequently add extra-logical constructions such as if .. then .. else and let .. in .. end since these are convenient and familiar forms. Armed with all of this speci cations in the language are still merely (perhaps long ) lists of axioms. A construct to delimit scope and hide information is also desirable.

15.5 Extended ML The Extended ML language can be used to express axiomatic relationships such as those given in the previous section. A good starting point for learning the language is two papers by Don Sannella [San86, San89]. The Extended ML language contains the modules language of Standard ML and all of the applicative datatypes. Reference types and streams (for input and output) are not supported. The logical sublanguage of Extended ML contains a number of logical quanti ers which include the universal and existential quanti ers (forall and exists ). The logical implication operator, implies , is available. In addition, a second (strong) equality operator, \==", is provided. Exercise 15.5.1 The implies keyword is a derived form in the Extended ML language. Suggest a suitable translation into a simpler expression.

15.6 Some sample speci cations 15.6.1 The functions ascending and descending The function ascending and the function descending have type int list  bool. As expected, ascending ([ 1, 2, 4 ]) evaluates to true and descending ([ 4, 2, 1 ]) evaluates to true. Both ascending ([ 1, 4, 2 ]) and descending ([ 1, 4, 2 ]) evaluate to false.

Formal speci cation 85

We could record the relationship between descending and ascending as an axiom. However, the following attempt is erroneous. axiom

descending = ascending o rev

The equality function which is used in this axiom is Standard ML's pre-de ned equality. There is a strong theoretical result which says that no computer program can test arbitrary functions for equality. The phrase \descending = ascending o rev" is not legal as a Standard ML expression even though both sides of the equation are well formed and they have the same type. Functions are not ML \equality types". The equality which we must use is non-computable. It is termed \strong equality". This equality relation behaves as Standard ML equality when comparing two de ned values of the same equality type. In addition, it returns true when (extensionally) equal functions or non-terminating computations are compared. It returns false otherwise. The following is the legal Extended ML axiom which we seek. axiom

descending == ascending o rev

Since we can now assert equality between two function values we can encode the requirement that the ascending function is the xed point of the obvious function expression for the ascending function. axiom

ascending == fn (x :: y :: t) => x true

15.6.2 The take function The take function was de ned on page 57. It selects the rst n elements from a given list. We will now attempt to axiomatise its behaviour. Consider the e ect of taking zero elements from the front of a list. Whatever list is given, an empty list is the result. We could write an axiom which captures this. axiom forall l => take (0, l) == nil (15.1) Perhaps surprisingly, we need to use strong equality in this axiom. The take function is fully polymorphic and could be applied to (an integer and) a list of functions. The axiom must capture the behaviour even in this case. At the other extreme of the behaviour of this function, taking as many elements from the list as we can would return the entire list. The maximum number of elements which can be taken is given by length (l). axiom forall l => take (length (l), l) == l (15.2) The range of values over which we can predict the behaviour of the take function is 0  n  length (l) for any given list l. We have given axioms which describe the behaviour at the extremes but for a precise axiomatisation we should like to describe the

86 Stephen Gilmore behaviour throughout the range. Clearly, we may observe that the list taken is always of the right length.

l n n 0 andalso n forall => >=

(15.3)

However, this does not identify the content of the list, merely its length. Perhaps any non-empty list returned contains only the head element of the given list, over and over again. Such behaviour would not be satisfactory and we should like to prohibit it by recording the observation that the result is a pre x of the input. A way of saying this is to observe that there is a sux which could be appended to the result in order to reconstitute the input.

l n

axiom forall => forall => >= exists =>

n

0 andalso n ordered (insert (x, t)) end end

However, this speci cation of a structure is not well-typed. The signature constraint requires the structure body to match the TREE signature. This matching fails since no values have been supplied which match the function values insert and present. Of course,

Structured speci cations 91

it would not be practical to require the function values to be supplied as executable Standard ML functions since this could require the (perhaps substantial) coding e ort which we are attempting to delay as long as possible. Even if we could circumvent this diculty, another is just around the corner. We wish to be able to undertake the re nement of speci cations to programs in a stepwise manner; that is, a speci cation need not be implemented in a single step, it may be replaced by another speci cation with some re nement towards code. The subsequent speci cation may then be re ned and so on. From all of the above, we are forced to conclude that we require another notational tool if we are to be able to specify structures. It would be desirable if the same notational extension could also treat the re nement problem outlined above.

16.3 Using axioms in functors The small notational extension which we shall adopt is to write a question mark for a value which has not yet been implemented. A ? has an arbitrary type. Thus the following is a legal conditional expression: if x > 100 then x { 10 else ?. The question mark may be used to denote a value or a type or a structure or a functor. All that now remains to be done is to write

val insert = ? val present = ?

to allow the signatures to match as required. We now give a functor speci cation for a functor which is to accept structures respecting the PO signature (this includes satisfying the axioms) and will produce ordered binary trees of the correct element type (again there is a requirement to satisfy the axioms in the body of the structure). For the two versions of elem and le to match, the result signature and the input signature must share the same structure (with the PO signature). This is enforced by the use of a Standard ML sharing constraint which requires the Elements substructure of the result to be identical to the input structure. Thus the result signature is not TREE as might be expected but sig include TREE sharing Elements = InPO end (where InPO is the name of the input structure with signature PO).

92 Stephen Gilmore functor MakeTree (InPO : PO) : sig include TREE sharing Elements = InPO end = struct structure Elements : PO = InPO open Elements datatype tree = empty | node of elem * tree local fun ordered ... in axiom forall (x, t) => ordered (insert (x, end val insert = ? val present = ? end

* tree t))

Exercise 16.3.1 Produce a functor implementation which matches the MakeTree specification. Exercise 16.3.2 The functor speci cation does not place any requirement on the trees to be height-balanced. Specify such a requirement.

Appendix A

Derived forms

Andrew Appel and David MacQueen

Standard ML is equipped with a number of derived forms, which in no way add to the power of the language, as each is expressible in terms of the more primitive constructs. The n-tuple type (ty1 * ty2 *    * tyn ) for n  2 is an abbreviation for the record type with numeric labels { 1:ty1 ,2:ty2 ,    ,n:tyn }. Similarly the n-tuple expression (exp1, exp2,    , expn) is an abbreviation for the record expression { 1=exp1, 2=exp2,   , n=expn }, and the n-tuple pattern (pat1, pat2,    , patn) is an abbreviation for the record pattern { 1=pat1, 2=pat2 ,    , n=patn }. The \empty record" {} can also be written as (). This value is conventionally returned from functions that have side-e ects but don't return an interesting value. The type of empty records is named unit (because the type only contains one value). The expression # lab for any label (symbolic or numeric) is a selector function that extracts the named eld from a record. The case exp of match expression is completely equivalent to a (fn match) expression applied to the exp. The if-then-else expression has conventional semantics: it evaluates a boolean condition, then evaluates either the then expression or the else expression; this behaviour can be de ned in terms of a case with patterns true and false. The orelse and andalso boolean operators evaluate their right-hand expressions only if the left-hand expressions are insucient to determine the answer (i.e. false for orelse and true for andalso). Their syntactic precedence is weaker than all other in x operators, and orelse binds weaker than andalso. Several expressions may be separated by semicolons, and the whole enclosed in parentheses; all the expressions will be evaluated and the result of the whole is the result of the last expression. When such an expression sequence is the entire body of a let expression, the parentheses may be omitted. The expression while exp1 do exp2 repeatedly evaluates exp1 followed by exp2 until exp1 evaluates to false (just as in Pascal); the while expression can be expressed in terms This chapter is reproduced with permission from \The Standard ML Reference Manual" by Andrew Appel and David MacQueen.

93

94 Andrew Appel and David MacQueen of a recursive function. The expression [ exp1 , exp2,   , expn ] is an abbreviation for the list

exp1 :: exp2::   

:: expn :: nil,

and similarly the pattern [ pat1 , pat2 ,   , patn ] is an abbreviation for the list pattern

pat1 :: pat2::   

:: patn :: nil.

In both patterns and expressions, [ ] is an abbreviation for nil. In a record pattern (but not in a record expression), an element id = id, where the pattern is a variable with the same identi er as the label, can be abbreviated as just id. Similarly, id = id as pat can be abbreviated as id as pat, id = id : ty can be abbreviated id : ty. Recursive clausal function de nitions can be de ned conveniently with the keyword \fun". The declaration fun | |

f pat1a pat2a pat3a = exp1 f pat1b pat2b pat3b = exp2 f pat1c pat2c pat3c = exp3

is an abbreviation for the curried function val rec f = fn pat1 => fn pat2 => case (pat1,pat2,pat3) of (pat1a,pat2a,pat3a) | (pat1b,pat2b,pat3b) | (pat1c,pat2c,pat3c)

fn

pat3 =>

=> => =>

exp1 exp2 exp3

The patterns must all be atomic patterns (including, of course, arbitrary patterns enclosed in parentheses) to avoid syntactic ambiguity. A special form of fun declaration is permitted for in xed function identi ers, of which an example is shown here: fun

a + b = b { ~a

The derived forms are summarized in the tables below.

Derived forms 95

A.1 Expressions and patterns Derived Form Types: ty1 * 2 *ty n Expressions: () (exp1, 2 , expn) case exp of match # lab if exp then exp1 else exp2 exp1 orelse exp2 exp1 andalso exp2 (exp1 ; 1 ; expn ; exp)

let dec in exp1 while exp1 do

[ exp1 ,

0

;

1

exp2

, expn ]

Derived Form Patterns:

() (pat1, 2 , patn) [ pat1, 0 , patn ] } { , id, { , id as pat, } { , id : ty,

; expn end

Equivalent Form {

1 : ty 1 ,

2

, n : ty n }

{} { 1 = exp1 , 2 , n = expn } (fn match) (exp) fn { lab = x, ... } => x case exp of true => exp1 | false => exp2 if exp1 then true else exp2 if exp1 then exp2 else false case exp1 of _ => => case expn of _ => exp let dec in (exp1 ; 1 ; expn ) end let val rec f = fn () => if exp1 then (exp2 ; f ()) else () in f () end exp1 :: 0 :: expn :: nil

Equivalent Form

{} {1

}

(no space between \ ()")

= pat1,

, n = patn } pat1 :: 0 :: patn :: nil { , id = id, } { , id = id as pat, } { , id = id : ty, } 2

Each derived form is identical semantically to its \equivalent form." The type-checking of each derived form is also de ned by that of its equivalent form. The derived type ty 1 * 2 * ty n is called an (n?)tuple type, and the values of this type are called (n?)tuples. The nal derived pattern allows a label and its associated value to be elided in a record pattern, when they are the same identi er.

A.2 Bindings and declarations A syntax class fb of function bindings is used as a convenient form of value binding for (possibly recursive) function declarations. The equivalent form of each function binding

96 Andrew Appel and David MacQueen is an ordinary value binding. These new function bindings must be declared by fun, not by val; however, functions may still be declared using val or val rec along with fn expressions.

Derived Form Function bindings fb: | |

id pat11

1

pat1n tc = exp1

id patm1

1

patmn tc = expm

fb1 and

1 and fbn

Declarations:

Equivalent Form id = fn x1 => 1 fn xn => case (x1, , xn) of (pat11, 1 , pat1n) => exp1 tc | | (patm1 , 1 , patmn ) => expm vb1 and 1 and vbn (where vbi is the equivalent of fbi )

fun fb

val rec

exp

val it

tc

vb

(where vb is the equivalent of fb)

= exp (only at top level)

In the table above, \tc" stands for an optional type constraint|a colon followed by a type expression. The last derived declaration (using \it") is only allowed at top-level, for treating top-level expressions as degenerate declarations; \it" is just a normal value variable.

References

[AJ89]

A. Appel and T. Jim. Continuation-passing, closure-passing style. In Proceedings of the Sixteenth ACM Symposium on Principles of Programming Languages, pages 293{302. ACM Press, 1989. [All88] L. Allison. Some applications of continuations. Computer Journal, 31(1):9{11, 1988. [AM87] Andrew W. Appel and David B. MacQueen. A Standard ML compiler. In Gilles Kahn, editor, Functional Programming Languages and Computer Architecture (LNCS 274), pages 301{24, New York, 1987. Springer-Verlag. [AMMT88] A. Appel, D. MacQueen, R. Milner, and M. Tofte. Unifying exceptions with constructors in Standard ML. Technical Report ECS-LFCS-88-55 (also published as CSR-266-88), Laboratory for Foundations of Computer Science, University of Edinburgh, June 1988. [App90] Andrew W. Appel. A runtime system. Lisp and Symbolic Computation, 3(343-80), 1990. [App92a] Andrew W. Appel. Compiling with Continuations. Cambridge University Press, 1992. [App92b] Andrew W. Appel. A critique of Standard ML. Technical Report CS-TR-364-92, Princeton University, February 1992. [ASS85] Harold Abelson, Gerald J. Sussman, and Julie Sussman. Structure and Interpretation of Computer Programs. The MIT Press, 1985. [Bac87] John Backus. Can programming be liberated from the von Neumann style? A functional style and its algebra of programs. In ACM Turing Award Lectures: The First Twenty Years, pages 63{130. ACM Press, 1987. [BD77] R.M. Burstall and J. Darlington. A transformation system for developing recursive programs. Journal of the ACM, 24:44{67, 1977. [Ber89] B. Berthomieu. Implementing CCS, the LCS experiment. Technical Report 89425, LAASCNRS, 1989. [Ber91] Dave Berry. The Edinburgh SML Library. Technical Report ECS-LFCS-91-148, Laboratory for Foundations of Computer Science, University of Edinburgh, April 1991. [BG80] Rod M. Burstall and Joseph A. Goguen. The Semantics of Clear, a Speci cation Language. In Proceedings of Advanced Course on Abstract Software Speci cations, Copenhagen, 1980. Springer-Verlag. [BM86] Marianne Baudinet and David MacQueen. Tree pattern matching for ML. Available from David MacQueen, AT&T Bell Laboratories, 600 Mountain Avenue, Murray, Hill, NJ 07974, 1986.

97

98 [BMT92]

Dave Berry, Robin Milner, and David N. Turner. A semantics for ML concurrency primitives. In Proceedings of the Nineteenth ACM Symposium on Principles of Programming Languages, pages 119{129, 1992. [Bur69] R.M. Burstall. Proving properties of programs by structural induction. The Computer Journal, 12:41{48, 1969. [Bur75] W.H. Burge. Recursive Programming Techniques. Addison-Wesley, 1975. [BW87] R. Bird and P. Wadler. Introduction to Functional Programming. International Series in Computer Science. Prentice-Hall, 1987. [Car83] Luca Cardelli. The functional abstract machine. Polymorphism, 1(1), January 1983. [Car87] Luca Cardelli. Basic polymorphic typechecking. Science of Computer Programming, 8(2):147{172, 1987. [CDDK86] D. Clement, J. Despeyroux, T. Despeyroux, and G. Kahn. A simple applicative language: Mini-ML. In ACM Conference on LISP and Functional Programming, pages 13{27, August 1986. [Cha92] Emmanuel Chailloux. An ecient way of compiling ML to C. In ACM SIGPLAN Workshop on ML and its Applications, pages 37{51, San Francisco, California, June 1992. [CM90] Eric C. Cooper and J. Gregory Morrisett. Adding threads to Standard ML. Technical Report CMU-CS-90-186, School of Computer Science, Carnegie Mellon University, December 1990. [CO92] A. Cant and M.A. Ozols. A veri cation environment for ML programs. In Proceedings of the ACM SIGPLAN Workshop on ML and its Applications, San Francisco, California, June 1992. [Cop80] M. Coppo. An extended polymorphic type system for applicative languages. In LNCS 88, pages 194{204. Springer-Verlag, 1980. [Cri92] Regis Cridlig. An optimizing ML to C compiler. In ACM SIGPLAN Workshop on ML and its Applications, pages 28{36, San Francisco, California, June 1992. [CW85] L. Cardelli and P. Wegner. On understanding types, data abstraction, and polymorphism. Computing Surveys, 17(4):471{522, 1985. [Dam84] Luis Damas. Type Assignment in Programming Languages. PhD thesis, University of Edinburgh, 1984. Also published as Technical Report CST-33-85, Department of Computer Science. [DD79] A. Demers and J. Donahue. Report on the programming language Russell. Technical Report TR 79-371, Computer Science Department, Cornell University, 1979. [DHM91] B. Duba, R. Harper, and D. MacQueen. Typing rst-class continuations in ML. In Proceedings of the Eighteenth ACM Symposium on Principles of Programming Languages, pages 163{73, New York, 1991. ACM Press. [Dij82] Edsger W. Dijkstra. Selected Writings on Computing: A Personal Perspective. Texts and Monographs in Computer Science. Springer-Verlag, 1982. [Dil88] Antoni Diller. Compiling Functional Languages. John Wiley and Sons, 1988. [DM82] Luis Damas and Robin Milner. Principal type schemes for functional programs. In Proceedings of the 9th ACM Symposium on Principles of Programming Languages, pages 207{212, Albuquerque, 1982. [FWH92] Daniel P. Friedman, Mitchell Wand, and Christopher T. Hayes. Essentials of Programming Languages. The MIT Press, 1992. [GB80] Joseph A. Goguen and Rod M. Burstall. The semantics of Clear, a speci cation language. Journal of the ACM, 39(1):95{146, January 1980.

99 [GGM91]

Carl A. Gunter, Elsa L. Gunter, and David B. MacQueen. An abstract interpretation for ML equality kinds. In T. Ito and A. R. Meyer, editors, Theoretical Aspects of Computer Software, volume 526 of Lecture Notes in Computer Science, pages 112{130. Springer-Verlag, September 1991. [GHT84] Hugh Glaser, Chris Hankin, and David Till. Principles of Functional Programming. PrenticeHall International, 1984. [GHW82] John Guttag, Jim Horning, and J. Wing. Some notes on putting formal speci cations to productive use. Science of Computer Programming, 2:53{68, 1982. [GMW79] M.J. Gordon, R. Milner, and C.P. Wadsworth. Edinburgh LCF. Springer LNCS 78, Berlin, 1979. [Gre93a] John Greiner. The soundness of a formulation of weakly polymorphic references (preliminary draft). Distibuted by the author, January 1993. [Gre93b] John Greiner. Standard ML weak polymorphism can be sound. Technical Report CMU-CS93-160, Carnegie Mellon University, May 1993. [GT79] Joseph A. Goguen and J. Tardo. An introduction to OBJ: a language for writing and testing software speci cations. In Speci cation of Reliable Software, pages 170{189. IEEE, 1979. [Hal90] Anthony Hall. Seven myths of formal methods. IEEE Software, pages 11{19, September 1990. [Har87] David Harel. Statecharts: A visual formalism for complex systems. Science of Computer Programming, 8(3):231{274, June 1987. [Har89] Robert Harper. Introduction to Standard ML. Technical Report ECS-LFCS-86-14, Laboratory for Foundations of Computer Science, University of Edinburgh, January 1989. Revised edition. [Har93] Rachel Harrison. Abstract Data Types in Standard ML. Wiley, 1993. [Hay92] Ian Hayes. VDM and Z: A comparative case study. Formal Aspects of Computing, 3(1):76{99, 1992. [Hay93] I. Hayes, editor. Speci cation Case Studies. International Series in Computer Science. Prentice-Hall, second edition, 1993. [Heh84] E.C.R. Hehner. The Logic of Programming. International Series in Computer Science. Prentice-Hall, 1984. [Hen88] Fritz Henglein. Type inference and semi-uni cation. In Proc. ACM Symp. Lisp and Functional Programming Languages, pages 184{197, July 1988. [Hen93] Fritz Henglein. Type inference with polymorphic recursion. ACM Transactions on Programming Languages and Systems, 15(2):253{289, 1993. [Hin69] J.R. Hindley. The principal type-scheme of an object in combinatory logic. Trans. Amer. Math. Soc, 146:29{60, 1969. [HJ89] I. J. Hayes and C. B. Jones. Speci cations are not (necessarily) executable. Software Engineering Journal, 4(6):320{338, November 1989. [HM93] R. Harper and J.C. Mitchell. On the type structure of Standard ML. ACM Transactions on Programming Languages and Systems, 15(2):211{252, 1993. [HMT87] R. Harper, R. Milner, and M. Tofte. A type discipline for program modules. In TAPSOFT '87, Berlin, 1987. Springer LNCS 250. [HMV92] My Hoang, John Mitchell, and Ramesh Viswanathan. Standard ML weak polymorphism and imperative constructs. Technical report, Department of Computer Science, Stanford University, Stanford, CA 94305, December 1992. [Hoa71] C.A.R. Hoare. Proof of a program: Find. Communications of the ACM, 14:39{45, January 1971.

100 [Hoa72]

C.A.R. Hoare. Proof of a structured program: The sieve of eratosthenes. BCS Computer Journal, 15:321{5, November 1972. [Hol83] Soren Holstrom. PFL: A functional language for parallel programming and its implementation. Report 83.03 R, Department of Computer Science, Chalmers University of Technology, 1983. [HS86] J. Roger Hindley and Jonathan P. Seldin. Introduction to Combinators and the -Calculus. London Mathematical Society, 1986. [Hud89] Paul Hudak. Conception, evolution, and application of functional programming languages. ACM Computing Surveys, 21(3):359{411, 1989. [Hug89] John Hughes. Why functional programming matters. The Computer Journal, 32(2):98{107, April 1989. [Jac89] Jonathan Jacky. Programmed for disaster: Software errors that imperil lives. The Sciences, pages 22{27, September/October 1989. [Jon90] C.B. Jones. Systematic Software Development Using VDM. International Series in Computer Science. Prentice-Hall, Second edition, 1990. [Jon92] Richard Jones. Tail recursion without space leaks. Journal of Functional Programming, 2(1):73{79, January 1992. [Jon94] Mark P. Jones. ML typing, explicit polymorphism and quali ed types. In Theoretical Aspects of Computer Software 94, 1994. [JS90] C.B. Jones and R. Shaw, editors. Case Studies in Systematic Software Development. International Series in Computer Science. Prentice-Hall, 1990. [Kah93] Stefan Kahrs. Mistakes and ambiguities in the de nition of Standard ML. Technical Report ECS-LFCS-93-257, Laboratory for Foundations of Computer Science, University of Edinburgh, April 1993. [KH89] Richard Kelsey and Paul Hudak. Realistic compilation by program transformation. In Proceedings of the Sixteenth ACM Symposium on Principles of Programming Languages, pages 281{292, 1989. [KM89] P.C. Kanellakis and J.C. Mitchell. Polymorphic uni cation and ML typing. In 16th ACM Symposium on Principles of Programming Languages, pages 105{115, 1989. [KMM91] P.C. Kanellakis, H.G. Mairson, and J.C. Mitchell. Uni cation and ML type reconstruction. In Computational Logic, Essays in Honor of Alan Robinson, pages 444{478. MIT Press, 1991. [KTU90] A. J. Kfoury, J. Tiuryn, and P. Urzyczyn. ML typability is Dexptime-complete. In Proc. 15th Colloq. on Trees in Algebra and Programming, pages 206{220. Springer LNCS 431, 1990. To appear in J. Assoc. Comput. Machinery under the title, \An Analysis of ML Typability". [KTU93] A. J. Kfoury, J. Tiuryn, and P. Urzyczyn. Type reconstruction in the presence of polymorphic recursion. ACM Transactions on Programming Languages and Systems, 15(2):290{311, 1993. [Lee93] Peter Lee. Proceedings of the ACM Sigplan Workshop on ML and its Applications. Technical Report CMU-CS-93-105, School of Computer Science, Carnegie Mellon University, 1993. [LG86] Barbara Liskov and John Guttag. Abstraction and Speci cation in Program Development. The MIT Electrical Engineering and Computer Science Series. McGraw-Hill, 1986. [LW91] X. Leroy and P. Weis. Polymorphic type inference and assignment. In 18th ACM Symposium on Principles of Programming Languages, pages 291{302, 1991. [Mac84] David B. MacQueen. Modules for Standard ML. In Proc. 1984 ACM Conf. on LISP and Functional Programming, pages 198{207, New York, 1984. ACM Press. [Mac85] David B. MacQueen. Modules for Standard ML. Polymorphism, 2(2), 1985. 35 pages. An earlier version appeared in Proc. 1984 ACM Conf. on Lisp and Functional Programming.

101 [Mac86]

David B. MacQueen. Using dependent types to express modular structure. In Proceedings of the 13th ACM Symposium on Principles of Programming Languages, pages 277{286, 1986. [Mac88] David B. MacQueen. The implementation of Standard ML modules. In ACM Conf. on Lisp and Functional Programming, pages 212{23, New York, 1988. ACM Press. [Mac90] David B. MacQueen. A higher-order type system for functional programming. In Research Topics in Functional Programming, pages 353{68, Reading, MA, 1990. Addison-Wesley. [Mar86] Johannes J. Martin. Data Types and Data Structures. International Series in Computer Science. Prentice-Hall, 1986. [Mat89] David C. J. Matthews. Papers on Poly/ML. Technical Report T.R. No. 161, Computer Laboratory, University of Cambridge, February 1989. [Mat91] David C. J. Matthews. A distributed concurrent implementation of Standard ML. In EurOpen Autumn 1991 Conference, 1991. [MCP93] Colin Myers, Chris Clack, and Ellen Poon. Programming with Standard ML. Prentice-Hall, 1993. [MH88] John C. Mitchell and Robert Harper. The essence of ML. In Fifteenth ACM Symp. on Principles of Programming Languages, pages 28{46, New York, 1988. ACM Press. [Mil78] Robin Milner. A theory of type polymorphism in programming languages. Journal of Computer and System Science, 17(3):348{375, 1978. [Mil83] Robin Milner. How ML evolved. Polymorphism|The ML/LCF/Hope Newsletter, 1(1), 1983. [Mil87] Robin Milner. Is computing an experimental science? Journal of Information Technology, 2(2):58{66, 1987. [MMM91] J.C. Mitchell, S. Meldal, and N. Madhav. An extension of Standard ML modules with subtyping and inheritance. In Proc. 18th ACM Symp. on Principles of Programming Languages, pages 270{278, January 1991. [MNV73] Zohar Manna, Stephen Ness, and Jean Vuillemin. Inductive methods for proving properties of programs. Communications of the ACM, 16(8):491{502, August 1973. [Mor90] C. Morgan. Programming from Speci cations. International Series in Computer Science. Prentice-Hall, 1990. [Mor91] J. Gregory Morrisett. Running your continuation threads in parallel. In Proceedings of the Third International Workshop on Standard ML, Pittsburgh, PA, September 1991. [Mot83] Joe L. Mott et al. Discrete Mathematics for Computer Scientists. Reston Publishing Company, Inc., 1983. [MS87] Carroll Morgan and J.W. Sanders. Laws of the logical calculi. Technical Report PRG78, Programming Research Group, Oxford, Oxford University Computing Laboratory, September 1987. [MT91a] Robin Milner and Mads Tofte. Co-induction in relational semantics. Theoretical Computer Science, 87(1):209{220, September 1991. [MT91b] Robin Milner and Mads Tofte. Commentary on Standard ML. The MIT Press, 1991. [MTH90] Robin Milner, Mads Tofte, and Robert Harper. The De nition of Standard ML. The MIT Press, 1990. [Myc84] Alan Mycroft. Polymorphic type schemes and recursive de nitions. In M. Paul and B. Robinet, editors, Proceedings of the Sixth International Symposium on Programming, Toulouse, pages 217{228. Springer-Verlag LNCS 167, April 1984. [Oho89] A. Ohori. A simple semantics for ML polymorphism. In Functional Prog. and Computer Architecture, pages 281{292, 1989. [Pau92] Larry Paulson. ML for the Working Programmer. Cambridge University Press, second, paperback edition, 1992.

102 [Per91] [Pie91] [Pit90] [PJL92] [PMN88] [RB88] [Rea89] [Rep89] [Rep91] [RG91a] [RG91b] [Rob65] [San86] [San88] [San89] [Sch92] [Sok91] [Spi88] [Spi92] [SS90] [ST88]

Nigel Perry. An extended type system supporting polymorphism, abstract data types, overloading and inference. In Proceedings of the Fifteenth Australian Computer Science Conference, 1991. Benjamin C. Pierce. Basic Category Theory for Computer Scientists. Foundations of Computing. The MIT Press, 1991. Andrew M. Pitts. Evaluation logic. In G. Birtwistle, editor, Proceedings of the IVth Higher Order Workshop, pages 162{189. Springer-Verlag, 1990. Simon L. Peyton-Jones and David Lester. Implementing Functional Languages: A Tutorial. International Series in Computer Science. Prentice-Hall, 1992. C.G. Ponder, P.C. McGeer, and A.P.-C. Ng. Are applicative languages inecient? SIGPLAN Notices, 23(6):135{139, 1988. D.E. Rydeheard and R.M. Burstall. Computational Category Theory. International Series in Computer Science. Prentice-Hall, 1988. Chris Reade. Elements of Functional Programming. Addison-Wesley, 1989. J. H. Reppy. First-class synchronous operations in Standard ML. Technical Report TR 89-1068, Dept. of Computer Science, Cornell University, 1989. J. H. Reppy. CML: A higher-order concurrent language. In ACM SIGPLAN '91 Conference on Programming Language Design and Implementation, SIGPLAN Notices 26(6), pages 294{305, 1991. John H. Reppy and Emden R. Gansner. eXene: A multi-threaded X window system toolkit. Technical report, Department of Computer Science, Cornell University, Ithaca, NY 14853, 1991. In preparation, email jhr at research.att.com. John H. Reppy and Emden R. Gansner. The eXene Manual. Department of Computer Science, Cornell University, Ithaca, NY 14853, February 1991. J.A. Robinson. A machine oriented logic based on the resolution principle. JACM, 12(1):23{ 41, 1965. Don Sannella. Formal speci cation of ML programs. Technical Report ECS-LFCS-85-15, Laboratory for Foundations of Computer Science, University of Edinburgh, November 1986. Revised November 1988. Don Sannella. A survey of formal software development methods. Technical Report ECSLFCS-88-56, Laboratory for Foundations of Computer Science, University of Edinburgh, July 1988. Don Sannella. Formal program development in Extended ML for the working programmer. Technical Report ECS-LFCS-89-102, Laboratory for Foundations of Computer Science, University of Edinburgh, December 1989. Oliver Schoett. Two impossibility theorems on behaviour speci cation of abstract data types. Acta Informatica, 29(6/7):596{621, November 1992. S. Sokolowski. Applicative High-Order Programming: The Standard ML Perspective. Chapman and Hall, 1991. J.M. Spivey. Understanding Z: A Speci cation Language and its Formal Semantics, volume 3 of Cambridge Tracts in Theoretical Computer Science. Cambridge University Press, 1988. J.M. Spivey. The Z Notation: A Reference Manual. International Series in Computer Science. Prentice-Hall, second edition, 1992. Harald Sndergaard and Peter Sestoft. Referential transparency, de niteness and unfoldability. Acta Informatica, 27:505{517, 1990. Don Sannella and Andrzej Tarlecki. Algebraic speci cations in theory and practice. Technical report, Laboratory for Foundations of Computer Science, University of Edinburgh, 1988.

103 [ST91] [Sta90] [Sta92a] [Sta92b] [Sto82] [Suf82] [SW74] [SW83] [TA90] [Tho92] [TLA92] [Tof88] [Tof89] [Tof90] [Tof92] [Tur81] [Tur82] [Tur91] [Wad92] [Wan84] [Wan86] [Wan87]

Don Sannella and Andrzej Tarlecki. Extended ML: Past, present and future. Technical Report ECS-LFCS-91-138, Laboratory for Foundations of Computer Science, University of Edinburgh, December 1991. Ryan Stansifer. Imperative versus functional. SIGPLAN Notices, 25(4):69{72, April 1990. Ryan Stansifer. The calculation of Easter. SIGPLAN Notices, 27(12):61{65, December 1992. Ryan Stansifer. An ML Primer. Prentice-Hall, 1992. J. Stoy. Some mathematical aspects of functional programming. In J. Darlington, P. Henderson, and D.A. Turner, editors, Functional Programming and its Applications. Cambridge University Press, 1982. Bernard Sufrin. Formal speci cation of a display-oriented text editor. Science of Computer Programming, 1(2):157{202, May 1982. C. Strachey and C.P. Wadsworth. Continuations: A mathematical semantics for handling full jumps. Technical Report PRG-11, Oxford University Computing Laboratory, 1974. D. Sannella and M. Wirsing. A kernel language for algebraic speci cation and implementation. In International Conference on Foundations of Computation Theory. Springer-Verlag, 1983. Lecture Notes in Computer Science, Vol. 158. Andrew P. Tolmach and Andrew W. Appel. Debugging Standard ML without reverse engineering. Technical Report CS-TR-253-90, Princeton University, Department of Computer Science, 1990. Simon Thompson. Type Theory and Functional Programming. Addison-Wesley, 1992. David Tarditi, Peter Lee, and Anurag Acharya. No assembly required: Compiling Standard ML to C. ACM Letters on Programming Languages and Systems, 1(2):161{177, June 1992. Mads Tofte. Operational Semantics and Polymorphic Type Inference. PhD thesis, University of Edinburgh, 1988. Also published as Technical Report CST-52-88, Department of Computer Science. Mads Tofte. Four lectures on Standard ML. Technical Report ECS-LFCS-89-73, Laboratory for Foundations of Computer Science, University of Edinburgh, 1989. Mads Tofte. Type inference for polymorphic references. Information and Computation, 89:1{34, 1990. Mads Tofte. Principal signatures for higher-order program modules. In The 19th Annual ACM Symposium on Principles of Programming Languages, Albuquerque, New Mexico, pages 189{199, January 1992. D.A. Turner. The semantic elegance of applicative languages. In ACM 1981 Conference on Functional Programming Languages and Computer Architecture, pages 85{92, Portsmouth, New Hampshire, October 1981. D. A. Turner. Recursion equations as a programming language. In J. Darlington, P. Henderson, and D.A. Turner, editors, Functional Programming and its Applications. Cambridge University Press, 1982. Raymond Turner. Constructive Foundations for Functional Languages. McGraw-Hill, 1991. Philip Wadler. The essence of functional programming. In Proceedings of the Nineteenth ACM Symposium on Principles of Programming Languages, pages 1{14, 1992. Mitchell Wand. A types-as-sets semantics for Milner-style polymorphism. In Proc. 11th ACM Symp. on Principles of Programming Languages, pages 158{164, January 1984. Mitchell Wand. Finding the source of type errors. In Proc. 13th ACM Symp. on Principles of Programming Languages, pages 38{43, January 1986. Mitchell Wand. A simple algorithm and proof for type inference. Fundamenta Informaticae, 10(2):115{121, June 1987.

104 [WF92] [Wik87] [Wri92] [Wri93]

Andrew K. Wright and Mathias Felleisen. A syntactic approach to type soundness. Technical Report TR91-160, Rice University, 1992.  Ake Wikstrom. Functional Programming using Standard ML. International Series in Computer Science. Prentice-Hall, 1987. Andrew K. Wright. Typing references by e ect inference. In B. Krieg-Bruckner, editor, ESOP '92: Proceedings of the 4th European Symposium on Programming, pages 473{491. Springer-Verlag LNCS 582, February 1992. Andrew K. Wright. Polymorphism for imperative languages without imperative types. Technical Report TR93-200, Rice University, February 1993.

Index

Tree, 89, 90 addfour, 21 addset, 74 addtwo, 21 alwaysone, 28 append, 52 array, 72 ascending, 84, 85 badthird, 38 bad, 38 bool, 12, 34 cat, 73 celsius, 33 close_in, 73 close_out, 73 colour, 34 compose, 23, 37 cons, 64 convert, 33 curry, 22 day, 13 delayed, 62, 63 delay, 63 descending, 84, 85 dropwhile, 56, 57 drop, 56, 57, 86 elem, 88, 89, 91 emptyset, 74 empty, 77, 80, 81, 89, 90, 92 equal, 75

"a, 55, 56, 58, 74, 75 'a, 34, 56, 68 Badint, 48 Badstring, 48 Div, 48 Elements, 89{92 Empty, 77, 80, 81 FIX, 30 Hd, 52 InPO, 91, 92 MakeTree, 92 Match, 49 PO, 89, 90, 92 Pop, 77, 80, 81 Pushdown, 78 Push, 77, 81 Retrieve, 56 SIG', 93, 96 SIG0', 94 SIG1', 94, 95 SIG2', 95 SIGj', 95 SIGn', 94 STACK, 80, 81 Stack, 77, 78, 80, 81 Subscript, 72 TREE, 89{91 Tl, 52 TooSmall, 47 Top, 77, 80, 81 105

106 Index

eval, 22 even, 27 e, 66 facbody, 30 fac, 20, 30 fahrenheit, 33 fastrev, 71, 72 b, 73 lter, 57, 58 nished, 19

atten, 60 foldl, 60 fold, 60, 72 force, 63 fourtimes, 21 from, 64 fst, 36 fusc, 25, 70 gcd, 27, 48 hd, 52 head, 64 heterogeneous, 35 hundreds, 65 id, 22 ifusc, 70 incn, 68 inorder, 54 input, 73 insertset, 74 insert, 58, 60, 89{92 instream, 73 intersection, 34 inttree, 34 int, 12 iseven, 28 iter', 23 iter, 21, 23, 25 l', 84, 86 lcons, 64 length', 60 length, 35, 52, 57, 58, 60, 84{86 le, 88, 91 listid, 60 listrev, 60

list, 51 lookahead, 73 loop, 36 makestring, 39 map_f, 59 map, 41, 59, 60 maxdepth, 50 max, 50 memberset, 74 member, 55 memo, 73 mf, 73 mindepth, 50 mod, 27 nasty, 38 nats, 64 next, 65 nil, 51 nodes, 50 nth, 86 nullset, 74 null, 41, 56 odd, 27 one", 67 one', 67, 69 oneszeroes, 64 ones, 64 one, 67 open_in, 73 open_out, 73 ordered, 90, 92 output, 73 outstream, 73 o, 23 pair, 36 paren', 36 paren, 36 perm, 58 pop, 77, 80, 81 postorder, 54 pre x, 58 preorder, 54 present, 89{92 push, 77, 80, 81

Index 107

rap, 71 real, 12 reduce, 18, 20 reforest, 54 ref, 67, 69 retrieve, 56 rev, 52, 53, 58, 59, 71, 84, 85 rotates, 86, 87 sequence, 89 set, 34, 74 size, 48 snd, 36 sort', 60 sort, 58, 60, 89 sqrt, 15 square, 39 sq, 17{19 stack, 77, 80 std_in, 73 std_out, 73 string, 73 subset, 75 sub, 72 succ, 13 sum', 15, 16, 18, 19, 24 sum, 15, 16, 25 tail, 64 takewhile, 56, 57, 86 take, 56, 57, 85, 86 tens, 65 tentimes, 65 third, 38 tl, 52 top, 77, 80, 81 tree, 35, 50, 89, 90, 92 trmap, 60 trrev, 53 trsum', 25 trsum, 25 ttree, 35 twice, 21 uncurry, 22 union, 34 unit, 62, 68, 73

update, 72 xgreater, 27 zc, 14 zeller, 14 zeroes, 65 accumulation parameter, 25 Anderson, Stuart, 58 anti-symmetric, 88 Appel, Andrew, 47, 77 assignment, 67 bitonic, 87 Burstall, Rod, 50 call-by-name, 62 call-by-value, 61 Church Rosser Theorems First Theorem, 62 Second Theorem, 62 composition, 23 in diagrammatic order, 23 in functional order, 23 constructors, 34, 47 nullary, 35, 49 curried functions, 20 Curry, Haskell B., 20 Damas, Luis, 40 declare-before-use rule, 26 dereferencing, 67 Dijkstra, Edsger, 25, 69 environment, 67 equality types, 55, 85 Euclid, 26 exception, 47 expressions expansive, 71 non-expansive, 71 Extended ML keywords axiom, 83{90, 92 exists, 84, 86, 87 forall, 84{90, 92 implies, 84, 86{88

108 Index factorial function, 20 function bonacci, 73 integer maximum, 50 functionals, 18 functions composition of, 23 curried, 20 factorial, 20 higher-order, 18 homogeneous, 36 idempotent, 21 identity, 19{21 polymorphic, 35 strict, 27 successor, 13, 21 Hayes, Ian, 83 Henglein, Fritz, 39 higher-order function, 18 Hindley, Roger, 32 Hoare, C.A.R., 58 Hughes, John, 2 idempotent functions, 21 identity function, 19{21 induction, 16 structural, 50 interchange law, 52 involution, 53 Jones, Cli , 83 leaf, 50 linear recursion, 24 MacQueen, David, 47, 71, 77 memoisation, 72 Milner, Robin, 7, 32, 40, 61 Mitchell, Kevin, 72 ML keywords abstype, 74, 75 andalso, 58, 75, 85, 86, 88, 90 and, 26, 27, 48, 66, 69, 72, 73, 77, 80, 81

as, 58 case, 73 datatype, 34, 35, 47, 64, 67, 75, 77, 78, 81, 89, 90, 92

do, 69, 72, 73 else, 41, 56{58, 66, 69, 84, 91 end, 14, 19, 25, 32, 58{60, 68{70,

72{75, 77{81, 84, 88{92 exception, 47{49, 52, 56, 77, 78, 80, 81 fn, 13{15, 17, 19{25, 27{30, 32{39, 41, 49, 60, 62, 63, 70, 71, 85 fun, 49, 50, 52, 55{60, 63{67, 69, 71{ 77, 89, 90, 92 handle, 47{49 if, 41, 56{58, 66, 69, 84, 91 in, 14, 19, 25, 27, 32, 58{60, 68{70, 72, 73, 75, 84, 90, 92 let, 19, 25, 27, 32, 58{60, 68{70, 72, 73, 84 local, 14, 19, 27, 75, 90, 92 of, 34, 35, 47{49, 64, 67, 73{75, 77, 81, 89, 90, 92 op, 20, 23, 37, 52, 60, 89, 90 orelse, 55, 74, 75, 89 raise, 47, 49, 52, 56, 77 rec, 15, 17, 19, 21, 24, 25, 27, 28, 30, 34{36, 38, 41 then, 41, 56{58, 66, 69, 84, 91 type, 33, 34, 62, 75, 80, 81, 89, 90 val, 13{15, 17, 19{25, 28, 30, 32, 33, 35{39, 41, 49, 63, 64, 66{75, 77, 81, 88{92 while, 69, 72, 73 with, 74, 75 ML modules keywords eqtype, 88 functor, 79, 92{96 include, 91, 92 open, 78, 79, 89, 90, 92 sharing, 91, 92 signature, 80, 81, 88, 89 sig, 80, 81, 88, 89, 91, 92 structure , 77, 78, 80, 81, 89, 90, 92

Index 109

struct, 77{80, 89, 90, 92 mutual recursion, 26 non-linear recursion, 25 nullary, 49 nullary constructors, 35 operators overloaded, 39 overloading, 39 parameter accumulation, 25 partial order, 88 path, 50 pattern matching, 13, 38 perfectly balanced, 50 polymorphic, 35 records, 33 recursion linear, 24 mutual, 26 non-linear, 25 tail, 24 references, 67 referential transparency, 67 re exive, 88 Russell, Bertrand, 32 Sannella, Don, 5, 84, 93 singleton, 57 sorting insertion sort, 57 state, 67 statically typed, 31 Stoy, Joseph, 20 strictness, 27 strongly typed, 31 subtyping, 38 successor function, 13, 21 tail recursion, 24 Tarlecki, Andrzej, 5 testing, 16

Tofte, Mads, 37, 71 transitive, 88 traversal inorder, 54 postorder, 54 preorder, 54 traversal strategies, 54 type inference, 31 type variables, 34 applicative, 71 imperative, 71