Open navigation menu
Close suggestions
Search
Search
en
Change Language
Upload
Sign in
Sign in
Download free for days
100%
(2)
100% found this document useful (2 votes)
654 views
Elements of ML Programming
Uploaded by
Angel Ernesto Petunchi
AI-enhanced title
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
Download now
Download
Save Elements of Ml Programming For Later
Download
Save
Save Elements of Ml Programming For Later
100%
100% found this document useful, undefined
0%
, undefined
Embed
Share
Print
Report
100%
(2)
100% found this document useful (2 votes)
654 views
Elements of ML Programming
Uploaded by
Angel Ernesto Petunchi
AI-enhanced title
Copyright
© © All Rights Reserved
Available Formats
Download as PDF or read online on Scribd
Download now
Download
Save Elements of Ml Programming For Later
Carousel Previous
Carousel Next
Save
Save Elements of Ml Programming For Later
100%
100% found this document useful, undefined
0%
, undefined
Embed
Share
Print
Report
Download now
Download
You are on page 1
/ 399
Search
Fullscreen
ML97 EDITION ELEMENTS OF ML PROGRAMMING JEFFREY D, ULLMANElements of ML Programming ML97 Edition Jeffrey D. Ullman == An Alan R. Apt Book ast Prentice Hall Upper Saddle River, New Jersey 07458Library of Congress Cataloging Ullman, Jeffrey D. Elements of ML programming/ Jeffrey D. Ullman. ML97 Edition Pp. cm “An Alan R. Apt. Book” Includes bibliological references and index. ISBN: 0-13-790387-1 1. ML (Computer program language). 1. Title. CIP DATA AVAILABLE Acquisitions editor: ALAN R. APT Editor-in-chief: MARCIA HORTON Production editor: IRWIN ZUCKER Managing editor: BAYANI MENDOZA DE LEON Director of production and manufacturing: DAVID W. RICCARDI Cover director: JAYNE CONTE Manufacturing buyer: JULIA MEEHAN Editorial assistant: TONT CHAVEZ © 1998, 1994 by Prentice-Hall Inc. El Upper Saddle River, New Jersey 07458 reproduced, in any form or by any means, without permission in writing from the publisher. ‘The author and publisher of this book have used their best efforts in preparing this book. These efforts include the development. research. and testing of the theories and programs to determine their effectiveness. The author and Printed in the United States of America 098765 ISBN QO-13-790387-1 Prentice-Hall International (UK) Limited, London Prentice-Hall of Australia Pty. Limited, Sydney Prentice-Hall Canada Inc.. Toronto Prentice-Hall Hispanoamericana. S.A.. Mexico City Prentice-Hall of India Private Limited. New Delhi Prentice-Hall of Japan. Inc... Tokyo Pearson Education Asia Pte. Ltd.. Singapore Editora Prentice-Hall do Brasil, Ltda.. Rio de JaneiroPreface I became interested in ML programming when I taught CS1U9, the introduc- tory Compnter Science Foundations course at Stanford, starting in 1991. MT. was used by several of the instructors of this course, including Stu Reges and Mike Cleron, to introduce concepts such as functional programming and type systems. It was also used for the practical purpose of introducing a second programming paradigm, other than the Pascal or C that students learned in the introductory programming course. Reimplementing algorithms and data structures in a significantly different language often is an aid to understanding of basic data structure and algorithm concepts. i firsi learned ML from the notes thai Reges and Cierou had written for their students. Initially, I was intrigued by the rule system, which gave me much of the power of Prolog, a language with which I had worked for several years. Yet ML did not introduce the semantic complexity that comes from the use of unification and backtracking in Prolog. However, I soon discovered other charms of ML: the type system, the use of exceptions, and the module system for creating abstract datatypes, among others. From the Reges and Cleron notes I also picked up the utility of giving the student a fast overview, stressing the most commonly used constructs rather than the complete syntax. In writing this guide to ML programming, I have thus departed from the approach found in many books on the language. As an outsider, I had the opportunity to learn the language from the standpoint of the typical program- mer. I have tried to remember how things struck me at first, the analogies I diew with conventional languages, and © coucepis that I found most useful in getting started. I hope that my selection is accurate, and that the book will facilitate the reader's transition from conventional languages to ML. The Second Edition You are reading the second edition of the book. The primary change between the first and second editions is that the second conforms to the new language standard called ML97. All major implementations of ML either have converted, or are in the process of converting, to this standard. For the few matters that are implementation-dependent, such as the choice of diagnostics, the second iiiiv PREFACE edition, like the first, follows the Standard ML of New Jersey (SML/NJ) im- plementation. SML/N4J is the work of Andrew Appel of Princeton University, David MacQueen of Lucent/Bell Labs, and their colleagues. The fo! by The following is b: y correspondence between the first and second editions. « Chapter 1 corresponds to the old Chapter 0. « Chapter 2 lays the groundwork for ML programming. Sections 2.1 through 2.4 correspond to the old Chapters 1 through 4, respectively. © Chapter 3 introduces functions in ML. The old Chapter 5 is now Sections 3.1 and 3.2, while the old Chapter 6 appears in Sections 3.3 and 3.6. Old Chapter 7 has become Section 3.4, while Sections 3.5 and much of Section 3.6 are new. © Chapter 4 covers ML mput and output. ‘Lhe old Chapter Y has been split among Sections 4.1, 4.2, and 4.4, while the new Section 4.3 comes from the old Chapter 22. © in Chapier 5 we reiurn io ide subject of funciious in ML, presenting a number of advanced topics. Section 5.1 covers matches and patterns like the old Chapter 19. Section 5.2 covers exceptions, from the old Chapters 8 and 20. Section 5.3 covers polymorphism as in the old Chapter 10. Material on higher-order functions from the old Chapters 11 and 21 is now split among Sections 5.4 throngh 5.6. ‘The case stndy in Section 5.7 was originally part of the old Chapter 22. © Chapter 6 introduces datatypes. The old Chapter 12 is split between Sections 6.1 and 6.2, while old Chapter 13 is now Sections 6.3 and 6.4. © Chapter 7 presents a number of advanced topics about data structures, Section 7.1 covers record structures, the old Chapter 18. Material on arrays, the old Chapter 16, is now in Sections 7.2 and 7.4. Old Chapter 17, about references, is in Sections 7.3 and 7.5. © Chapter 8 covers the ML module system. Old Chapter 14 is split among Sections 8.1 through 8.3, and old Chapter 15 is now in Sections 8.4 and 8.5. The case study in Section 8.6 is new. © Finally, Chapter 9 attempts to summarize the entire language. Some concepts not appearing elsewhere in the book are introduced as well. Section 9.i on infix operators, corresponds to the oid Chapter 24. Sections 9.2 and 9.3 summarize what is called the “top-level environment,” the set. of features one has in ML without asking for them explicitly. Then Section 9.4 summarizes the “standard basis,” or capabilities one can obtain if one calls for them explicitly. Some of the old Chapter 25 is spread among Sections 9.2 through 9.4, but much of these sections is new for ML97.PREFACE v Section 9.5 corresponds to the old Chapter 23 and covers some important. features found only in SML/N4, involving the creation of executable fil Section 9.6 concludes with syntax diagrams for the entire language and is Features of the Book The test of a language is not the hest or most. succinct. examples of its use. Rather, a language will only be adopted widely if it can handle everyday pro- gramming chores well. Thus, I have considered in this book many of the most common data structures, such as trees and hash tables, and many of the most common algorithms, such as sorting or Gaussian climination. I think the reader will be impressed by how well ML handles these standard tasks that were se- lected because of their ubiquity, not because they exhibit special features of the language. To focus the reader's attention, I have inserted boxes at various places in the text. These boxes are interruptions from the main focus of the text, but the other hand, footnotes are also interruptions to the main thread, but they are there only “for the record” rather than as an aid to understanding. Exe: re. Most of the sections have exercises at the end. In the text, we indicate that an exercise or part of an exercise has a published solution by preceding the exercise or part of an exercise by a star. You can find solutions to exercises with stars at URL http://www-db.stanford.edu/“ullman/emlpsols/sols.html. Exercises are graded by difficulty. Harder exercises are indicated by an exclamation point in the margin, and a few of the hardest exercises have two exclamation points. Use of the Book The book as a whole is a tutorial and reference for the person who wants to program productively in ML. Although there are occasional references to 4 conventional languages like C ur Pascal, I believe the buok is suffici contained that it could be used to teach ML as a first programming language. When we teach students ML in the CS109 course at Stanford, the material covered corresponds closely to what is in Chapters 2, 3, 4.1, 5, 6, and 7. The book can be used as a supplement to a programming language concepts course, in which case Chapter 8 would surely be included.at TABLE OF CONTENTS Acknowledgments I would like to thank Andrew Appel and David MacQueen, both of whom filly critiqued the original edition. Matthias Blume was equally a boon for the second edition. They are all three tied for the title of “world’s greatest referee.” I value a number of important pointers on ML from John Mitchell. Also, Henry Bauer, Richard LeBlanc, Peter Robinson, and Jean Scholtz have my appreciation for their work as referees of the first-edition manuscript. Errata from the first edition were found and pointed out to me by Baoquan Chen, Franklin Chen, Martin Brwig, Mark Girod, Naomichi Komuro, Hugh Finally, I appreciate the help from several members of the core ML commu- nity that kept me informed of changes and encouraged me to keep this book on track. I even got help on the design of the cover for the first edition (see the following nove), which has Deen carried over to the second edition as well. Cover Art Special thanks go to Luca Cardelli, who volunteered to create original art for this book. The result is on the cover. Supplementary Material on the Web The book’s home page is ww-db.stanford.edu/“ullman/emlp.html. There you can find: 1. Solutions to starred exercises. . Code for the major programs in the book. . Errata. Fen Notes and exams from Stanford’s CS109 involving ML. es . Links to ML documentation and resources. 1.D.U. Stanford CA September, 1997Table of Contents 1 A Perspective on ML and SML/NJ 1 11 Why ML? ... 0... 2 ee eee bene sees 1,2 Standard ML of New Jersey 1.3. Prerequisites for the Reader 1.4. References and Web Resources 1,5 Features of ML97............ 2 Getting Started in ML 2.1 Expressions ........ 2.1.1 Constants : 2.1.2 Arithmetic Operators 2.1.3 String Operators 2.14 Comparison Operators 2.1.5 Combining Logical Values 2.1.6 IfThen-Elsc Expressions... .. . . bene 2.17 Exercises for Section 21........... 2.2 Type Consistency 22.1 Type Errors... 2... ee eee 2.2.2 Coercion Between Integers and Reals . . . . 2.2.2 Coercions Retween Characters and Integers . 2.2.4 Coercions Between Strings and Characters 2.2.5 Exercises for Section 2.2... . 2.3. Variables and Environments . Identifiers... . . tees ‘Lhe Top-Level Environment . . An Assignment-Like Statement. . A View of ML Programming 2.3.9 Exercises for Section 2.3 2.4 Tuples and Lists sees a 2.4.2 Accessing Components of Tuples 243 Lists. ..........00% : 2.4.4 List Notation and Operatorsviii TABLE OF CONTENTS 2.4.5 Converting Between Character Strings aud Lists... . . 40 2.4.6 Introduction to the ML Type System - 41 24.7 Exercises for Section 2.4..........4 42 3 Defining Functions 45 3.1 It’s Easy; It’s fun. : 45 3.1.1 Function Types . . see 46 3.1.2 Declaring Function Types .: 7 3.1.3. Function Application... . . 49 3.14 Functions With More Than One Parameter... 50 3.1.5 Functions that Reference External Variables . . 52 3.1.6 Exercises for Seciion 3. 3.2. Recursive Functions 3.2.1 Function Execution 3.2.2 Nonlinear Recursion . 3.2.3 Mutual Recursion 3.2.4 How ML Deduces Types - 3.2.5 Exercises for Section 3.2 . 3.3 Patterns in Function Definitions 3.3.1 Patterns as Function Parameters “As” You Like it: Having it Both Ways ‘ Anonymous Variables... . + What Is and What Isn’t a Pattern? How ML Matches Patterns A Subtle Pattern Bug ... . Exercises for Section 3.3 3.4 Local Environments Using let... 0.0 eee eee 3.4.1 Defining Common Subexpressions ......-.-. 34.2. Effect on Environments of let 3.4.3 Splitting Apart the Valuc Returned by a Function .... 80 3.44 Mergesort: An Efficient, Recursive Sorter... .. 2... 3.4.5 Exercises for Section 3.4 3.5. Case Study: Lincar-Time Reverse... 0. eee eee 3.5.1 Analysis of Simple Reverse... 0... « 3.5.2. ML’s Representation of Lists Ls 3.5.3 A Reversal Function Using Difference Lists . . - 3.5.4 Analysis of Fast Reverse . 3.5.5 Exercises for Section 3.5. . . 3.6 Case Study: Polynomial Multiplication i Representing Poiynomiais by List = A Simple Polynomial-Multiplication Algorithm . . Analysis of Simple Multiplication . . . . Auxiliary Functions for a Faster Multiplication . . . ‘The Karatsuba-Ofman Algorithm... . Analysis of the Karatsuba-Ofman AlgorithmTABLE OF CONTENTS ix 3.6.7 Exercises for Section 3.6... 6.2.0 eee 98 4 Input and Output 41 Simple Ontpnt 4.1.1 The Print Function... .... 4.1.2 Printing Nonstring Values . . . 4.1.3 “Statement” Lists ....... = 4.14 Statement Lists Versus Let Expres ions 4.1.5 Exercises for Section 4.1... 4.2 Reading Input Froma File ...... Instreams Reading Characters From a File... 2.2 Reading Lines of aFile ........... Reading Complete Files... . Reading a Single Character . . .2.6 Lookahead on the Input .. . . 4.2.7 Closing Instreams 4.2.8 Exercises for Section 4.2... . 4.3 Output to Files . 43.1 Outstreams 4.3.2 Closing Outstreams 2... 4.3.3 The output Command 43.4 Exercises for Section 4.3 44 Case Study: Sununing Integers... eee 44.1 The Function startInt .............-.00005 44.2 The Function finishInt 44.3 The Function getInt 2... 2... ee ee 444 The Function sumInts. 0.2.00... eve eee 4.4.5 Eager Evaluation 4.4.6 Exercises for Section 4.4... 0... 0. eee ees 5 More About Functions 5.1 Matches and Patterns 2... 0... 0.0000 eee BLL Matches... oe cee eee 5.1.2 Using Matches to Define Functions . . . . 5.1.3 Anonymous Functions ..... 0.2... 5.14 Case Expressions 2.0.0... 0200 - 5.1.5 If-Then-Else Expressions Revisited 5.1.6 Exercises for Section 5.1.......-. Exceptions 5.21 User-Defined Exceptions . 5.2.2 Expressions With Parameters 5.2.3 Handling Exceptions... . . 5.2.4 Exceptions as Elements of an Em eee . 5.2.5 Local Exceptions o R5.3 5.5 56 5.7 Defining Your Own Types 61 6.2 6.3 TABLE OF CONTENTS 5.2.6 Exercises for Section 5.2... . . Polymorphic Functions... . . 5.3.1 A Limitation on the Use of Polymorphic Funetions . . . . 145 5.3.2 Operators that Restrict Px wee —= 5.3.3 Operators that Allow Polymorphism . 5.34 The Equality Operators .. . . 5.3.5 Exercises for Section 5.3... . Highes-Order Functious oo 1 5.4.1 Some Common Higher-Order Functions. . 5.4.2 A Simple Map Function 5.4.3 The Function reduce 2.0... 0... e eee eee 5.4.4 Converting Intix Uperators to Function Names ...... 165 5.4.5 The Function Filter 5.4.6 Exercises for Section 5.4... . « Curried Functions 2... 2.0.00. 5.5.1 Partially Instantiated Functions 5.5.2 The ML Style of Function Appli 5.5.3 Exercises for Section 5.5... 0.2.0... . Built-In Higher-Order Functions 5.6.1 Composition of Functions .......... 5.6.2 The ML Operator o For Composition . . . 5.6.3. The “Real” Version of Map . : 5.6.4 Folding Lists . .6.5 Exercises for Section 5.6 . Case Study: Parsing Expressions . . . : 5.7.1 The Grammatical Structure of Arithmetic EBxpresons . 184 .7.2 Structure of the Parsing Program . . . . 5.7.3 Detailed Explanation of the Parser Code. = 5.7.4 Exercises for Section 5.7 . . Defining New Types 6.1.1 Review of the ML Type System. 6.2... eee 6.1.2 New Names for Old Types. . . : 6.1.3. Parametrized Type Definitions 6.14 Exercises for Section 6.1... . Datatypes... 2.2... = 6.2.1 A Simple Form of Datatype Declaration . . 6.2.2 Using Constructor Expressions in Datatype Definitions 6.2.3 Recursively Defined Datatypes 6.2.4 Mutually Recursive Datatypes 6.2.5 Exercises for Section 6.2... . Case Study: Binary Trees 6.3.1 Binary Search Trees . 6.32 Lookup in Binary Search ‘TreesTABLE OF CONTENTS xi 6.3.3 6.3.4 6.3.5 6.3.7 6.3.8 64 Case Study: General Rooted Trees GAL 6.4.2 6.4.3 6.44 7 More About ML Data Structures 7.1 Record Structures TL T12 713 714A 71S 716 x ey 7.21 7.22 7.2.3 Arrays Insertion into Binary Search Trees 2... 0 eee 214 Deletion from Binary Search Tre Some Comments About Running Time . Prowler ‘Traversals . wee Exercises for Section 6.3... . A Datatype for Trees Summing the Labels of a General ‘Iree . . . =o Computing Sums Using Higher-Order Functions 227 Exercises for Section 64... 0.000000. e eee ee 227 Records and Their Types . . . Extracting Field Values .. . . Tuples as a Special Case of Record Structures Patterns That Match Records . Shorthands in Record Patterns Exercises for Section 7.1 Why Do We Need Arrays’. . . Array Operations Exercises for Section 72.0... 00 cee eee ee (ebm RetereCeS a mee meee omem memes Seems eee ees Se 242 731 7.3.2 7.3.3 7.34 7.3.5 74 Case Study: Hash Tables 7A1 TAD 74.3 744 7.5 Case Study: Triangularization of a Matrix T51 75.2 7.5.3 8.11 8.1. 8.2 Structures... . 2... 0.000. Encapsulation and the ML Module System 8.1 Why Modules? The ref Type Constructor Obtaining the Value of a Ref-Variable .. 2.6... 21. Modifying Ref-Variables..........0.002.005 The While-Do Statement Exercises for Section 7.3.0.0... 0.00 c eee eee The Dictionary Ope: How a Hash Table Works sae see ‘An Example of Hash ‘Table Implementation... . Exercises for Section 7.4 Creating and Initializing the Matrix... . ‘Triangularization by Row Operations Exercises for Section 7.5... 0. eee eee Information Hiding... ... . Clustering Connected ElementsTABLE OF CONTENTS 8.2.1 Signatures... ee eee eee ees 262 8.2.2 Restricting Structures Through ‘Their Signatures... . . 264 8.23 Accessing Names Defined Within Structures 8.24 Opening Structures . sees 82.5 Exercises for Section 8.2 . 8.3 Functors..... ee 8.3.1 Motivation for Functors . 5. . = 83.2 Using Fuucturs w Import Information . . « 8.3.3 More General Forms for Functor Parameters and “Argu- ments 8.34 Exercises for Section 83... « 84 Sharings.... 0.2... eee 84.1 Sharing Specifications 8.4.2 Substructures . 8.4.3 Sharing of Types . 844 Sharing of Substructures | 8.4.5 Exercises for Section 8.4 . 8.5 ML Techniques for Hiding Information 8.5.1 An Information-Hiding Problem 8.5.2 Using Signatures to Hide Information 8.5.3 Abstract Types . . 8.5.4 Local Definitions . 8.5.5 Opaque Signatures = 8.5.6 Exercises for Section 8.5 . 8.6 Case Study: Feedback Shift Registers : 8.6.1 Operation of a Feedback Shift Register . . 8.6.2 A Functor to Create Random Number Generators. . 8.6.3 Generating a Feedback Shift Register 8.6.4 Exercises for Section 8.6... ......- Summary of the ML Standard Basis 9.1 The Infix Operators 9.1.1 Precedence»... 6. eee eee eee 9.1.2 Precedence Levels in ML 9.1.3 Associativity of Operators . . 9.14 Creating New Infix Operators 9.1.5 Infix Data Constructors . . . 9.1.6 Exercises for Section 9.1. . . eee 9.2 Functions in the Top-Level Environment . . . . . 9.2.1 Functions on Integers 9.2.2 Functions on Reals... .. . 9.2.3 Functions on Booleans . . . . 9.2.4 Functions on Characters . . . 9.2.5 Functions on Strings... . . 9.26 Functions on OptionsTABLE OF CONTENTS xiii 9.3 9.5, 96 9.2.7 Functions on References... . 9.2.8 Functions on Lists ae .9 Functions on Exceptions R 9.2.11 ‘Top-Level ‘ 9.3.1 Primitive Types 9.3.2 Primitive Type Constructors. 9.3.3 Primitive Datatypes 9.3.4 Top-Level Exceptions 9.3.5 Exercises for Section 9.3... . . pes and Exceptions... . Structures of the Standard Basis... 9.4.1 The Structure Int 942 The Structure Word 9.4.3 The Structures Real and Math . ¥.4.4 ‘Lhe Structure Char 9.4.5 The Structure String... . . 9.4.6 The Structure Substring . . 9.4.7 The Structure List... . . 9.4.8 The Structure Array . . 9.4.9 The Structure Vector . 9.4.10 The Structure 0S os 9.4.11 The Structures Time and Timer . 9.4.12 What If I Lose a Name? . . . 9.4.13 Exercises for Section 9.4... Additional Features of SML/NJ 9.5.1 Exporting Functions 9.5.2 Exporting the ML Environment 9.5.3 Exercises for Section 9.5.0... 0.0... eee eee Summary of MI Syntax 9.6.1 Lexical Categories... eee 9.6.2 Some Simplifications to the Grammatical Structure . 9.6.3 Expressions... 2.2... an . 9.6.4 Matches and Patterns .. . . 9.6.5 Types ...... 9.6.6 Declarations. . . 9.6.7 Signatures... . 9.6.8 Structures... . 9.6.9 Functors.... .Chapter 1 A Perspective on ML and OQnaAT /NIT SIVEL/ ING In this preliminary chapter we shall introduce the reader to the history of ML and the reasons for its existence and popularity. We shall also present the mechanics of using a particular implementation of ML, called Standard ML of New Jersey, which is the implementation used for examples in this book. Finally, some general references on the language are given, including URL's for obtaining ML compilers aud on-line documentation. 1.1. Why ML? ML is a relatively new language that has some extremely interesting features. Its designers incorporated many modern programming-language ideas, yet the language is surprisingly easy to learn and use. In this section we shall enumerate the most important of these features. A Functional Language MLis primarily a functional language, meaning that the basic mode of compu- tation is the definition and application of functions. Functions can he defined by the user as in conventional languages, by writing code for the function. But it is also possible in ML to treat functions as values and compute new functions from them with operators like function composition. Side-Effect Freedam ‘A consequence of the functional style is that computation proceeds by eval- uating expressions, not by making assignments to variables. There are ways to give expressions side-effects, which are operations that permanently change 12 CHAPTER I. A PERSPECTIVE ON ML AND SML/NJ the value of a variable or other observable object (e.g., by printing output). However, side-effects are treated as necessary aberrations on the basic theme. Tn contrast, languages like Pascal or C use statements with side-effects as a effect, since the value of variable a is changed after the assignment is executed. In contrast, when ML evaluates an expression like bt, it typically creates an entirely new element with which to associate the result. Higher-Order Functions ML supports higher-order functions — functions that take functions as argu- ments — routinely and with great generality. In comparison, languages like Pascal or C support functions as arguments only in limited ways. Polymorphism ML supports polymorphism, which is the ability of a function to take arguments of various types. For example, in Pascal or C we may have to create different mg 9 hg of pairs of integers,” and so on. We would then have to define operations like “push” and “pop” for each different type of stack. In ML, we can define one notion of a stack, one push function, and one pop function, each of which works no matter what type of elements our stacks have. Abstract Data Types ML supports abstract data types through: 1. An elegant type system, 2. The ability to construct new types, and 3. Constructs that restrict access to objects of a given type so all access is through a fixed set of operations defined for that type. ‘An example is a type like “stack,” for which we might define the push and pop operations and a few other operations as the only way the contents of a stack could be read or modified. These abstract data types, called structures, offer the power of “classes” used in object-oriented programming ianguages like C‘*, Java, or Smailitalk. ‘They are considered very important for such programming goals as modularity, encapsulation of concepts, and reusc of software. However, the ML notion of a structure also includes and generalizes several other important ideas, such as the libraries of functions provided in many languages and “friend” classes in C++1.1. WHY ML? 3 Recursion ML strongly encourages recursion in preference to iterators like for-loops or whileloops that are need commonly in Pascal or C. Reenrsion generally pro- vides a cleancr expression for computational ideas, especially when coupled with ML’s functional programming style. We shall learn a natural, recursive style of programming similar to that used in Lisp or Scheme. However, iterative constructs are available in ML for the times when that style is most appropriate. Rule-Based Programming There is in ML an easy way to do rule-based programming, where actions are based on if-then rules. The core idea is a pattern-action construct, where a value is compared with several patterns in turn. The first pattern to match causes an associated action to be executed. In this way, ML has much of the power of Prolog and other languages that are thought of as “artificial intelligence languages.” Strong Typing ML is a strongly typed language, meaning that all values and variables have a type that can be determined at “compile time” (ie., by examining the program but not running it). A value of one type cannot be given to a variable ot another type. For example, the integer value 4 cannot be the value of a real- valued variable, even hough the real 4.0 could be the value of that variable. Many other languages allow confusion of types. For example, C allows a value to change its type arbitrarily, through the “cast” mechanism, while Lisp and Prolog do not try to constrain types in general. Strong typing is a valuable debugging aid, since it allows many errors to he by th her th hen the program is run. Interestingly, although most other strongly typed languages require a declaration of the type of every variable, ML tries hard to figure out the unique type that each variable may have, and only expects a declaration for a variable when it is impossible for ML wo deduce its type. ML is not the only language to possess these features. For example, Lisp is principaily functional, supports higher-order functions, and promotes the use of recursion. Prolog also promotes recursion and supports rule-based program- ming naturally. Smalltalk and C++ offer powerful abstract-data-type facilities, and so on. However, the combination of features found in ML offers the user & great deal of programming ease. At the same time, ML allows one to use a full palette of modern programming language concepts4 CHAPTER 1. A PERSPECTIVE ON ML AND SML/NJ 1.2 Standard ML of New Jersey In this book, we shall assume that the SML/NJ, or “Standard ML of New of ML is used SML, INT wae ted hy Daw NacGucen of Lucent Bell Laboratories, Andrew Appel of Princeton University, and their colleagues. It is available for most UNIX workstations, for PC’s running LINUX, and in an experimental version (at the time of this book’s writing) for PC's running Microsoft Windows. There is a Web site at Bell Laboratories from which software and documentation may be downloaded; see the references in Section 1.4. SML/NJ version 109.30, upon which this book is based, has been implemented to conform to the recent ML97 standard. Interactive Mode To run SML/N4J in interactive mode, in response to the UNIX prompt type snl SML/NJ will respond with: Standard ML of New Jersey --- Here, as throughout this book, we shall use italic font to indicate ML’s re- sponses, while text typed by the user will be in the “teletype” font, as sml ahove. The dash on the second line is ML’s prompt. The prompt invites us to type an expression, and ML will respond with the value of that expression. We can make definitions and enter expressions indefinitely, and SML/NJ will respond to cach with the resulting value. ¢ To terminate an SML/NJ session, type
d. Direct Program Execution It is also possible to get SML/NJ to execute a program in a conventional way. For example, if your ML program is in file foo, give the sm1 command with that file as standard input: sul < foo Another option is to issue the sml command to UNIX, which gets us started in interactive mode. Then, in response to the prompt, read and cxecute a file foo that coutains an ML program. We do so by typing to ML the expression use "foo";1.3. PREREQUISITES FOR THE READER 5 Any quoted UNIX path name can appear in place of "foo". ‘his mode is handy when we are debugging a program and want to read in its definitions and then try them in interactive mode, There is a third way to get an Mi program to run using SML/NJ. One can compile an ML program source file into a file that includes the SML/NJ runtime system and thus can be executed directly without. invoking command smi. This mode of operation is discussed in Section 9.5. What ML Gives You When we invoke ML. we are given access to several resources. In MI., all available capabilities — operators like + for addition, functions like sin, and some very complex operators not present in other languages — are organized into structures, such as Int and Real, as suggested in Fig. 1.1. The entire collection of capabilities is called the standard basis Top-level SS) =] Environment } Structures Figure 1.1: Organization of resources in ML A structure in ML is akin to a library in most other languages. For example, the structure called Int contains many functions useful for dealing with inte- includes some less typical operators, Thus, ML selects the most important operators from the various structures and puts them in the top-level environment. ‘These capabil- ities are available when we invoke ML. The additional capabilities, those that are found in the various structures but that are not part of the top-level envi- ronment, are also accessible if we make a small amount of additional effort. We shall describe how to access those capabilities not in the top-level environment starting in Section 4.1.2. 1.3 Prerequisites for the Reader We assume the reader is familiar with programming in some conventional lan- guage such as Pascal or C. Occasionally, as a matter of interest, we shall6 CHAPTER 1. A PERSPECTIVE ON ML AND SML/NJ compare ML constructs with those of Pascal or C, but familiarity with one or both of these languages is not essential. It is also assumed the reader is familiar with the process of writing and debugging progr has written at least a few recursive programs and has some comfort with that style of programming. However, our first recursive examples will be covered in sufficient detail that the style may be learned here. In addition, we assume the ivader is faiuilias with simple data structures and data structure concepts such as records, pointers, lists, and trees. ‘he author immodestly recommends Foundations of Computer Science: C Edition by A. V. Aho and J. D. Ullman, Computer Science Press, New York, 1995 for the reader who desires further background on these subjects. 1.4 References and Web Resources The original definition of Standard ML is from [3], which evolved into the book [5]. An elaboration of this work is [4]. This version of ML is now given the retronym MLO0. The recent revision, called MLST, is described in ie buuk [6]. Au iapuriani part of the definition of ML97 is the standard basis, which is obtainable on-line in [1] The original paper on the Standard ML of New Jersey implementation as- sumed in this book is [2]. There is an extensive resource library available on-line. The root. URI. is: http: //cem. bell-labs . com/em/cs/vhat/smlnj A uscful on-line document is http: //cm. bel1-1abs.. com/cn/cs/what/smnj/top-level-compar ison html which explains the difference between earlier versions of SML/NJ and the cur- rent, ML97-based versions starting with Version 109.24. To obtain software to run SML/NJ on various hosts, start at http: //cm.bell-labs.com/cm/cs/what/smlnj/software. html An important source for non-UNIX implementations of ML is http: //www.dina.kvl.dk/~sestoft/mosml.html which is the Keldysh Institute of Applied Mathematics in Moscow. Their im- plementation runs on PC’s and MAC’s, as well as workstations, and largely conforms to ML97 at the time this book was written. 1. Appel, A. W., N. Barnes, D. Berry, E. R. Gansner, L. George, L. Huels- bergen, D. MacQueen, B. Monahan, C. Miller, J. H. Reppy, J. Thackray, and P. Sestoft, The Standard ML Basis Library. Its URL is:15. N + ° FEATURES OF ML97 http://cm.bel1-Labs.com/cm/cs/what/sminj/sm197.htm1 Appel, A. W. and D. B. MacQueen, “Standard ML of New Jersey,” Inter- national Symposium on Programming Languages, Implementation Logic, pp. 1-13, Springer-Verlag, 1991 is a technical article describing the SML/NGJ system. Harper, R. M., D. B. MacQueen, and R. Milner, “Standard ML,” ECS- LFCS-86-2, Laboratory for Foundations of Computer Science, Edinburgh University, Dept. of CS, 1986. Milner, R. and M. Tofte, Commentary on Standard ML, MIT Press, Cam- bridge MA, 1991. Milner, R., M. Tofte, and R. M. Harper, The Definition of Standard ML, MIT Press, Cambridge MA, 1990. . Milner, R., M. Tofte, R. M. Harper, and D. B. MacQueen, The Definition of Standard ML (Revised), MIT Press, Cambridge, MA, 1997. 1.5 Features of ML97 If you are familiar with the earlier version of ML called ML90, then you will notice certain differences between ML90 and the version ML97 covered in this book. If you are not familiar with ML90, then skip this section, The complete list of changes is found in the ML97 source book, reference [6] above. However, for the reader with ML experience, the following is an incomplete list of the changes that are most likely to affect your programming. e x . There is a cleaner organization to features that are available in the top- level basis and features that are available through a library structure. . Certain values with unknown type, such as nil, are no longer legal ex- pressions. However, polymorphic functions remain a feature ot ML. Input /ontput operators are now defined as part of the standard, rather than being implementation-dependent. . Characters are now a separate type, different from strings of length 1. . Reals are no longer an equality type; i.e, you cannot test r = s for reals rand s . There is an unsigned integer type called word. Both words and ordinary integers may be represented in hexadecimal, if we wish. . A datatype called option is provided to represent elements that are Op- tionally missing.CHAPTER 1. A PERSPECTIVE ON ML AND SML/NJ 8. Requirements to specify types are reduced because there is a default type (integer) for overloaded operators such as + or <.Chapter 2 Getting Started in ML In this chapter we shall introduce the reader to the simplest form of program- ming in ML, where one types expressions to the ML system and receives back values for these expressions. We shall learn how to construct expressions using atomic types such as integers and strings. We shall also discuss expressions in- volving lists and a simple form of record structure called tuples; lists and tuples are both basic ML constructs. The reader will also see an important difference between ME and most other languages: the ML rules regarding types of ex- pressions allow the ML compiler to check at compile time for type errors that in other languages can lead to mysterious run-time bugs 2.1 Expressions When we are in interactive mode, the simplest thing we can do is type an expression in response to the ML prompt (-). ML will respond with the value and its type. ML response. 142435 val it = 7: int Recall from Scction 1.2 our convention that we use “teletype” font for things we type and italic font for the response of the ML system. Here, we have typed the expression 1 + 2¥ 3, and ML responds that the value of variable it is 7, and that the type of this value is integer. The variable it plays a special role in ML, It receives the value of auy expression that we type in interactive mode. a Two useful points to observe from Example 2.1 are:10 CHAPTER 2, GETTING STARTED IN ML © An expression must be followed by a semicolon to tell Une ML system that the instruction is finished. If ML expects more input when a
is typed, it will respond with the prompt ‘The = sign is a warning that we have not Snished our © The response of ML to an expression is: 1. The word val standing for “value,” 2. The variable name it, which stands for the previous expression, 3. An equal sign, ¢ i * 5. A colon, which in ML is the symbol that associates a value with its type, and 6. An expression that denotes the type of the value In our example, the value of the expression is an integer, so Uhe type int follows the colon. 2.1.1 Constants As in any other langnage, expressions in MI, are composed of operators and operands, and operands may be cither variables or constants. At this point, we have not yet discussed the way values may be assigned to variables, so it does not make sense to use variables in expressions. However. syntactically. variables present no surprises. You may think of Pascal identifiers (letters followed by letters or digits) or the identifiers in your favorite language as names for ML variables, although as we shall see in Section 2.3.1, ML identifiers differ somewhat from identifiers in these languages. ML provides as part of its top-level environment (see Section 1.2) a number of types that are similar to those found in most languages. There is also a way to ert from the system some additional types. In this preliminary discussion, iuoduce only the most commonly used atomic types aud allowable values. The complete set of types is discussed in Section 9.3. Integers Integers are represented in ML as in other languages, with one exception involv- ing the minus sign. A positive integer is a string of one or more digits, such as 0, 1234, or 11111111. A negative integer is formed by placing the unary minus sign, which is the tilde (~), not a dash, in front of the digits, such as ~1234. Integers may also be represented in heradecimal notation, where the char- acters Ox or OX arc followed by a string of hexadecimal digits. Recall that the hexadecimal digits are 0 through 9 and A through F, with the letters standing for “digits” with values 10 through 15 (in decimal), respectively. The hexadecimal digits that are letters may be written in either upper or lower case.2.1. EXPRESSIONS u Example 2.2: Here are the responses of the ML system to some expressions that are hexadecimal integers. Ox1234: val it = 4660 : int Here, 1234 in hexadecimal, whose decimal value is 1x 172842 x 14443 x 1244 = 4660 is converted to decimal in ML/s response. Notice that ML gives you a deci- mal representation, regardless of whether you write the integer in decimal or hexadecimal. “Oxah; val it = 170 : int Here, we notice that either a or A stands for the hexadecimal digit “10,” and upper and lower case can be mixed. We also see that the negation symbol ~ may be used in hexadecimal integers. O Reals Reals are also represented conventionally, with the exception that minus signs within reals are represented by ~. An ML constant of type real thus consists of 1. An optional ~, 2. A string of one or more digits, and 3. One or both of the following elements: (a) A decimal point and one or more digits. (b) The letter E or e, an optional ~, and one or more digits. As in other langudges, the value of a real number is determined by taking the number that appears before the E or e and multiplying it by 10 raised to the power that is the integer that follows. Example 2.3: Here are some examples of real numbers: i. 123.0 is the negative reai that happens tu have au i 2. 3E73 has value .003. 3. 3.14e12 has value 3.14 x 10". a12 CHAPTER 2. GETTING STARTED IN ML Booleans There are two boolean values: true and false. ML is case-sensitive (unlike some other languages such as Pascal or SQL that also use true and false as boolean constants), so these constants must be written in lower case, never as ‘TRUE, False, or any other combination involving capitals. Example 2.4: Here is what happens when we type a boolean value. true; val it = true : bool Notice that the type of buvieaus is bel in tite ML respuuse. Go Strings Valucs of type otring arc double quoted character strings like "£00" or "ROI Certain special characters are represented by sequences of characters, as in the language C, where the backslash (\) serves as an escape character. The principal ways to represent characters that cannot be typed on the keyboard, or characters with a speciai meaning thai would confuse ihe inierpreiaiiun of strings, are: 1. The two-character sequence \n is used for the “newline” character 2. \t is used for the tab character. 3. \\is used for the backslash character. 4. . \" stands for the double-quote character, which otherwise would be in- terpreted as the string ender. 5. A backslash followed by three decimal digits stands for the character whose ASCII code is the number represented by those three digits, in base 10. This convention allows us to type characters for which there is no key on the keyboard. For example \007 is the “character” that rings the bell on the console. 6. Those characters that are control characters can also be written by the three character sequence consisting of a backslash, the caret or uparrow symbol *, and a character whose ASCII code is in the range 64-95 (dec- imal), i.e., the capital letters and the five characters [\]*_. The actual character represented is determined by subtracting 64 from the ASCIL code for the character typed. For example, \"G stands for
G and is the same bell-ringing charactcr that is represented by \007. ‘There are certain other escape sequences that are less commonly used or that may not be supported by a given ML implementation. See the box on “Other Character Codes.”2.1, EXPRESSIONS 13 Example 2.5; The string "A\tB\tC\n1\t2\t3\n" is printed as A B Cc 1 2 3 Here we see uses of the tab sequence \t and the newline sequence \n. If a string is too long to be written conveniently on a single line, we may continue it over several lines. We make all but the last line end with a backslash, and all but the first line begin with a backslash. over three lines as follows: "\\\" stands for the double-quote character, \ \which vthe:wise would be inlespreted \ \as the string ender." Tn the first line, the first quote is not part of the string but indicates that a string foliows. The first wo backsiashes represent the character \. The third backslash and the quote represent the character " (the second character of item 4 above). The backslash at the end of the first line indicates that the string continues on the next line. Note that the space after the comma is shown explicitly on the first line. If that space were missing, the represented string In general, © Any sequence of characters beginning and ending with the backslash and containing between the backslashes only “whitespace” characters such as blank, tab, and newline, is ignored in interpretation of strings. another backslash to make a string break over several lines without the newlines becoming part of the string Characters As in C, there is a distinction between a character string of length one and a single character. ML provides a type char for characters. The representation of character values in ML is somewhat unusual: he character # followed by a character string of length one. That is, #' represents the character ©. Example 2.7: Character a is represented by #"a", The tab character is rep- resented by #"\t". O4 CHAPTER 2. GETTING STARTED IN ML Other Character Codes MI. also provides the following escape sequences: \a. \b. \v. \f. and \r for the ASCII characters 7, 8, 11, 12, and 13, which are the bell-ringing character, backspace, vertical tab, form feed, and carriage return, respec- tively. In addition, the ML97 standard permits, but does not require, that an implementation support an extended ASCTT character set of np to 16 its, such as the 16-bit character code used in the language Java. If an im- plementation supports such an extended set (SML/NJ version 109.30 does not), then one can represent such characters hy the sequence \u followed 2.1.2 Arithmetic Operators The arithmetic operators of ML are similar to those of Pascal or C. There are: 1, The low-precedence “additive” operators: +, -. 2. The high-precedence “multiplicative” operators: *, / (division of reals), div (division of integers, rounding down toward minus infinity), and mod (the remainder of integer division). 3. The highest precedence unary minus operator, ~. However, note the following. ¢ A unary minus sign is always denoted by a tilde (~), never by a dash: Thus, we write ~3#4 and 3-4, but never 374 or -344. © ML is case-sensitive, so the operators mod and div must be written in lower case. « Associativity and precedence is like Pascal or C; higher precedence op- crators arc grouped with their operands first, and among operators of equal precedence, grouping proceeds from the left. Grouping order can be altered by parentheses in the usual manner. Example 2.8: Here are some expressions and their responses from the ML interpreter. 3.0 - 4.5 + 6.7; val it = 5.2. real Note that grouping of equal precedence operators is from the left. This expres- sion is interpreted as (3.0 — 4.5) + 6.7, not 3.0 - (4.5 + 6.7), which has value ~8.22.1, EXPRESSIONS 15 43 div (8 mod 3) * 5; val it = 105 : int All throe operators div, mod, and * are of the same precedence, hut the parentheses force us to use the mod first, then group from the left. Since mod calls for the remainder when its left argument is divided by the right, the value of 8 mod 3 is 2. We thus evaluate (43 div 2)*5, or 105. O 2.1.3 String Operators We may not apply the arithmetic operators to string operands. There is, ho The operator ge and only to sti tor stands for concatenation of strings; it has the precedence of an additive opera- tor. When we concatenate two strings s, and s2, we get the string 5152. That is, the resulting string is a copy of string s, followed by a copy of s2 Example 2.9; Here are sume examples of string concatenation. "house" * "cat"; "Linoleum" >"; val it = “linoleum” : string Notice in the second example that "" represents the empty string, the string with no characters. When we concatenate the empty string with any other string, cither on the left or right of the ~ operator, we get the other string as a result. O 2.1.4 Comparison Operators ‘he six comparison operators that we find in Pascal are also part of the ML repertoire. These are =, <, >, <=, >=, and <, representing, respectively, the comparisons =, <, >, <, >, and #. They can be used to compare integers, reals, characters, or strings, with one exception: Reals may not. be compared using = or <>. The other four comparisons of reals, such as <, are permitted, however. In the case of characters, c, < cp means “lexicographically precedes”; that is, the character code for ci is less than the character code for c2. Similarly, <= means “cquals or lexicographically precedes,” and so on. For strings, < is lexicographic order, just as < in Pascal or strcmp in C. That is, if #; and s» are strings, then 8; < 52 if either 1. sy is a proper prefix of s2, or16 CHAPTER 2. GETTING STARTED IN ML Why Can’t We Test Reals for Equality? The policy that forbids testing r = s in ML, when r and s are real quanti- ties, is motivated by the fact that all machines perform real arithmetic only approximately, Thus, in some circumstances, wo real-valued expressions that are theoretically equal could turn out, because of rounding error, to be unequal in the machine. Tf yon definitely want to test whether r = s, you can test both r < s and s
4; ML does not evaluate the second condition (3 > 4), since the first being true is sufficient to guarantee that the whole expression is true. Remember that the result of a comparison is a boolean, so it makes sense to connect two comparisons by a logical operation such as orelse. In the following expression: 1<2 andalso 3>4; val it = false : bool it is necessary to evaluate both conditions. Had the first condition been false, then there would have been no need to check the second, because the whole expression could only be false. O Recanse the symbol not. has such high precedence, we must. be careful to group its argument properly. Here is an cxample. Example 2.12: ‘he expression not 1<2 is grouped as (not 1)<2, which makes no sense and is a type error in ML. We would have to write not (1<2), although the simpler expression 1>=2 would do as well. © Incidentally, one might wonder why it matters whether or not the second operand of a logical operation is evaluated, if the result of the entire expression cannot depend on that operand. The reason is that in some special cases, an ML expression can have a side-effect, which is an action whose effect does not disappear after the expression is evaluated. The most common example of a side-effect is when something inside an expression causes information to be18 CHAPTER 2. GETTING STARTED IN ML printed or read. We have not yet seen any ML operator that has a side-effect, and indeed it is in the ML style to avoid side-effects normally. However, side- effects are possible, as we shall see in Section 4.1 and elsewhere. When they nder which part of an it is casential that we understand the conditions expression will not be evaluated aud its side-effects consequently not performed. Remember to use andalso and orelse, never and and or, for the logical operations. There is no special meaning for or in ML, but and has another meaning cntircly, having nothing to do with logical operations. 2.1.6 If-Then-Elsc Expressions ML lets us usc conditional expressions of the form if E then F else G. We compute the value of this expression by first evaluating expression E, which must have a boolean value. If that value is true, then we evaluate expression F (and never evaluate G); the value of F becomes the value of the entire if. then-else expression. If the value of E is false, then we evaluate ouly G, which becomes the value of the entire expression. Example 2.12: Consider the following conditional expression: if 4¢2 then 344 else 5+6; val it = 7; int We begin by evaluating the expression hetween the if and then. In this case, valuate the sccond 7 we evaluate the sccond expres- ¢ expression 1 < 2 evaluates to true. Thus, sion, 3+4. The result, 7, is the value of the entire expression. We do not evaluate the expression 5 + 6, and if in its place there were an expression with side-effects, those side-effects would not be executed. Here are a few important points about conditional expressions. © The conditional, or if-then-else operation, is one of the rare operations that takes mare than twa operands. There is, hawever, a similar three. operand (ternary) operator in C, using the characters ? and : in place of then and else (nothing in place of if). © Ifthen-else forms an expression. It is not a control-flow construct that groups statements together, as we find in most languages © There is no if -+- then construct in ML. Such an expression does not have a value when the condition is false. This point emphasizes the dif- ference between if-then-else as an expression form and as a control-flow construct. There is no harm in having a control-flow construct if-then, since it simply executes no statements if the condition is false. However, an if-then expression might return no value at all and thus could not be used inside larger expressions2.1. EXPRESSIONS 19 Case Sensitivity in ML ML is case-sensitive, and operators whose names are composed of letters are written with lower-case letters only. For example, we must be careful to wrile not, andalso, if, mod, and so on. ‘There might appear to be an exception concerning letters used in the expression of certain constants. For instance. we saw that either For e may be used in real constants, and hexadecimal integers can be introduced with either Ox or OX. In fact, the hexadecimal digits themselves can be written in either upper or lower case. However, this phenomenon is not an nply allows forms of expression of certain constants. 2.1.7 Exercises for Section 2.1 Exercise 2.1.1: What is the response of ML to the following expressions? * a) 14243 b) 5.0-4.2/1.4 *c) 11 div 2 mod 3 d) "too"™™bar" * e) 3>4 orelse 5<6 andalso not (7<>8) f) if 6<10 then 6.0 else 10.0 * 5) OXAB+123 h) Oxab<123 Exercise 2.1.2: The following ML “expressions” have errors in them. Explain what is wrong with each *a) 8/4 b) if 2¢3 then 4 *c) is2 and 573 d) 647 DIV 2 * 0) 4.43.5 f) 1.0€2.0 or 3>420 CHAPTER 2. GETTING STARTED IN ML * g) rane h) 123. #11) 1.0 = 2.0 Exercise 2.1.3: Write a string that when printed creates the displayed text on lines (3)~-(5) of Example 2.6. You may assume that the indentation of the lines is made by a single tab character. Your string should be written over several lines so there are no more than 80 characters appearing on any one line. Exercise 2.1.4: Express: *a) Eorelse F b) E andaleo F as if-then-else expressions. Incidentally, in ML, expressions formed with the symbols orelse and andalso are actually shorthands for these if-then-else ex- pressions. 2.2 Type Consistency Having seen some of the important building blocks of expressions, we must now learn what can go wrong when we use expressions built from these operators. ML assigns a unique type to every expression. Operators also have partic- ular types that they require their operands to have. Certain opcrators take operands of one particular type only. Examples are /, which requires operands of type real, div, which requires operands of type integer, and *, which requires operands of type string. Others, like + or +, can take arguments of different types, e.g., two integers or two reals. As we shall see shortly, it is not possible to mix operands of integer and real types, as it is in C or most other languages. of one type to an “equivalent” value of another type. We shall also learn a number of these “coercion” operators in this section. Let us again remind the reader that there is a purpose to this seeming inflex- ibility on the part of ML. It enables the ML compiler to type-check programs completely. ‘hus, no program that can run at all can have a type error. The advantage to the programmer is that what. could he a run-time bug in another language's program is caught by the ML compiler. 2.2.1 Type Errors As we saw in Example 2.1, when au operator is given operands of the proper type, it responds with the result. However, when one or both operands are of the wrong type, we get. an error message. The nature of error messages depends21 on the particular implementation. We shall use the responses from SML/NJ version 109.30 in examples. Example 2.14: The operator + can take either integer or real arguments However, both operands must be the same type. When the types of the operands are the same, ML attributes the same type to the result, for instance: 1+ val it — 3: int 1.0 + 2.0; vai tt = 3.0 : real On the other hand, when the operands are of mixed type, we get an error message, as shown in Fig. 2.1. Let’s see what ML is telling us. The first line of the response says that the operator expects operands of types othe: than what it saw. The second line of the response tells us that the operator + expects an “operand” whose type is a pair of integers. Although + can apply to either integers or reals, the fact that the left argument 1 is an integer suggests that integer addition was meant here. 142, Error: operator and operand don’t agree [literal] operator domain: int * int operand: int * real in expression: +: overloaded((I : int),2.0) Figure 2.1: A type error and its diagnostic message The * operator in the expression int * int is not multiplication, but rather an operator that applies to types and produces a product type, that is, the type of a pair, uiple, or so on. In particular, int * int is the type of any pair of integers, for example, of the pair (1,2). This response makes us aware of a rather rigid view ML has of operators and operands. Strictly speaking, all operators in ML are unary, that is, they take @ single argument. A binary (two-argument) operator like + is perceived by ML as taking a single argument that is a pair. Tn most situations there is no problem with viewing a binary 1 ces that address in Section 5.5. The third line of the response tells us what ML saw as the operand of the operator, namely a pair whose first component (the left operand) is an integer THowever, SML/NJ also returns a line and column number locating the point at which it detected the error. We do not show this response since it is rarely meaningful out of context.22 CHAPTER 2. GETTING STARTED IN ML but whose second component (the right operand) is a real. The final two lines indicate the expression in which the error occurred. The only additional nuance is that the operator and operand are shown in the conventional ML prefix form, he pair (1,2 In the fifth line of Fig.. 2.1, we note the use of the term “overloaded” in reference to +. An operator is overloaded if it can apply to two or more different types, as + can. Notice that the fact + is defined for two integers or two reals does not mean that it can be applied to one of cach. Similar comments apply to overloaded operators like -, *, <, and the other comparison operators. Tf we were to use an operand of the wrong type with an operator that is not overloaded, we get an error message similar to Fig. 2.1, but without the word “overloaded.” An example follows. Example 2.15: The expression eb applies the nonoverloaded operator *, which concatenates strings, to a character and a string. The error message would look like: Error: operator and operand don't agree literal] operator domain: string * string operand: char * string in expression: a? Phe?) a Another type of error involves applying an operator, overloaded or not, to operands at least one of which has a type inappropriate for the operator. Example 2.16: The division operator / applies only to reals, as we learned in Section 2.1.2. Here is what happens when this operator is misused. 1/2; Error: overloaded variable not defined at type symbol: / type: int Our first observation is that the error message talks about the symbol / and its application to the type int, which we know is improper. However, what is the “overloaded variable” in the first linc of the error message? ML thinks of / as a variabie. As we shali see in Section 2.3.1, / is a iegitimate identifier for a variable in ML, unlike most languages, where variable identifiers are restricted to letters and digits plus perhaps a few other symbols. Although we said that / applies only to “reals,” an implementation of ML may support several kinds of reals, such as single- and double-precision numbers. Thus, / might indeed be defined for several different types.2.2, TYPE CONSISTENCY 23 Auother place where type mismatches may occur through carelessness is in an if-then-else expression. The rules regarding types for this expression are: © The expression foiiowing 11 must have booiean type. © The expressions following then and else can be of any one type, but they must be of the same type. Example 2.17: Figure 2.2 shows what happens when the types of the expres- sions following then and else disagree. Here, one is a character and one is a siting. if 1<2 then #"a" else " Error: types of rules don't agree [tycon mismatch] earlier rule(s): bool > char this rule: bool + string in rule: false > "be” cM Figure 2.2: A mismatch between the then and else parts about finding a (lc, "be") about finding a string {ic., "be") ing us somethi when it expected a character to match the character #"a" that followed the then. But what’s this about “rules”? The explanation lies in the fact that the if- then-else expression is really a shorthand for a more general kind of expressiot the case expression. We shall cover the case expression in Section 5.1.4. For the moment, let. us just note that ML’s view of the if-then-else is that it involves two “rules,” each of which takes a boolean value and produces a value of some one type. ‘The first of these rules associates the boolean value true with the character #"a". This rule expresses the principle that if the condition is true, we use the value of the expression that follows the then. The second rule associates the boolean value false with the value following, the else, namely "bc" in this case. However, ML expects to find another character-valued expression following else, which it will then associate with false in the second rule. ML is unhappy that it has found a string-valued expression, because ML will not tolerate groups of rules that produce values of di ype. 2 © The word “tycon” in the first line of response in Fig. 2.2 is short for “type constructor,” that is, a way of constructing types from simpler types. Rules in the sense used in Fig. 2.2 are actually of a function type, mapping booleans to some other type. We discuss function types in Section 3.1.1.24 CHAPTER 2. GETTING S1ARTED IN ML Applying Functions, ML Style ML offers us a diction for applying a function or operator to an argument that may be unfamiliar to some: f x means “apply function f to argument x,” just as f(t) does in C or most other languages. Since there is no harm in putting parentheses around an argument, we have used the more conventional style, writing rea1(1) instead of the preferred MT, style: real 1. By adhering to the more familiar style, with parentheses, we hope to focus attention on the more significant issues of ML, without adding to the “newness” of the language. However, as the book progresses. we I gradually shift to the ML style of omitting all gradually shift to the MIL style of omitting the argument of a function whenever appropriate. 2.2.2 Coercion Between Integers and Reals Sometimes we have a reason to convert (coerce) a value of one type to an “equivalent” value of another type. ‘hus ML provides certain built-in functions that do the conversion for us. Perhaps the clearest case is when we want to convert an integer to a real with the same value. The function real lets us do just that. Example 2.18: Applied to an integer, real produces the equivalent real value real(4); val it = 4.0: real As another instance, we can fix Example 2.14, where we tried to add an integer and a real, if we first apply real to the integer. reai(i) + 2.0; val it = 3.0: real shows a correct version of this addition. Of course, there is no point in writing real (1) instead of 1.0, but if we replaced 1 by an integer-valued variable, we would have no choice but to convert the variable by applying the operator real toit. O When we try to convert a reai to an integer, it 1s not so clear which integer we want, since the real may not equal any integer. MT. provides four coercion operators: floor, ceil (cciling), round, and trun (truncate). Each produces the integer with the same value when given a real that happens to be an in- teger; for instance, 4.0 is converted to 4 by each of these four functions. In general, given a real number r, floor produces the greatest integer that is no2.2. TYPE CONSISTENCY 25 larger than r, and ceil produces the smallest integer no less than r. Function round produces the closest integer, with 0.5 raised to the next highest integer, regardless of whether the real is positive or negative. The trunc function drops Example 2.19: Figure 2.3 shows the effect of these four operators on positive and negative real numbers. We include the special case of a hall-integer (3.5 and —3.5), noticing that rounding occurs upward. We also include typical cases where the rounding is to the closest integer. Notice that floor and trune do the same thing on positive numbers, but trunc agrees with ceil on negative numbers. Remember that —3 is “larger” than —3.5 and —4 is “smaller.” x_| floor(x) | cei1(x) | round | trunc(x) 3.5 3 4 4 3 3.5) 4 73 3 os 3 4 3 3 “3.6 “4 “3 “4 “3 Figure 2.3: Effect of real-to-integer coercion operators 2.2.3 Coercions Between Characters and Integers We convert from characters to integers, just as in Pascal, using the ord function (which, however, must be lower case in ML). The result of applying ord to a character is the integer code for that character. Normally, the character will be one of the ASCII characters, and ord will return the ASCII code for that character. Example 2.20: ora (stam) » val it = 97: int ord(#"a") - ord(#"A"); val it = 92 : int The latter example computes the difference between the ASCII codes for lower- case a and capital A. This result is no coincidence. Every lower-case letter has an ASCII code that is 32 more than its corresponding capital letter. O Similarly, we can convert integers in the range 0 to 255 to characters. The function chr performs this task as: chr (97); val it = "#fa” : char26 CHAPTER 2. GETTING STARTED IN ML 2.2.4 Coercions Between Strings and Characters If we have a character, we can convert it to a string of length one with the operator str. That is, : string However, conversion from strings to characters is not so straightforward. Part of the problem is that we have to deal with strings that are not of length one. ML provides several ways to make the conversion where it makes sense. For example, we shalll see the explode operator in Section 2.4.5, which converts a string to a 2.2.5 Exercises for Section 2.2 Exercise 2.2.1; Write cxpressions to make each of the following conversions. * a) Convert 123.45 to the next lower integer. b) Convert -123.45 to the next lower integer. c) Convert 123.45 to the next higher integer. * d) Convert ~123.45 to the next higher integer. *e) Convert #"Y" to an integer. £) Convert 120 to a character. *1g) Convert #"N" to a real. 'h) Convert 97.0 to a character. i) Convert #"2" to a string Exercise 2.2.2: The following expressions contain type errors. What are the a) ceil (4) b) if true then 5+6 else 7.0 * c) chr(256) d) chr(~1) * 6) ord(3) f) chr(#"a") g) if 0 then 1 else 2 * h) ord("a")2.3. RIABLES AND ENVIRONMENTS 27 2.3 Variables and Environments In most languages, such as C or Pascal, computing takes place in an environment consisting of a collection of “boxes,” usually called variables. Variables have names and hold values. The name of a box is an identifier, which is a string of characters (typically, letters and digits) that the language allows as the name of a variable. There is usually a type associated with a variable, and the contents of a “box” can he any value of the appropriate type. Paseal, C, and most other Tanguages allow variables of types integer, real, and many other types. ‘At any given time the set of values stored in the variables’ boxes constitute the store. Tn conventional lanenages, computation oe by sideffors, A about ML j that it is impossible for the store to change, with a few exceptions, such as arrays and references, that we shall introduce in Chapter 7. Rather, ML does its computing by adding to the environment new value bindings, which are assuciatious between identifiers aud values. The above brief overview of this section is heady material, so let’s start again from the beginning. 2.3.1 Identifiers Identifiers are character strings with certain restrictions. Most languages allow identifiers that are letters followed by any number of letters and digits. ML allows these too, along with many other strings that are not identifiers in most other languages. In ML, identifiers fall into two classes: alphanumeric and ables, as described below, which are alphanumeric identifiers beginning with an apostrophe. Alphanumeric Identifiers The alphanumeric class of identifiers consists of strings formed by 1. An upper case ar lower case letter ar the character ? (called apostrophe or “prime”), followed by 2. Zero or more additional characters from the set given in (1) plus the digits and the character - (underscore). However, identifiers beginning with the apostrophe ’ are type variables. They can only refer to types and cannot be bound to ordinary values. Example 2.21: The following are examples of alphanumeric identifiers: abe x29 Number_of_Hamburgers_Served arbre28 CHAPTER 2. GETTING STARTED IN ML The following is a legal alphanumeric identifier: ’a. However, it cannot be bound to values like 3, 4.5, "six", or any of the values we normally think of as the values of variables. Tt. can only be bound to a type. In fact, ML often be chooses the ide i of any type. For instance, *a might in some contexts be given the type integer as its “value.” Note that being bound to the type integer is quite different from being bound to a particular integer like 3.0 Symbolic Identifiers Of all the characters we can type with a conventional keyboard, there are only ten that cannot appear as part of some sort of identifiers. These ten characters are the three kinds of pairs of parentheses (round, square, and curly), double quote, period, comma, and semicolon. ‘That is, the only characters that always stand alone and cannot, he part. of an identifier are CPCI LIF" 2 Of course the “white space” characters — blank, tab, and newline — also are not part of identifiers. These do not have a meaning by themselves, but they serve to separate the elements of a program. The remaining 20 keyboard characters that cannot appear in alphanumeric identificrs can be used to form symbolic identifiers. To be precise, the sct of characters for symbolic identifiers is tof eC >eP OER KONIG? Many of these symbols by themselves are names of operators. For example, we have seen the use of +, *, and several others. ML interprets the identifier + as a special function that adds either two r i More precisely, ML binds any other symbolic identifier that stands for an operator to the function implementing that operator. We are free in ML to form our own identifiers from strings of the 20 charac- ters listed above. These identifiers might be used to name new operators that we define, but they can also be used routinely to name integers, reals, and so on. Example 2.22: The following are legal symbolic identifiers: $$$, >>>=, and !@#%. However, !@a is not a legal identifier because it mixes the characters ! and @ (which may only appear in symbolic identifiers) with the character a (which can only be part of an alphanumeric identifier), 02.3. VARIABLES AND ENVIRONMENTS 29 Exercise Care Using Symbolic Identifiers We advise against using symbolic identifiers to represent values of types such as integers or strings. Besides looking strange, they often cause trou- ble because they must be surrounded by white space to prevent them from “attaching” to operators like + and forming unintended identifiers that confise the MT. system and canse an error That is, although ¢¢ and a are both legal identifiers, we must write << +a or << + a to add them. Should we write <
=
That is, we use the keyword val, the identifier for which we wish to create a 1 i 1 si d fs 1 ish to associate with that identifier. Example 2.23: Here is an example of how the identifier pi shown in Fig. 2.4 might have heen added to the environment. val pi = 3.14159; val pi = 3.14159 : real Notice that in response to the val-declaration of variable pi, ML responds with the value of pi rather than with the value of it, as was the case in all previous examples. Otherwise, the response to a val-declaration is the same as the response to an expression. ¢ In general, responses to val-declarations tell us the identifiers that have been bound to values and what those values are. ‘e might next define an identifier radius as: val radius = 4.0; nal radius = 4.0 : real 3-The val-declaration is actually considerably more general, and in place of a single identifier we can have arbitrary “patterns.” The matter is discussed further in Section 3.3.4,2.3. VARIABLES AND ENVIRONMENTS 31 Some Points About ML “Assignment” Remember to use the keyword val to cause a value binding to oc- cur. Assignment statements like x = y or x := y, familiar from other languages, are errors in ML (with one exception, discussed in Section 7.3.3). © It is tempting to think of the equal-sign in a val-declaration as equiv- alent to := in Pascal or = in C. However, these assignment operators from other languages cause side-effects, namely the change in the value stored in the place named on the left of the assignment opera- tor. In ML, the val-declaration causes a newentry in the environment to be created, associating what is to the left of the equal sign with the value to the right of the equal-sign. Example 2.24 illustrates this, point. Now we have some variables, namely pi and radius, that we can use along with constants to form expressions. For instance, we can write an expression that is the familiar formula for the area of a circle: pi * radius + radius; val it = 50.2544 : real Similarly, we could introduce another identifier, say area, and use a val- declaration to give it a value. val area = pi * radius * radius; val area = 50.26544 : real Note that in the above exampie, the expression suppiying the vaiue itself in- volves variables and operators. In previous examples the “expression” was a single constant. 2.3.4 A View of ML Programming We now have a rudimentary view of what ML programs look like. They are sequences of detinitions, such as the val-declaration that associates values with identifiers (which are loosely the same as “program variables”). So far, we don’t have any really interesting assignments to make; we can only bind values of a basic type (e.g., real or string) to identifiers, and we can ask for the value of an expression involving these identifiers and constants, by typing that expression. In Chapter 3 we shall see how to give identifiers values that are functions and32 CHAPTER 2. GETTING STARTED IN ML how to apply functions to values in order to compute new values. When these functions are recursive, we shall find ourselves programming in a mode that gives us all the power of other programming languages, yet has a distinctive It is natural to think of a val-declaration as an assignment, and often we shall not go wrong if we do so. However, there is a subtle but important difference in the way ML views what happens in response to a val-declaration. The next example ilusuates sume of that difference. Example 2.24: Suppose that after issuing the val-declarations of Example 2.23 we “redefine” radius to be equal to 5.0 by: val radius = 5.0; val radius = 5.0: real We might imagine that the entry in the environment for radius has had its value changed from 4.0 to 5.0, However, the proper ML view is suggested in Fig. 2.5. Below the top entry is the environment that existed before radius was “assigned” 5.0. We do not show all the identifiers that ML defines for us (c.g., +), but we concentrate on those we have defined: pi, radius, and area area 50.26544 4.0 Existing before 3.14159 val radius = 5.0 Figure 2.5: The environment after redefining radius The topmost entry in Fig. 2.5 is an addition to the environment that re- sults from the new val-declaration. We have shown in the current. environment. two entries that are named by the identifier radius, but only the most recent (upper) one is visible at this time. If we are running ML in interactive mode and simply entering a sequence of val-declarations, then the earlier declaration of radius cannot again become accessible through the current environment. When we discuss functions and their effect on the environment in Section 3.2.1, we shall see that it is sometimes possible to access a “buried” value binding such as the lower entry for radius, just as it is in conventional languages such as C or Pascal. O2.3. VARIABLES AND ENVIRONMENTS 33 Identifiers Do Not Have Fixed Types Note that when creating an entry with an old name, as we did in Exam- ple 2.24, there is no restriction that the new value be of the same type as the old value. We could just as well have defined radius to be an integer in Example 2.24, for instance: val radius = However, we then could not have used this variable radius in expressions like pi * radius * radius, because of the type mismatch. 2.3.5 Exercises for Section 2.3 Exercise 2.3.1: Tell whether each of the following character strings is (i) an alphanumeric identifier suitable for ordinary (nontype) values, (ii) a symbolic identifier, (iii) an identifier that must represent a type as a value, or (iv) not an identifier of ML. * a) The7Dwarves b) 7Dwarves * c) SevenDwarves ,The d) ’SnowWhite’ * e) aseb f) burrahi *e) HL h) 7123 Exercise 2.3.2: Show the effect on the environment of making the following val a= "three"; val c = a*str(chr(floor(b)));34 CHAPTER 2. GETTING S1ARTED IN ML 2.4 Tuples and Lists So far we have seen five types that ML values may have: integer, real, string, character, and boolean. Most languages start with a similar collection of types and build more complex types with a set of operators called type constructors, which are dictions allowing us to define new types from simpler types. For example, Pascal has, among other type constructors, 1. The record. . .end notation to build record types, whose fields may be of any type, 2. The * operator to build a type whose values are pointers to valucs of some simpier type, and 3. ‘The array constructor that defines an array type, given a type for elements and an index type. ML also has a number of ways to define new types, including datatype constructions discussed in Section 6.2 that go beyond what we find in C, Pascal, ‘or most other languages. However, the simplest and possibly most important ways of constructing types in MT. are notations for forming tuples, which are similar to record types in Pascal or C, and for forming lists of elements of a given type. In this section we shall learn these notations and also cover the most important operations associated with these types. 2.4.1 Tuples A tuple is formed by taking a list of two or more expressions of any types, sepa- rating them by commas, and surrounding them by round parentheses. Thus, a tuple looks something like a record, but the fields are named by their position in the tuple rather than by declared field names. Example 2.25: In the following val-declaration we assign to variable t a tuple whose first component is the integer 4, whose second component is the real 5.0, and whose third component is the string "six". val t = (4, 5.0, "six' val t = (4, 5.0, "siz”) : int * real * string Let’s try to understand the ML response. It repeats the fact that the value of t is the one we just gave it, which should be no surprise. However, it uses terminology we have not seen before in an ML response, as it describes the type of t. Recaii from Section 2.2.1 that the type int * real * string is a product type. Its values are tuples that have three components. The first component is an integer, the second is a real, and the third component is a string. The operator * has a different meaning when applied to types than it does when applied to integer or real values. Here * has nothing to do with multiplication, but indicates tuple formation. O2.4, TUPLES AND LISTS 35 Tr In general, a product type is formed from two or more types Ti, Tas ++ by putting *’s between them, as T; * Tz * +++ * Ty. Values of this type are tuples with k components, the first of which is of type T;, the second of type and 73 is string. Example 2.26; Here are some further examples of tuples and their types. 1. (1,2,9,4) is of type int + int + int * int. 2. (1,(2,3.0)) is of type int * (int * real). 3. (4) is of type int. Strictly speaking, it is not a tuple, just a parenthesized integer. In (2) the tuple has two components, the first of which is an integer. ‘The second component is itself a tuple with two components: an integer and a real. This grouping is reflected in the type description. ‘The « operator applied to types is not an associative operator. For example, int * (int * real) is not the same type as (int * int) * real. The latter type describes tuples of two components, the first of which is a pair of integers and the second of which is a single real. For example, ((1,2),3.0) is a value of type (int * int) * real. Neither is the same as the type int * int * real, which describes “flat” tuples like (1,2,3.0). 2.4.2 Accessing Components of Tuples Given a tuple or a variable whose value is a tuple, we can get any particular component, say the ith, by applying the function #i. Example 2.27: In Example 2.25, identificr t was bound to the tuple value G, 5.0, "six") Now we can obtain its components. For example: #1(t) 5 val it = 4: int #3.(t); val it = "six” : string It is an error to apply a function like #4 that designates a component nuinber higher than the number of components the tuple has. O Tuples can be likened to records whose field names are the numbers 1,2, . In truth, tuples as we have defined them are a special, simplified case of a more general record-structure construct that does allow the programmer to specify names for fields. However, the tuple is adequate and quite convenient for most purposes. We defer the more general case of record structures to Section 7.1.36 CHAPTER 2. GETTING STARTED IN ML 2.4.3 Lists ML provides a simple notation for lists whose elements are all of the same type. We take a list of elements, separate them by commas, and surround them with square brackets. Example 2.28: The list of three integers 1, 2, 3 is represented in ML by [4,2,31. The response of ML to an expression that is this constant value is (12,35 val it = [1,23] : int list The response to our list expression is informative. In addition to the usual repetition of the value in the expression, it assigns the list the type int list, which is ML’s way of saying “list of integers.”. O © In general, “T List” is the type of a list of elements each of which is of type T. Bawmple 2.28: Iu ou second eaainple, the list has a single element that is of type string. [a]; val it = [’0”] string list ‘The type attributed to the list expression is string list, or “list of strings.” ‘The fact that there is only one string in the list is irrelevant. The square brackets differentiate the expression "a", which is of type string, from the expression C'a"], which is a list of strings that happens to have only one string on the list. 0 Example 2.30: Finally, here is an example where we erroneously try to mix the types of elements of a list. We tried to write a list of three characters, but we forgot the pound sign on the last one, so it became a string of length one, instead of a character. Cea", ep", Error: operator and operand don’t agree [tycon mismatch] operator domain: char * char list operand: char * string list in expression: BPD” 2s Pe” ss nil ‘We shall explain the error message after we have learned some of the notation of lists in Section 2.4.4. 02.4. TUPLES AND LISTS 37 2.4.4 List Notation and Operators In this section we shall learn several operators that involve lists. These include notation for the empty list, the head and tail of a list, “cons” or construction of a list from a head and tail, and concatenation of lists. ‘The Empty List Lhe empty lst, or list of no elements, is represented in ML by either the name nil or by a pair of brackets, (] Head and Tail Any list besides the empty list is composed of a head, which is the first element, and a tail, which is the list of all elements but the first, in the same order. Example 2.31: If L ic the list [2,3,4], then the head of L ic 2, and the tail of L is the list [3,4]. If M is the list [5], then the head of M is 5, and the tail of M is the empty list, or nil. O the list, respectively. The following restates Example 2.31 in a sequence of ML expressions. Example 2.32: Suppose we define lists Land M by the val-declarations val L = [9,3,4] val L = (2,8,4] : int list val M = [5]; val M = [5] : int list Now we can get the head and tail of each of these lists as follows. hd(L) ; val it = : int t1(L); val it = [3,4] : int list hd (MD: val it = 5: int tL); val it = [] : int list38 CHAPTER 2. GETTING SVARTED IN ML In the last of these expressions, ML describes the type of nil as int list. It is possible for nil to be of any list type. In this case, since it is the tail of an integer list, it is appropriate to assign it this type. O Concatenation of Lists While hd and t1 take apart lists, there are also two operators that construct lists: concatenation and cons. We consider each in turn. The concatenation operator for lists, which is @, takes two lists whose cle ments are the same type and produces one list consisting of the elements of the first list followed by the elements of the second. Thus (1,2]0(3,4]; val it = [1,2,3,4] : int list ‘+ Do not interchange the * operator, which is concatenation of strings, with the @ operator, which is concatenation of lists. Cons The cons operator, represented by a pair of colons (::), takes an element (the head) and a list of elements of the same type as the head, and produces a single list whose first element is the head and whose remaining elements are the elements of the tail. Thus val it = [2.0] : real list The precedence of the :: and @ operators is below that of the additive operators such as +, but above that of the comparison operators like <. Most unusual is that these operators are right-assoctative, meaning that they group from the right instead of the left as do most operators we have seen Example 2.33: Especially important about right-associativity of these oper- alors is the interpretation of a cascade of cons operators, like 1i:2i:3simal This expression is grouped from the right, as 1::(2::(3::ni1)). Expression 3: 1a represents the list with head 3 and an empty tail, that 1s, Next, 2::(3] is the list whose head is 2 and whose tail is the list whose only element is 3; this list is 2,3]. Similarly, the entire expression denotes the list [1,2,3]. Notice that when we have a sequence of cons operators, only the last operand must be a list, such as nil in the example above. The other operands must be elements. It would not make sense to group an expression like2.4, TUPLES AND LISTS 39 The Types of Heads and Tails « Remember that the types of the head and tail are different. If the type of the head is T, then the type of the tail is “list of 7,” or T list in ML. © Similarly, the cons operator :: takes a first argument that is of some type T, and a second argument that is of type T list. ¢ On the other hand, the operator @ takes two arguments of type T list for some type T. 1is2i 53s inal from the left, as ((1::2)::3)::nil, because 1::2 is a type mismatch. That is, when the cons operator sees the left operand 1, it expects that the type of 8 ype of 2 is int, ms : to this pair of operands. U possible to apply Example 2.34: Let us reprise Example 2.30 and consider the meaning of the error message that we saw there. We repeat the relevant part of Example 2.30 in Fig. 2.6. tea", #”D", "oD; 1) Error: operator and operand don’t agree [tycon mismatch] 2) operator domain: char * char list 3) — operand: char * string list 4) in expression: 5) PPD es Pe? os nib Figure 2.6: Error message from Example 2.30 ML parses lists from the back (i.e., the right end). It starts off assuming a list is empty, ie. nil. When it sees the last element, "c", it “conses” that clement with the list following, i.c., "c" :: nil to get a list of one clement, t Evidentiy, the type of this jist is string 11st, since its one eiement is a string. Now, ML tries to attach the next-to-last clement, #"b", as the head of a list whose tail is ("c"]. But there is a type mismatch in the resulting list #"b" :: ["c"]. That is, since the head is a character, the expected domain of the operator :: is char * char list, ie., a pair consisting of a character40 CHAPTER 2. GETTING STARTED IN ML (the head) and a list of characters (the tail). That is what line (2) of the error message is telling us. However, as line (3) states, the actual type of the pair to which the :: operator was applied is char * string list, ie., ahead that isa (4) and (8) of confirm that the problem occurs in the expression #"b’ 2.4.5 Converting Between Character Strings and Lists In ML, strings and lists are different types. However, there is a great similarity between a string and a list of characters, and it is possible to convert between the two representations using the built-in functions explode and implode. The first Uf Uhese iakes a siting aud couveris ii iv the list of charaviers appearing in that string, in order. Example 2.35: Here are two examples of the use of explode. explode ("abcd" val it = [#a”,£"b",#"c”,#"a") : char list explode("""); val it = [] : char list Notice in the second example that "" is the empty string, which when exploded elds an empty list of characters. O The function implode takes a list whose elements are characters aud con- catenates all the characters together to form a single string. Example 2.36: Here are three cxamples of imploding lists. implode ([#"a",#"b",#"c",#"a"]) ; val it = "abed” : atring implode(nil) val it = "": string implode (explode ("xyz")); val it = "ayz” : string The second example points out that we can implode the empty list and get the empty string. The third illustrates that implode and explode are inverses of one another, and the effect of explode followed by implode on any string is to return the string itself. ©2.4, TUPLES AND LISTS a The Type of the Empty List Notice that in the second case of Example 2.35, ML deduced ‘that the empty list was of type char list, even though there are no elements in the list. ML knows it is an empty list of characters, because explode always returns a list of characters. Tn general, the type of the empty list is ’a list, i-e., a list. of elements of any one type. Recall from Example 2.21 that identifiers beginning with a quote mark denote types. Thus, ’a list is ML’s way of saying “any- type list.”. However, when the empty list appears as a value, the ML be able to di sys eo type for type that the elements would have if there were any elements. We shall have more to say about the need to resolve types in Section 5.3.1. A third operator, similar to implode, works on lists of strings instead of lists of characters. If L is a list of strings, then concat (L) produces the string 37: Here is an example of concat applied to a list of strings. concat(["ab", "cd", "e"]); val it = "abede” : string a 2.4.6 Introduction to the ML Type System Every programming language has a type system, that is, a collection of types for its values and variables and a way of expressing those types. We have not seen nearly all of the ML type system yet. but it is useful to observe the way types and their representations are constructed. The type system of ML is constructed from a basis of elementary types by applying certain type construc~ tors recursively. A type constructor is an operator that builds new types from simpler ones. Here is what we have seen so far of the ML type system BASIS: We have seen the elementary types int, real, bool, char, and string. INDUCTION: We have seen two type constructors: 1. The product-type constructor builds the types of tuples. If 7}, T»,..-;Tn are types, then 7; * 72 * --- * Tn denotes the type of a tuple whose ith component has type T;, for i = 1,2,...,n. ‘Recall that all hinary operators in ML are perceived as applying to a single pair, rather than ta two arauments.42 CHAPTER 2. GETTING STARTED IN ML 2. The list-type constructor List builds list types from element types. If T is a type, then T list is the type of lists each of whose elements is of type T. We may apply these type constructors in any order, as many times as we like, to build new types of increasingly complex structure. In type expressions, list is of higher precedence than *. Thus, we may need to use parentheses to group operands properly. Example 2.38: Here are some examples of constructed types and a typical value for each. 1. Type expression int ist is a list of integers. It is the appropriate type for values such as [1,2,3]. 2. Type expression string * int list * int is the type for a tuple with three components, whose types are respectively a string, a list of integers, and a single integer. A typical value of this type is ("ab", [1,2,3], 4). Note that in type expressions, list has higher precedence than *, so this type expression is properly parsed atring # (int list) * int, rather than (string + int) list * int. 3. Type expression (int * int) list list is the type of a list of lists of pairs of integers. An appropriate value for this type is LLC,2),(3,4)], L(6,6)J, nill The list consists of three elements. The first element is the list consisting of the pairs (1,2) and (3,4). The second element is the list with only one clement: (5,6). The third clement is the empty list. a 2.4.7 Exercises for Section 2.4 Exercise 2.4.1: What are the values of the following expressions? * a) #2(3,4,5) b) hd([3,4,5]) d) explode("foo") * e) implode([#"z", #"0", #"0"]) f) "ec tran nen)2.4. TUPLES AND LISTS 43 * 8) [rc","omJ@L"b", "0", "1" h) concat(["e","a","t"]) What possible, suggest an appropriate correction. * a) #4(3,4,5) b) nac{]) *tc) #1(1) d) explode(["bar"]) *e) implode(#"a",#"b") f) Cer"Je: [rane] +g) cco) h) 102 *i) concat((#"a",#"b")) Exercise 2.4.3: Give the types of the following expressions. * a) (1.5,("3", [4,5])) b) ((1,2] nil, (3]] *c) [(2,3.5), (4,5.5), (6,7.5)] d) ((#"a",#"b"J, (nil, (1,2,3]]) *! Exercise 2.4.4: Are (1,2) and (1,2,3) the same type? Are [1,2] and [1,2,3] the same type? Exercise 2.4.5: Give examples of appropriate values for each of the following type expressions. Do not use the empty list as the value for any list component. *a) int list list list b) (int * char) list *c) string list * (int « (real * string)) * int d) ((int * int) * (bool list) * real) * (real * string) *e) (bool * int) * char. !f) real * int list list list list. ! Exercise 2.4.6: Using two of the operators we have learned in this section, it is possible to convert a string of length one into the character of that string. Show how to accomplish this transformation.Chapter 3 Defining Functions Now we know everything there is to know about ML, except how to program! In this chapter we shall learn about defining and using functions. Essentially all programming in ML is conducted by the definition of functions and the application of these functions to arguments. As we shall see, ML uses functions in places where more traditional languages use iteration (e.g., while-loops). 3.1 It’s Easy; It’s fun The keyword tun introduces function definitions. In this section we shaii see the simplest form of function definitions, which are essentially single expressions that are evaluated for the arguments of the function whenever the function is called. Later sections discuss the more common forms of function definition, involving the matching of arguments to patterns, and the use of temporary defi- nitions in functions. We defer to Chapter 5 some of the more advanced concepts regarding ML functions, such as polymorphic functions (those that can take ar- guments of different types), higher-order functions (those that take functions as arguments or produce functions as results), and Currying of higher-order functions (writing a function so new functions may be created by instantiating one of its arguments). The simplest form of function declaration is fun
(
) =
char There are a number of observations we should make about the function upper. First, it has one parameter c. The value of upper is computed by the expression chr(ord(c)-32). We discuss the ML response to the definition ot upper in Section 3.1.1 We may use function upper to convert lower-case letters, just as we would in most languages, by applying upper to the desired letter. For instance, we can convert #"a" to #"A" by: upper (#"'a") ; val it = A” : char ML responds to expression upper (#"a") as it would to any expression. by assigning its value to it and telling the value. 0 3.1.1 Function Types Notice from Example 3.1 how ML represents function types. The response to the definition of function upper was val upper = fn : char - char In general, when a function is defined, ML does not respond with the value of that function, which is hard to express other than by repeating the defini- tion of the function. Rather, it responds with the type of the function. The specification of the function type has the form: fn :
->
fs 'There is actually a function toUpper that is available in a library of functions that ML. calls the Char “structure.” We shall cover struct Section 8.2 and the particular structure Char in Section 9.4.4. Function toUpper also has an inverse function, toLower, which converts upper-case letters to their corresponding lower-case letters, Moreover, each of the functions topper and toLover leave intact those characters that are not lower- or upper-case letters, respectively, which our simple function upper does not do.3.1, IT’S EASY; IT’S FUN 47 Function Parameters and Arguments In this book we call the variables to which a function is applied in its definition the parameters, while the expressions to which the function is applied in a function call are arguments. In other literature, one sometimes sees the terms “formal parameters” and “actual parameters” where we use “parameters” and “arguments.” 1. The keyword fn. 2. A colon. 3. The type of the parameter(s), called the domain type for the function. This type is chaz in Daample 3.1. ML regards cach function as having one parameter, but the type of this parameter can be a product type. So in practice there can be any number of parameters for a function. o The type of the result of the function, that is, the range type for the function. In Example 3.1, the range type is also char, but it is common for the domain and range types to differ. ML views each function as returning a single valuc, but since this value may be a tuple, in effect a function can return severai items. The operator -> is another way to construct types, just like * and the word List. If T, and 7; are types, then T; ~> Tz is the type of functions with domain type T; and range type Tp, that is, functions which take an argument of type T; and return a result of type T». Operator -> is right-associative, so T + T ++ Ty is interpreted as Ty > (hh > Ts) and is the type of a frnetion whose parameter is of type T, and whose result. is itself a function; that function has domain type Tz and range type Ts. The notion of a function producing a function as a value may seem strange, but these “higher order functions” are an integral part of ML programming that we shall examine starting in Section 5.4. 3.1.2 Declaring Function Types It might surprise the reader that we never had to declare the type of the param- eter c in the function upper of Example 3.1 or the type of the value returned by this function. ML deduced that these types are both char because of what it knows about the functions ord and chr. In general ML does not require48 CHAPTER 3. DEFINING FUNCTIONS Don’t Confuse fun With fn The response in Example 3.1 uses the keyword fn, which should not be confused with fun, even though both are short for “function.” We use fun to introduce a declaration of a particular identifier to be a certain function, while fn is used in ML to introduce a value that has a function type declarations for types. although you are free to declare the type if you wish. We shall have more to say about how ML deduces types in Section 3.2.4, and there we shall get a better idea of when we can rely upon ML to deduce types for us. The most common situation in which we have to declare a type is when ML would use the default rule for an arithmetic or comparison operator to deduce that certain variables were of integer type, and yet we want these variables to be of some other type on which the operator can be used. If we need to, we can follow any variable or expression by a colon and a type. The that variable or expression to have that type. Recall that the colon symbol is also used in ML responses to connect. valies with their types. Example 3.2: Our next example is a function that squares reals. fun square(x:real) = x#x; val square = fn : real -> real ‘The function square has one parameter, x. By following parameter x with a colon and the type real, we declare to ML that the parameter of function square is of type real. ML then infers that the expression x#x represents real multiplication, and therefore the value returned by square is of type real. O It is necessary to indicate that x is real somewhere. Otherwise, ML will use the default type, integer, for x, resulting in a function that can square integers but not reals: fun square(x) = x*x; val square = fn: int + int We couid have attached the :real to any or ail of the three occurrences of x in the definition of square in Example 3.2, For example fun square(x) = (x:real)*x is a possibility. However:3.1. IT’S EASY; IT’S FUN 49 © We must be careful to parenthesize the arguments of the colon operator — for example, (x:real) — because the colon has lower precedence than the arithmetic or comparison operators. Example 3.3: Some care must be exercised in how we specify the types of variables in a function definition. Here is an example of a surprising error that can occur if we do not group a variable with its type properly. fun square(x) = xireal + x; Error: unbound type constructor: « Here, because + has higher precedence ian :, ME has isied iv “uultipiy” real by the third of the three x’s before applying the operator :. ‘That is not as strange as it. seems. ML knows real is a type, and * applied to types forms a product type. That is, ML is trying to form a type consisting of pairs whose first component is of type real and whose second component is of type x. But it doesn’t know about any type named x, so it complains. The solution, which we used following Example 3.2, is to parenthesize the x:real so ML will group its operators as we intend. © 3.1.3. Function Application As an example of the use of the square function, suppose we have defined the variables pi and radius to have values 3.14159 and 4.0, as in Example 2.23. pi*square(radius) ; val it = 50.26544 : real In this example, function application looks just like it does in Pascal or most: languages; a function is applied to a list of arguments, with parentheses around the argument list. However, as we discussed in the box on “Applying Functions, ML Style” in Section 2.2.3, formally, the ML syntax for function application is simply a pair of expressions standing next to one another, with no intervening punctuation. That is, F E requires the expression F to be evaluated and interpreted as a function. ‘Then, expression # is evaluated and function F is applied to the value of F. Example 3.4; We could have computed the area of a circle by pi * square radius; val it = 50.26544 + real Function application has higher precedence than any of the arithmetic opera- tors, so the above expression first applies function square to argument radius, and the result is multiplied by pi. O50 CHAPTER 3. DEFINING FUNCTIONS In principle, it doesn’t matter whether or nol we pul parentheses around @ simple argument (i.e., an argument without operators); that is, £ x and £(x) are treated the same hy MI.. However, we advise using the parentheses. Not only do par pp! but sometimes they prevent an error such as failure to put parentheses around an operand and its type, to which the operand is connected by the : symbol. Example 3.5: To undersenre the point that fimetion application has higher precedence than the common operators, consider the sequence of statements below, ending in an application of the function square from Example 3.2. val ¥ = 3.0; val y = 4.05 square x+y; val it = 13.0 : real We see from the value produced that ML has grouped the function application (square x)+y; that is, function square is applied to x before the addition with y takes place. If we want to square the sum of x and y. then we are required square (x+y) ; val it = 49.0 : real as we would in most other languages. 0 3.1.4 Functions With More Than One Parameter We can define a function that has any number of parameters. Normally, we put parentheses around the list of parameters or arguments, both in the function definition and use. The effect is to combine the list of arguments into a tuple, which formally is a single argument but which we may Ueat as if there were several arguments. It is also possible to write multiparameter functions without parentheses; see Section 5.5. Example 3.6: Figure 3.1 is another example of a function; it produces the largest of three real numbers. It begins by comparing parameters a and b in line (2). Tf a is larger, it returns as a result the larger of a and c at lines (3) and (4). If b is larger, then in lines (6) and (7) it returns the larger of b and c. a Example 3.6 brings up a number of important points about ML types. * Notice that in Fig. 3.1 ML deduces that b and ¢ are reals, even though only a was declared. One way to make this deduction is to use the fact that the if --- then --- else operator must have the same type in both branches. We shall discuss type deduction further in Section 3.2.4.3.1, IT’S EASY; IT'S FUN 51 (1) fun max3(a:real,b,c) = (* maximum of three reals *) (2) if a>b then (3) if a>c then a (4) else c (8) else (6) if bec then b mM else c; val maz? = fn ; real * real * real + real Figure 3.1: Function computing the maximum of its three arguments Comments Now that we can write programs of more than one line, we shall have reason to comment our code. The proper way to do so is shown in line (1) of Fig. 3.1. The pair of characters (* introduce a comment, which continues. even across lines, until the matching sequence of two characters *) is encountered. This convention is similar to most implementations of Pascal, but in ML it is possible to uest pairs of (#...4), just like parentheses are nested. Also notice that the type of max3 is a function that takes a triple of real numbers as its argument and produces a real. That type is shown in the response as real + real + real -> real. © In type expressions + takes precedence over -. Thus in the type expres- sion above, the domain type is real * real * real, and the range type is real * If we did not declare the type of any variable in function max3, then ML would assume the variables compared by > have the type integer, the default type for operator >. One advantage of the ML view that functions have only one parameter is that a variable whose value is of the appropriate product type can be defined and used as the argument of a multiparameter function. For’ example, val t = (1.0,2.0,3.0)5 max3(t) ; is correct ML and produces the value 3.0.52 CHAPTER 3. DEFINING FUNCTIONS 3.1.5 Functions that Reference External Variables The functions illustrated so far use only their parameters in computing their result. Sometimes, we wish to write a function that uses previously defined variables in its body. As in most other languages, the use of a variable x in the definition of a function f “freezes” x as far as the function f is concerned. That is, subsequent redefinitions of «c will not affect the function f. A simple example will illustrate the rule. Example 3.7: Consider the following sequence of steps: 1) val x = 3; 2) fun addx(a) = atx; 3) val x = 10; 4) addx(2); int val it = A picture of the changes to the environment is shown in Fig. 3.2. At line (1) we create a variable x and give it the value 3. When at line (2) the function a id by an as in Fig. 2.9 that the definition of addx refers to this value binding for z. Remember that, as we suggested in Section 2.3.4, the value binding for «, after being added to the environment at line (1), never changes. Thus, we can be sure that the definition of addx will always use 3 as the value of «.? Now, in line (3) we create a new variable, also named x. As we see in Fig. 3.2, the new binding for x goes above the old binding for x and the definition of addx. However, the definition of addx docs not change; it continues to refer to. the value of x that pertained when the definition of addx was made. Thus, we see that in line (4), addx(2) results in the value 5, not 12, because the value of x in the definition of addx is still 3. 3.1.6 Exercises for Seciion 3.1 Exercise 3.1.1: Write functions to compute the following: * a) The cube of a real number 2. b) The smallest of the three components of a tuple of type int * int * int. *c) The third element of a list. The function need not behave properly if given an argument that is a list of length 2 or less. ?You may therefore wonder why we would want to write addx as we did. Indeed, fun addx(a) = a¢3 would be a simpler way to write the same function. ‘There are some good reasons to write functions that refer to external variahles, however For example, in Section 8.1.2, we consider a collection of finetions that all use the same external variable, If we change this variable, all the functions change in a coordinated way.3.1, IT'S EASY; IT’S FUN 53 x 10 Added at line (3) definition aa ad: Added at li addx of addx 5 at line (2) x 3 ? Added at line (1) Previous environment, Figure 3.2: Changes to the environment in Example 3.7 a yo ) * e) The third character of a character string. Your function need not behave well on strings of length less than 3. Hint: Use explode and your function from Exercise 3.1.1(c). {a2, a3, ++ +54, aa)- ! Exercise 3.1.2: Write functions to do the following. * a) Given three integers, produce a pair consisting of the smallest and largest b) Given three integers, produce a list of the three in sorted order. ©) Round a real number io ihe nearest tenth, 1d) Given a list, return that list with its second element deleted. Your function need not behave well on lists of length shorter than 2. Exercise 8.1.8: Suppose we execute the following sequence of definitions: val a = 2: fun f(b) val b= 3 fun g(a) = a+b; ab: Give the value of the following expressions: * a) £(4)54 CHAPTER 3. DEFINING FUNCTIONS ‘When and Where Function Definitions Occur The behavior of ML regarding the value of variables used in a function definition is essentially the same as the policy followed by C or Pascal, or iost other languages. For example, in C a function’s definition can refer to static variables defined prior to the function definition in whatever file the function definition appears. That is, the variables a function definition can use depends on where the definition appears in a C program. It might appear that which variables are usable by a function defini- tion in ML depends on when the function is defined. That is, the function ly le — 5 is caused by the fact that we have been thinking of ML progranuning as done in interactive mode, where steps are entered one at a time. If we think of what we type in interactive mode as a single file of program elements, then we see that ML follows the same rule as © you may use variables that are located above the function definition in the file. ‘Lhis rule applies exactly if we write ML programs in files and execute them using use, as we discussed in Scction 1.2. There are, however, two differences between ML and C in this regard: 1. In C, the value of a variable can change, thus changing what the function docs; in ML the value cannot change. 2. In ML, it is possible to make several declarations for the same iden- tifier, external to any function. In C, that would be considered an illegal redefinition. *b) £(4) 4b. a d) g(S)+a. *e) £(g(6)). f) g(£(7)) 3.2 Recursive Functions It is possible, and indeed frequently necessary, for ML functions to be recursive, that is, defined in terms of themselves, either directly or indirectly. In fact, re- cursive functions in ML substitute for most of the iterations such as while-loops or for-loops than one finds in C, Pascal, and most other languages. Looping3.2, RECURSIVE FUNCTIONS 55 statements, while present in ML (See Section 7.3.4), are awkward and generally discouraged. When writing recursive functions, we must be careful that if a recursive the programmer, sinaller than its own argument. For example, if the argument is an integer i, we could safely call the function with argument i—1 or any integer smaller than i. If the argument. is a list I., we could call the function on the tail of the list or any shorter list. Normally, a recursive function consists of 1. A basis, where for sufficiently small arguments we compute the result without making any recursive call, and 2. An inductive step, where for arguments not handled by the basis, we call the function recursively, one or more times, with smaller arguments. Tn this section we shall learn abuut writing simple recursivus. We then in- troduce two extensions: nonlinear recursion, where the recursive function calls itself several times, and mutual recursion, where several functions are defined recursively in terms of each other. We begin with a simple example of a recur sion. Example 3.8: Let us write a function reverse(L) that produces the reverse of the list L.? For example, reverse([1,2,3]) produces the list [3,2,1] BASIS: The basis is the empty list; the reverse of the empty list is the empty INDUCTION: For the inductive step, suppose L has at least one element. Let the first or head element of L be h, and let the tail or remaining elements of L be the list T. Then we can construct the reverse of list L by reversing J’ and following it by the element h. For instance, if L is [1,2,3], then h = 1, T is [2,3], the reverse of T is [3,2], and the reverse of T concatenated with the list containing only h is {3,2]¢@(1], or (3,2,1]. (1) fun reverse(L) = (2) if L = nil then nil @) else reverse(ti(L)) @ [hd(L)]; nal reverse = fr: ’a list + ‘a list Figure 3.3: A recursive function to reverse a list In Fig. 3.3 we see the ML definition of reverse that follows the basis and inductive step described above. Lines (2) and (3) are the expression that forms IML actually has a built-in function rev that performs this operation.56 CHAPTER 3. DEFINING FUNCTIONS When Does a Function Need to Know Its Type? Given our discussion in Section 3.1.2, it might surprise you to find that in Example 3.8 it was not necessary for the particular type of elements to be deduced by the ML compiler. The difference between Example 3.8 and previous examples of functions that work on parameters of only one type, is that. some functions use an overloaded operator such as + or < that require us to tell ML what type its operands have (or to use the default type for the operator). In Example 3.8, there is no overloaded operator, and thus, we were able to avoid specifying the types of elements of the Section 5.3 wl is to know the type of its operands and when it can be “polymorphic,” working on values of various types. the body of the function definition. In line (2) we handle the basis case: the reverse(t1(L)) takes the tail of the given list and reverses it, recursively. We then concatenate this new list with the head element, which is obtained by subexpression hd(L) In order to concatenate the reversed tail with the head element, we must place square brackets around the head element, as [hd(L)]. Remember that the concatenation operator @ requires two lists as its arguments. If we were to omit the square brackets, we would be concatenating a list and an element, leading to a type mismatch. The response to the definition of reverse in Fig. 3.3 illustrates an interesting point. Unlike our previous examples of functions, ML cannot tell exactly what the type of argument and result is. It can only deduce that these types are both lists of elements of the same type. It calls the element type ’a, and it calls the argument and result types ’a list.4 The type of reverse is then a function from ?a lists Uo ?a lists. 3.2.1 Function Execution Whenever a function is called, its arguments are evaluated, and an addition to the environment is created that associates the resulting values with the param- eters of the function. This style of argument passing is known as call-by-value ‘Recall that identifiers beginning with a quote are variables denoting types. Actually, the type variable used by MI. in this example is ?%a (ie, two quotes hefore the a). There is a subtle distinction hetween 'a and 1a, which we shall discuss in Section 5.3.4. Refore then, we shall use only *a, "b and so on as variables denoting, types.a g 3.2. RECURSIVE FUNCTI VS Ivis the same as the manner by which arguments are passed to functions and procedures in C, and the manner in which non-var parameters are handled in Pascal. When the functi entries that bind the parameters of the function to their associated values. If the function is recursive, new additions are built on top of the old ones for each recursive call. Each addition binds the parameters of the function to the aigument values. These bindings intercept any reference to the parameters, thus distinguishing themselves from the entries with the same identifiers in levels below. When a function completes and returns its value, its addition to the environment gocs away, but the returned value is available for use in the expression being evaluated. ic executed, we place on top of the old oi Suppose we are in an environment that has the definition of se from Example 3.8. If we call Example 3. the function re’ reverse([1,2,3]) then we add to the environment an entry for parameter L and its value. We show this first step above the line in Fig. 3.4. ‘Added in call to . {1,2,3] reverse((1,2,31) reverse | definition of Environment a before call Figure 3.4: Environment after the initial call to reverse With this value of L as argument, the condition of line (2) in Fig. 3.3 is false; that is, L is not nil. Thus, we must evaluate the expression on linc (3), which requires us to evaluate reverse(t1(L)) or reverse([2,3]). Thus we set up another call to reverse, adding to the environment a new binding for L that associates L with the value [2,3]. In a similar manner, the new cali to reverse causes us to make auviher ¢ with L bound to (31, and an addition to the environment is set up with this binding. Again a recursive call to reverse is necessary, and in the fourth call L is bound to nil. The additions to the environment for all four calls are stacked one above the other as suggested in Fig. 3.5. At this point, the identifier L refers to the top binding, with value nil.58 CHAPTER 3. DEFINING FUNCTIONS Current. View /( J . Added in call to r — reverse(nil) Added in call to x = reverse([3]) Added in call ta r 2,3] reverse([2,3]) Added in call to ' (9,31 severse([1,2,3]) reverse | Sou Environment before call Figure 3.5: Additions to the environment. when four calls to reverse are made Now when we evaluate the body of reverse, the test of line (2) is satisfied, because L has the value nil. The value nil is returned and used in place of (1(L)) by reveree([3]) — to p: its own answer on line (3). After the return, te lop entry fur L in Fig. 3.5 disappears, exposing the appropriate value of L, namely [3]. Since hd([3]) is 3, the result produced by reverse([3]) is the empty list concatenated with [3], or just [3]. Now, the addition to the environment for reverse([3]) goes away, and its result is used by the call below it: reverse({2,3]). That, in turn, produces {5,2} as a result aid its addition tv the euvirumment goes away, leaving the environment that was originally shown in Fig. 3.4. However, the corresponding call, reverse([1,2,3]), now receives the value [3,2], returned from above, to use in place of reverse(t1(L)) in line (3). Thus the original call to reverse is able to produce its value, [3,2,1]. At this point, ali bindings for L have disappeared. O3.2, RECURSIVE FUNCTIONS 59 3.2.2 Nonlinear Recursion ‘The form of recursion illustrated in Examples 3.8 and 3.9 is relatively simple. Hach call either results in one recursive call with a smaller argument, or we reach the basis case and there is no need for a recursion. Now we shall examine a function where the recursion involves more than one recursive call. The function combinations of m Ukings out of u or “n chose m,” usually written ("), is the number of ways we can pick a set of m things out of n distinct things. For example, two aces ont. of the four aces in a card deck can be picked in six possible ways. That is, we can pick any of the four aces first and any of the three remaining aces second. That looks like 12 ways, but in fact we have picked each set in two different orders. For example. the aces of spades and hearts could be picked spade-then-heart or heart-then-spade. In general, (") = n!/((n — m)!m!), where 2! (x factorial) is the product of all the integers from 1 up to x. For instance, (8) = a/(212) = 4 8x2 1/(2x 1x21) =6 Intuitively n!/(n — m)!, which equals n x (n — 1) x +++ x (n= m+ 1), is the number of ways we can select among n things for the first choice. then among the m — 1 remaining things for the second choice, and so on for m choices. We must divide this number by m! because each set of m elements will have been selected in m! different orders. There is also a natural recursive way to define (”). Here are the basis and induction rules. BASIS: There are two parts to the basis. If m = 0, then the number of ways to pick 0 things out of n is 1 — don’t pick anything. Thus, (7) = 1 for any n > 0. Also, if m = n, then there is one way to pick all n things out of n — pick them all. Thus, (") =1 for all n > 0. INDUCTION: If 0 < m
n, so this basis and induction entirely define the function. Example 3.10: We can write a function comb(n,m) that computes (”.). The code appears in Fig. 3.6. Line (2) handles the basis case, and line (3) implements the inductive step. Note that the program will not behave well if the assumption about n and m in the comment of line (1) is violated. We really should test for violations, and there is an important. mechanism, the “exception,” that allows6 CHAPTER 3. DEFINING FUNCTIONS us to do so and still adhere to the principle that functions return a value of one particular type invariably. We discuss exceptions in Section 5.2. O) (1) fun comb(n,m) = (« assumes 0 <= m <= n *) (2) if m=0 orelse m=n then 1 (3) else comb(n-1,m) + comb(n-1,m-1); val comb = fr: int * int > int Figure 3.6: Function to compute n choose m The sequence of recursive calls initiated by a single use of function comb is rather complex. For example, in the expression comb(4,2); val it = 6: int the initial call first calls comb(3,2) and later calls comb(3,1). Iowever, be- tore the latter call, comb(3,2) calls comb(2,2) and comb(z,1), and so on. Figure 3.7 shows the structure of the calls as time progresses from left. to right. Figure 3.7: Structure of recursive calls for the comb function 3.2.3 Mutual Recursion Occasionally, one needs to write two or more functions that are mutually re- cursive, meaning that each calls at least one other function in the group. Most languages, such as Pascal or C, put some obstacles in the way of writing such functions, but ML has a straightforward mechanism. We shall give an example of mutually recursive functions, first showing the problem that arises if we are not careful. ‘hen, we shall show how ML lets us handle the problem. Example 3.11: Suppose we want to write a function that takes a list L as argument and produces a list consisting of alternate elements of L. There are two natural versions of this function. One, which we call take(L), takes the first element of L and alternate elements after that (i.e.. the first. third. fifth3.2. RECURSIVE FUNCTIONS GL and so on). The other, which we call skip (L), skips the first element and takes alternate elements after that (i.e., the second, fourth, sixth, and so on). It is convenient to define these two functions in terms of each other. BASIS: If L is empty, both functions return the empty list. INDUCTION: If L is not empty, take returns the head element of L followed by the result of applying skip to the tail of L. On the other hand, skip returns the result of applying take to the tail of L. Figure 3.8 shows a failed attempt to define the functions take and skip. AU the third line of take, we assemble the result using the cons operator; the head is the head of L, and the tail is the result of applying skip to the tail of L. The problem is that at the third line, the function skip is not defined, even though we intend to define skip immediately thereafter. Thus, ML responds with an error message. Defining skip first would cause a similar error because take is used in the third line of skip. O fun take(L) = at L = nil then nil else hd(L)::skip(t1(L)); Error : unbound variable or constructor: skip fon akip(t.) = if L = nil then nil else take(tl(L)); Figure 3.8: Erroneous attempt to define mutually recursive functions We can get ML to wait until it has seen both functions take and skip before trying to interpret. variables, if we use the keyword and between the function definitions. The general form for defining n mutually recursive functions is shown in Fig. 3.9. There we see the n definitions connected by and’s. There is one use of fun at the beginning and one use of the semicolon, at the end. ‘* Do not confuse and, which is used to indicate mutual recursions, with andalso, which is the logical AND operator in ML. © It is not necessary to use the and construct if there is no mutual recur- sion. If we define functions fi, fo,-... fn. and, for each i, in the detimtion of f; we use only functions that appear carlier on the list — that is, Sis fa--+ fi-1 — then there is no mutual recursion. Example 3.12: ‘The correct definition of the functions take and skip from Example 3.11 is shown in Fig. 3.10. Notice that the response from ML does not62 CHAPTER 3. DEFINING FUNCTIONS fun
and
and and
; Figure 3.9: Form of a mutually recursive function definition come until after both functions have been seen. Both are identified as functions from lists to lists. The elements of the input and output lists of both functions must be of one type ’a, but ML cannot identity the type. fun take(L) = if L = nil then nil else hd(L)::skip(t1(L)) and skip(L) = if L = nil then else take(t1(L)); nal take = fn : ’a list + 'a list val skip = fn: ’a list + a list Figure 3.10: Correct definition of mutually recursive functions Here are two exampies of the use of these functions. vake([1,2,3,4,51); val it = [1,3,5] : int list skip([#"a"#"b" ,#"c",#"d",#%e"]) 5 val it = (#"b”,#"d"] : char list When we use the functions on particular lists, ML can figure out the type of list elements from the argument. Hence, the type of the result list is reported with each use: int list in the first. case and char list in the second. 13.2, RECURSIVE FUNCTIONS 63 3.2.4 How ML Deduces Types ML is quite good at discovering the types of variables, the types of function parameters, and the types of values returned by functions. The subject of how ML does so is quite complex, but there are a few observations we can make that will cover most of the ways types are discovered. Knowing what ML can do helps us know when we must declare a type aud when we can skip type declarations. 1. The types of the operands and result of arithmetic operators must all agree. For example, in the expression (a+b) +2.0, we see that the right operand of the * is a real constant, so the left operand (a+b) must also be real. If the use of + produces a real, then both its operands are real. ‘Thus, a and b are real. They will also have a real value any other place they are used, which can help make further type inferences. 2. When we apply an arithmetic comparison, we can be sure the operands are of the same type, although the result is a boolean and therefore not necessarily of the same type as the operands. For example, in the expres- sion a<=10, we can deduce that a is an integer. 3. In a conditional expression, the expression itself and the subexpressions following the then and else must be of the same type. 4. Ifa variable or expression used as an argument of a function is of a known type, then the corresponding parameter of the function must be of that type. Similarly, if the function parameter is of known type, then the variable or expression used as the corresponding argument must be of the same type. 5. If the expression defining the function is of a known type, then the function returns a value of that type. G. If no way to determine the type of a particular use of an overloaded operator exists, then the type of that operator is defined to be the default for that operator, normally integer. Example 3.13: Consider the function comb(n,m) in Fig. 3.6, which we repro- duce here for convenience. (1) fun comb(n,m) = (* assumes 0 <= m <= n +) (2) if m=0 orelse m=n then 1 (3) else comb(n-1,m) + comb(n-1,m-1); In linc (2), we sce that in one branch of the if then-clsc the result is the integer 1. Thus, the expression on line (3) must also be of type integer, and the function comb returns an integer value. In line (3) we also see the expressions n-1 and m-1. Since one operand of each subtraction is the integer 1, the other operands,* 64 CHAPTER 3. DEFINING FUNCTIONS nin one case and m in the other, must also be integers. Thus, bouh parameters of the function are integers, or strictly speaking, the (one) parameter of the function is of type int. * int, that is, a pair of integers. at line (2). We see m compared with integer 0, so m must be an integer. We also see n compared with m, and since we already know m is an integer, we know the same about n. O 3.2.5 Exercises for Section 3.2 Exercise 3.2.1: Write the following recursive functions. *a) The factorial function that takes an integer n > 1 and produces the product of all the integers from 1 up to n. Your function need not work correctly if the argument is less than 1. b) Given an integer é and a list L, cycle L i times. That is, if then the desired result is [a:41,0:42)-++sn;41502,-.,a)]- You may use the function cycle defined in Exercise 3.1.1(f). *c) Duplicate each element of a list. That is, given the list [a,,a2,...,@n], produce the list [a1,01,@2,02,..-,4n,4y)- d) Compute the length of a list.> ¢) Compute x‘, where c is a real and 1 is a nonnegative integer. This function takes two parameters, x and i, and need not behave well if i < 0. *!f) Compute the largest element of list of reals. Your function need not behave well if the list is empty. ! Exercise 3.2.2: In the following function definition fun foo(a,b,c,d) = if a=b then cti else if a>b then c else b+d it is possible to deduce that a, b, c, and d are all integers. Explain how ML makes these deductions. Exercise 3.2.3: Suppose we define a function f by a statement that begins fun f(a:int, b, c, d, e) =. ®There is a function length in the MI. top-level environment that performs this function; the exercise asks you to write the function as if it were not already available.3.3. PATTERNS IN FUNCTION DEFINITIONS 65 Tell what can be inferred about the types of b, ¢, d, and/or e if the body of the function is each of the following if-then-else statements: * a) if acbtc then d else e. b) if acb then c else d. *c) if acb then btc else dte. !d) if ach then béc else d e) if béc then a else ctd a1) !g) if b
(
) =
I
(
)
| !
(
) =
;66 CHAPTER 3. DEFINING FUNCTIONS ‘The identifiers must all be the same (they are each the name of the function), and the types of the values produced by the expressions on the right of the equal- signs must all be the same. Likewise, the types of the patterns themselves must be the came, but they can differ from the type of the values produced, As with functions in general, the parentheses around the patterns are op- tional. However, the juxtaposition of expressions representing application of a function to its arguments has higher precedence than any of the usual opera- tors. Thus it is wise to put parenthescs around patterns that arc more complex than a single variable. Otherwise, we run the risk that only the first part of the pattern will be treated as the function argument, and an error will result. ML goes through the various patterns in the order that they appear until it finds one that maiches its argument. The first maich determines the value produced; other patterns are not considered. Thus, there can be overlap among the various patterns. «* It is also legal to fail to cover all possible casca with the forms. However, you will get the diagnostic Warning: match not exhaustive You should then be very sure that the function will be used only with arguments that match one of the patterns. Example 3.15: Let us reconsider the function reverse from Example 3.8 There are two patterns for the argument L. If L is empty it matches the pattern nil. If L is not empty, it will match the pattern x::xs. For instance, if the list has a single element, x becomes that element and xs gets the value nil. A nonempty list cannot match nil, and x::xs does not match the empty list, because there is no head element. to give a value to x. (Tt is not possible to. give x the value nil because x is an clement, not a list). Thus, the following definition works: fun reverse(nil) = nil | reverse(x::xs) = reverse(xs) @ [x] val reverse = fn: ‘a list + ‘a list Compare this definition with the equivalent definition in Fig. 3.3.7 Here, x plays the role of hd(L) and xs plays the role of t1(L). The above function operates by first checking if its argument is nil and returning nil if so. If the argument is not nil, then we can match x: :xs to the argument; x acquires the valne of the head and x8 acquires the value of the tail ®However, SML/NJ, as a default, treats completely redundant patterns, that is, patterns that can never be reached when the argument has any value that will match the pattern, as an error. There is actually a small difference hetween the two functions we called reverse, con- cerning the types of the elements that may form the lists being reversed. We shall address this distinction in Section 5.3.4.3.3. PATTERNS IN FUNCTION DEFINITIONS 67 Names for List Components in Patterns It is conventional to use a pair of identifiers like x for the head of a list and xs (read “exes”) for the tail of the same list. However, beware using a::as. Since as is a keyword in ML (see Section 3.3.2), you will get a strange diagnostic and must find another variable to use in place of as. added in call to reverse(nil) = ot added in call x 3 to reverse((3]) pee fe) added in call x 2 to reverse((2,3]) as E283 added in call x 1 to reverse([1,2,3]) L £1,2,3] initial environment Figure 3.11: Binding values to the identifiers of a pattern Figure 3.11 suggests the addition to the environment that occurs when reverse(L) is called, where L has the value [1,2,3]. Notice at the last call, to reverse(nil), there are no bindings for x or xs because the pattern nil matches the argument. and we never even try to match the second pattern. All these additions to the environment go away when the initial call to reverse completes. O 3.3.2 “As” You Like it: Having it Both Ways It is possible to take a single value and at one time give the value to an identifier and match the value with a pattern. In the match, variables mentioned in the ¢ their own values. The form ie
as
Example 3.16: Let us wrile a function merge(L,M) that takes two lists of integers, L and M, that are sorted lowest-first, and merges them. That is, merge produces a single sorted list. with all the elements of L and M. The68 CHAPTER 3. DEFINING FUNCTIONS following recursive definition of merge works, assuming that the given lists are sorted. Note that, although no types are specified, integer type is inferred for all values hecause that is the default type for <. BASIS: If L is empty, then the merge is M. If M is empty, the merge is L. INDUCTION: If neither L nor M is empty, compare the heads of L and M. If the head of I, say x, is smaller, then the sorted list. is 2 followed by the merge of the tail of L with all of M. Note that in this case, x is the smallest of all the elements, so x followed by the merge of the other elements will be the proper sorted list. If instead, the head of Af, say y, is ast ’ ae is y followed by the merge of L and the tail of M. Since y belongs at the head of the result, the complete list. will be sorted. (1) fun merge(nil,M) = M (2) | merge(L,nil) = L (3) | merge(L as x::xe, Mas y:tys) = @) if xsy then x::merge(xs,ii) (5) else y::merge(L.ys); val merge = fn : int list * int list + int list Figure 2 19: Merging two sorted lists Figure 3.12 defines the function merge. Lines (1) and (2) cover the basis cases. Line (3) begins the inductive step. Here cach list is nonempty, or the pattern match would have stopped at line (1) or line (2). When we assemble the result in lines (4) or (5), sometimes we want to use an entire list and sometimes only the tail. We also need to refer to the head of each list, to tell which is the smaller on line (4). Thus, in line (3) we express the first argument both as L and as x::xs. For instance, if we call merge with first argument [1,2,3], L gets the value (1,2,3], x gets the value 1, and xs gets the value [2,3]. Similarly, in line (3) we express the second arguinent both as M and as yiiys. Then on line (4) we compare the heads. If the head of L is smaller, we assemble the output by taking the head of L and following it by the result of merging the tail of L, expressed by xs, with the entire list M. On line (5) we cover the case where the head of L is not smaller than the head of M. We assemble the result from the head of M — that of the entire list Z and the tail of M. O — followed by the merge Incidentally, the as-construct in Fig. 3.12 is useful but not essential. We could have used x: :xs in place of L and y::ys in place of M. Lines (3) through (5) of Fig. 3.12 would then look like:3.3. PATTERNS IN FUNCTION DEFINITIONS 69 | merge(xiixs, y::ys) = if x
int Notice that the type of the argument is (int * int) list, that is, a list of pairs of integers. The head element in the pattern on the second line is (x,y), 80 x acquires as value the first component of the head pair and y acquires the second component of the head pair. Also, zs in the pattern acquires the tail of the argument as ils value. Example 3.19: Another similar function is sumLists shown in Fig. 3.14. It takes as argument a list whose clements are themselves lists of integers. The purpose is to sum the integers found among all the lists. Notice that ML finds the type of the argument to be int list list, that is, a list whose elements are of type int List. For example, the value of3.3. PATTERNS IN FUNCTION DEFINITIONS 7 sumLists(((1,2], nil, [3,4,5], (6]]) is 21. Here, the argument is a list with four elements: the lists [1,2], nil, (1) fun sumLists(nil) (2) | sumLists(nil (3) | sumLists(( val sumLists = fn : sumLists (YS) = x + sumlists(xs YS); int list list + int Figure 3.14: Summing the elements of a list of lists Line (1) of Fig. 3.14 covers the case where the list of lists is empty and the sum is 0). Line (2) covers the case where there is a first element. on the list, but that clement is itself the empty list. In this case, we can dispense with the head and just sum the integers on the lists of the tail. Line (3) covers the case where there is at least one element on the list that is the head of the list We 1 it the f applying sumLists to the list in which the element x has been removed from the first list, but all other lists are the same. For instance, if the entire list is ((1,2], (3,4]], then the recursive call’s argument is ((2], (3,4]]. 9 As we learn about constructors and the creation of our own datatypes in Section 6.2, we find there are many other ways to construct data structures besides lists (which are constructed by the cons operator ::) and tuples (which are constructed by parentheses and commas). All datatypes make patterus of their own. However, there are some other patterns that make sense but are illegal in ML. For example, we might expect to be able to construct patterns using the concatenation operator @ or arithmetic operators. The next example indicates what happens when we try to do so. Example 3.20: We might expect to be able to break a list into the last element and the rest of the list. For instance, we might try to compute the length of a list by:* fun length(nil) = 0 | Length(xs@[x]) = 1 + length(xs); Error: non-constructor applied to argument in pattern: @ Error: unbound variabie or constructor: xs However, as we can see, the pattern xs@[x] is not legal and triggers two error messages. The first message complains that @ is not a legal pattern constructor. BML does provide a function length that gives the length of a list. It may be implemented by expressing a nonempty list as x::xa and returning i#length (xe)72 CHAPTER 3. DEFINING FUNCTIONS The second message is caused by the fact that, because Ube pattern is flawed, variable xs does not get bound to a value. Therefore, when we encounter it later, in the expression length(xs), ML has no value to use for xs. get a arithmetic operator to construct a pattern. For instance, fun square(0) = | square(x#1) = 1 + 2ex + square(x): is equally erroneous, even though it is based on a correct inductive definition 2 of’, O As a final example of a nonpattern, a real constant cannot appear in pat- terns. For instance, the following function definition fun £(0.0) = 0 | f(x) = is regarded as syntactically incorrect because a real number is not permitted in a pattern.? 3.3.5 How ML Matches Patterns A pattern, like any expression, can be represented by a tree. The outermost, or highest-level, operator is the root of the tree, and it has one child for each operand. The child for an operand ie, § the root of a operand. The basis case, an expression or subexpression that is a single constant, or variable, is represented by a node labeled by that constant or variable. Example 3.21: Consider the pattern cxpression (xtiyrizs, w) This expression has as outermost operator the pair-forming operator, which we shall represent by (,). Its left operand is the subexpression x::y::zs, and the right operand is the subexpression w. The latter is represented by a single node labeled w. The former is grouped x::(y::zs) and is represented by a tree with root operator ::, left child x (a single node) and right child the Toot of a tree representing subexpression y::zs. The entire expression tree is shown in Fig. 3.15(a); for the moment, ignore the curved lines connecting it to Fig. 3.15(b). Similarly, Fig. 3.15(b) represents the expression ([1,2,3,4], 5). The root operator is again the pairing operator (,), and the right child of the root rep- resents constant 5. The left operand is the list [1,2,3,4]. We build lists as The reason for this seemingly strange restriction is that MI. does not allow equality tests between reals; see Section 2.1.4, Without such a test it is impossible to tell whether a given real constant matches the real constant in a pattern,3.3, PATTERNS IN FUNCTION DEFINITIONS 73 /\ /\ /\ rowers La 7 \ /\ nil (a) Figure 3.15: Matching a pattern to an expression = the list consisting of the last element, so there are n uses of the cons operator in a list of length nO ‘To match a pattern and an expression, we overlay the pattern’s tree and the expression’s tree, starting, as a basis step, by matching the roots. For the inductive step, if we have matched nodes N and M of the pattern and expression respectively, then the children of N and M must also be matched in order However, sometimes a match will be impossible, and the pattern-match fails. This situation occurs when we try to match a pattern node that is labeled by an operator or constant, and the matching node of the expression has a different label. Example 3.22: If we try to match the pattern x::xs with the expression nil, we must match operator :: with constant nil at the respective roots, and we fail. If we try to match pattern x: zs with [11 (or as an expression: nil), we match the roots with operators :: successfully. However, at the right children we must match the second :: from the pattern with nil from the expression, and thus we fail. O If we successfully match the pattern with the expression, then any identifiers at the leaves of the pattern tree match nodes that represent subexpressions.4 CHAPTER 3. DEFINING FUNCTIONS These subexpressions become the values associated with those identifiers. Example 3.23: Consider again Fig. 3.15. The pattern in Fig. 3.15(a) suc- cessfully matches the expression in Fig. 3.15(b); the curved lines indicate the correspondence of the nodes. As a result, the node labeled x in the pattern corresponds to the node labeled 1 in the expression, so x acquires the value 1. The pattern node labeled y corresponds to expression node 2, and pattern node zs corresponds to the expression node representing expression 3: :4::nil, or equivalently, the list 3,4]. Finally, the pattern node w corresponds to the expression node 5. 0. 3.3.6 Often we wish to use an identifier with a special meaning like nil in our pat- terns. At this point we have few such special words. But beginning in Sec- tion 6.2. we shall see that. words of this type, called “data constructors,” can he created by the programmer and used in patterns. Such a misspelled word is usu- ally a legal identifier and looks like a pattern that matches anything. SML/NJ treats completely redundant patterns as an error, but other ML compilers may be nil (1) fun reverse(niil) (2) | reverse(x::xs) = reverse(xs) @ [x]; (3) Error: match redundant (4) niil >... 6) 3 ras Figure 3.16: The reverse function with a misspelling Example 3.24: In Fig. 3.16 is the reverse function of Example 3.15, in which we h pelled nil (1). We Hines (3) through (5) the SML/NJ response. The system has detected the pattern niil will match any argument, and therefore the pattern x: :xs on line (2) can never be reached. The single arrow at the beginning of line (5) indicates which pattern is redundant. a 3.3.7 Exercises for Section 3.3 wing functivus fom previous exercises, using two or more patterns in each. * a) The factorial function of Exercise 3.2.1(a). b) The function from Exercise 3.1.1(f) that cycles a list one position. If the list is empty, return the empty list.3.3. PATTERNS IN FUNCTION DEFINITIONS 75 c) The function from Exercise 3.2.1(b) that cycles a list ¢ limes, where i, as well as the list, is a parameter. * d) The function from Exercise 3.2.1(c) that duplicates each element of a list. e) The function from Exercise 3.2.1(d) that computes z'. * £) The function of Exercise 3.2.1(e) that computes the largest of a list of reals. ! Exercise 3.3.2: Write a function that flips alternate elements of a list. That is, given a list [ay,a2,...,aq] as argument, produce [ag, a1, 04,03, 06,05, -. J. If ! Exercise 3.3.3: Write a function that, given a list L and an integer i, returns a copy of L with the ith element deleted. If the length of L is less than i, return Tr Exercise 3.3.4: Show the sequence of calls to sumLists (as defined in Fig. 3.14) and the bindings to variables af patterns that occur when we call sumLists(({1,2] ,nil, [3]]) Exercise 3.3.5: Does the pattern of Fig. 3.15(a) match the following expres- sions? If so, give the value bindings for each of the variables x, y, 2, and w * a) (La","b","c"] Da" "e"]) b) ({"a","b"] 4.5) *c) ((5), (6,71) Exercise 3.3.6: Draw trees as in Fig. 3.15 to show how the pattern C(x, y) 28] matches the expression [((1,2) ,3)]. Exercise 3.3.7: There is a recursive definition of the square of a nonnegative integer: 0? = 0 (basis), and n? = (n — 1)? + 2n — 1 (inductive step for n > 0). Write a recursive function that computes the square of its argument using this inductive formula. Exercise 3.3.8: Write a function that takes a list of pairs of integers, and orders the elements of each pair such that the smaller number is first. Use the as construct, so you can refer to the pair as a whole when it is not necessary to change it.* 76 CHAPTER 3. DEFINING FUNCTIONS Exercise 3.3.9: Write a function that takes a list of characters and returns true if the first element is a vowel and false if not. Use the wildcard symbol _ whenever possible in the patterns. Exercise 3.3.10: The simple rule for translating into “Pig Latin” is to take a word that begins with a vowel and add "yay", while taking any word that begins with one or more consonants and transferring them to the back before appending "ay". For example, "able" becomes “ableyay" and "stripe" be- comes "ipestray". Write a function that converts a string of letters into its Pig-Latin translation. Hint: Use explode and the function from Exercise 3.3.9 that tests for vowels. Exercise 3.3.11: Suppose we represent scts by lists. The members of the set may appear in any order on the lisi, but we assume ihai there is uever inure than one occurrence of the same element on this list. Write tunctions to pertorm the following operations on sets. * a) member(x,S) returns true if element x is a member of set S; that is, appears somewhere on the list representing S. b) delete(x,S) deletes x from S$. Remember that you may assume that x appears at most once on the list for S. *c) insert(x,S) puts z on the list for S if it is not already there. Remember that in order to preserve the condition that there are no repeating elements on a list that represents a set, we must check that x does not already appear in S; it is not adequate simply to make x the head of the list. Exercise 3.3.12: Write a function that takes an element a and a list L of lists of elements of the same type as a and inserts a onto the front of each of the lists on the list L. For example, if a = 1 and L is ((2,3],(4,5,6] ,nil), then the result is [[1,2,3], [1,4,5,6], (12). Exercise 3.3.13: Suppose sets are represented by lists as in Exercise 3.3.12. ‘Lhe power set of a set S is the set of all subsets of S. A set of sets can be represented in ML by a list whose elements are lists. For example, if S is the set {1,2}, then the power set of S is {0, {1}, {2}, {1,2}}, where 0 is the empty set. This power set can be represented in ML by the list of lists (nil, (1), (2), (1,2]]. ‘That is, the elements of the lists are themselves lists, each representing one of the subsets of S. Write a function that takes a list as argument, representing some set S, and produces the power set of S. Hint: Recursively construct the power set for the tail of the list and use the function from Exercise 3.3.12 to help construct the power set for the whole list. ! Exercise 3.3.14: Write a function that, given list of reals [a;,a2,...,@n), computes Tic; (ai - a3)3.4. LOCAL ENVIRONMENTS USING LET 7 That is, we compute the product of all differences between elements, with the element appearing later on the list subtracted from the element appearing first. If there are no pairs, the “product” is 1.0. Hint: Start by writing an auxiliary Exercise 3.3.15: Write a function to tell whether a list is emply. That is, return true if and only if the argument is an empty list.!° Exercise 3.3.16: Explain how ML deduces that the function sumPairs of Example 3.18 has domain type (int * int) list 3.4 Local Environments Using iet Sometimes we need to create some temporary values — that is, local variables — inside a function. The proper way to do so is with a let --- in --- end expression. A simplified form of this expression, where only val-declarations are used, is shown in Fig. 3.17. let val
=
; val
=
; val
=
in
end Figure 3.17: Simple form of the “let” construct. That is, following the keyword let is a list of one or more val-declarations, just like those introduced in Section 2.3.3. These are followed by the keyword in. Following in is an expression that may use the variables defined after let. This expression may also use any other variables accessible in the environment in which the function using let is defined, provided their identifiers are not redefined by the temporary declarations between let and in. The keyword end completes the expression. Here are a few important points to remember about let expressions: ‘© Semicolons following the declarations are optional. We shall adopt Pascal style and follow each but the last by a semicolon. «Just as for val-declarations use the keyword val. in the top-level environment, don’t. forget to 1O-There is a built-in MI. function nu11 that does this task. We should not use this function in the solution.78 CHAPTER 3. DEFINING FUNCTIONS © We must not omit the keywords in and end, which are as essential as the let. « In truth, the let expression is more general than is suggested by Fig. 3.17, and any “declaration” can appear where we have shown val-declarations. So far, we have not seen any other kinds of declarations besides val- declarations and function declarations (with the keyword fun). However, there are several others; for example, we shall meet exception declarations in Section 5.2. The complete syntax for declarations is in Fig. 9.19. @ As another generalization, a pattern may appear in place of a single iden- Lifier in any val-declaration. Also, more than one expression may appear atter the let, although the utility of an expression list wili not become apparent, until we study side-effects in Section 4.1.3. 3.4.1. Defining Common Subexpressions One use of a let expression is to allow us to use common subexpressions. The following example illustrates the technique. ber . We could write the expression xx* -+- =x(100 2's) if we had the pa- tience, but it is less tedious and less prone to error if we write the function in Fig. 3.18. fun hundredthPowes (a:veal) = let val four = x#x*x*x; val twenty = four+four+four+four+four in twenty*twenty*twenty*twenty*twenty end; val hundredthPower = fn : real + real hundredthPower (2.0); val it = 1.2675060022823E30 : real hundredthPower (1.01); val it = 2.70481382942153 : real Figure 3.18: Raising a number to the 100th power In Fig. 3.18 we define two local variables, four and twenty (no jokes about blackbirds, please). We first define four to be 2‘, and then define twenty to3.4. LOCAL ENVIRONMENTS USING LET 79 De four raised to the fifth power, or 2°, Finally, we use twenty in the final expression after the keyword in, which is twenty raised to the fifth power, or 100 7100, 00. hitch is at ) Which is about 10%, and then computing (1.01)'°°. The latter value is close to € = 2.718-+-, as it must be because e is the limit as n goes to infinity of (1+1/n)". oO We then sec two uses of this function, 3.4.2 Effect on Environments of let When we enter a let expression, an addition to the current environment is created, adding value bindings for all the identifiers defined between the lat. and the in. twenty 1049576.0 added for let-expression four 16.0 ot | added on call x 2.0 to hundredthPower environment before call to hundredthPower Figure 3.19: Additions to environment when hundredthPover is called Example 3.26: In Fig. 3.19 we see the situation when the function of Fig. 3.18 is called. The first addition is for the function call; it is a binding for the parameter x. The next additions are for the let expression and include bindings for the local variables four and twenty. We have shown x bound to the value 2.0 in the call and the local variables bound to their consequent values. As always, when the function call returns, the additions to the environment disappear. However, the returned value is made available as the value of the function in the environment that results after the return. O Exampie 3. can rewrite Fig. 3.18 to use x not oniy as the agi of the function hundredthPower, but also as both local variables. The function then appears as in Fig. 3.20. It behaves exactly like the function of Fig. 3.18. However, the additional bindings in Fig. 3.21 each associate the variable x with avalue. O80 CHAPTER 3. DEFINING FUNCIIONS fun hundredthPower (x:real) = let val x = xexexex; val x = xexexexex in IMI end; val hundredthPower = fn ; real + real Figure 3.20: Repeat of Fig. 3.18 with x used for all variables added for second x 1048576.0 val-declaration added for first x 16.0 val-declaration added on call x 20 to hundredthPower environment before call to hundredthPower Figure 3.21: Additions to environment corresponding to Fig. 3.20 3.4.3 Splitting Apart the Value Returned by a Function Another important use of let expressions is when the result of a function has components or parts that we want to separate before we use them. In particular, when the type of the value returned by a function is a tuple, we can get at the components by a more general form of val-declaration than we suggested was possible in Fig. 3.17. Instead of a single identifier following the word val, we can have any pattern. For instance, if a function f returns a three-component tuple, we could write val (a,b,c) = £(... aid have ihe dee compunenis uf the resuli of f bound iv variables a, », aud c respectively. This approach is often more convenient than writing val x = f(... which associates the entire tuple with x, and then extracting the individual components with #i operators in subsequent val-declarations such as3.4. LOCAL ENVIRONMENTS USING LET 81 Patterns for Lists of Length 1 Note that the way we express “list of length 1” as a pattern is to put square brackets around a single identifier, like [a] in line (2) of Fig. 3.22. Such a pattern can only match a list with a single element, and variable a acquires that element as its value. Another way to express “list of length 1” is with the pattern a::ni1 Again, a acquires the lone clement as its value. val a = #1(x); Example 3.28: Let us implement a function split(L) that takes a list L and splits it into twa lists. One list. consists of the first element, third element, fifth element, and so on; the other list consists of the second element, fourth element, sixth element, and so on. This function has an important application. In tandem with the function merge of Fig. 3.12, it lets us write a function geSor in Section 3.4.4. ‘We want the function split to produce a pair of lists. The recursion consists of two basis parts and an inductive part. BASIS: If L is empty, then produce a pair of empty lists. If L has a single element, the first list of the pair produced has that element and the second iist is empty. INDUCTION: If the given list has two or more elements, let. the first. two ele- ments be a and 6. Recursively split the remaining elements into a pair of lists (M,.V). ‘Lhe desired result is the pair of lists (a :: M, b:: N). ‘That is, the first list has head @ and tail equal to the first of the returned lists, and the second has head 6 and tail equal to the second of the returned lists. An ML implementation of sp1it is shown in Fig. 3.22. Line (1) implements the first part of the basis: return a pair of empty lists in response to the empty list. Line (2) implements the second part of the basis, where the given list has length 1. Lines (3) through (5) handle the inductive case. ‘Ihe pattern line (3) can only match a list with at least two elements; a acquires the first clement as valuc, b acquires the second, and ¢s acquires the list of the third and subsequent elements as its vaiue. in iine (4), we appiy split recursively to the third and subsequent elements; the result is bound to the pair (M,N). ‘That is, Mis bound to the first component of the result, which is the elements in positions 3, 5, 7, and so on of the original list. N acquires the second component of the return value, which is the elements in positions 4, 6, 8, and so on from the original list.82 CHAPTER 3. DEFINING FUNCTIONS (4) fun eplit(nil) = (nil,nil) (2) | split(fa]) = (fa],nil) (3) | split(a: s) = let (4) val (M,N) = split(cs) in (5) (ariM, b::N) end; val split = fn: ‘a ist > “a list * ‘a list split((1,2,3,4,5]); val it = ([1,3,5],[2.4]) : int list * int list Figure 3.22: Splitting lists ally, in Tine (5) we construct the retnrn value for the present call to split. The first component has head a — that is, the first element of the given list — followed by M, the list of all the other odd-position components. Thus, the first component is the odd-position elements in order. Similarly, the second component b::N is all the even-position clements. O 3.4.4 Mergesort: An Efficient, Recursive Sorter We can combine the functions merge of Fig. 3.12 with split of Fig, 3.22 to sort lists of integers. This algorithm is one of the simplest ways to sort n elements in time proportional to nlogn steps. We shall not develop the analysis of this algorithm here, but we shall complete the specification of the algorithm in ML. The idea behind the mergesort algorithm is expressed in the following induction. BASIS: If the given list L is empty or consists of a single element, then L is surely sorted already, so just return L. INDUCTION: If L has at least two elements, split L to produce the (approxi- mately) half-size lists M and N. Recursively mergesort M and N. ‘Then merge the sorted lists M and N to produce the sorted version of L. The function mergeSort is shown in Fig. 3.23. It must be preceded by the funciious merge and split io form ihe complete impiemeniation of the mergesort algorithm. Incidentally, ML discovers that mergeSort works only on integer lists because it uses merge, which we wrote to work only for integer lists. Lines (1) and (2) implement the basis; the remaining lines are for the induc- tive step. Line (4) splits the given list. Lines (5) and (6) sort the half-sized lists, and the result is produced by merging the sorted lists in line (7). Incidentally,* 3.4. LOCAL ENVIRONMENTS USING LET 83 (1) fun mergeSort (nil) = nil (2) | mergeSort([a]) = [al (3) | mergeSort(L) = let «) val (M,N) = eplit(L); (5) val M = mergeSort(M); (6) val N = mergeSort (N) in mM merge (M,N) end; val mergeSort — for Figure : Mergesort we could also have combined some steps by eliminating lines (5) and (6) and replacing line (7) by merge (mergeSort (N) ,mergeSort (M)). 3.4.5 Exercises for Section 3.4 Exercise 3.4.1: Write a succinct function to compute 1°, the components of pair x as needed. Exercise 3.4.3: Improve upon the power-set function of Exercise 3.3.13 by using a Let expression and computing the power set of the tail only once. Exercise 3.4.4: Improve upon the function of Exercise 3.2.1(e), to compute the maximum of a list of reals, by using a let expression. Hint: Compute the wmaxiniuis of tae ‘ail of Une Lisi firsi. Exercise 3.4.5: Write a function to compute 2”' for real x and nonnegative integer i. You should make only one recursive call in your function. Hint: Note that we can start with « and apply the squaring operation i times. For cxample, when i = 3, we compute ((x*)*)? Exercise 3.4.6: Write a version of sumPairs of Example 3.18 that sums each component of tie pairs separately, returning a pai f the eum of the first components and the sum of the second components. Exercise 3.4.7: Write a function that takes a list of integers as argument and returns a pair consisting of the sum of the even positions and the sum of the odd positions of the list. You should not use any auxiliary functions.84 CHAPTER 3. DEFINING FUNCTIONS 3.5 Case Study: Linear-Time Reverse We have seen two versions of a function to reverse lists: first in Fig. 3.3 and then in Evample 215. These finetions each seem simple enough, but. they suffer from a common flaw that they take time proportional to n? to reverse lists of length n. In comparison, a well-designed reverse function, such as the function rev in the ML top-level environment, can reverse lists of length n in time proportional to n. In this section, we shall see how to write a list-reverse function that is efficient and learn a general Lechnique for progranuning with lists as we do. 3.5.1 Analysis of Simple Reverse Let us begin by understanding why a function like that of Example 3.15 takes time proportional to n?. The function is reproduced here for reference: fun reverse(nil) = nil | reverse(x::xs) = reverse(xs) @ [x]; Suppose T(n) is the time it takes reverse to work on a list of length n. We can develop a recurrence relation, where T(n) is defined in terms of T(n — 1) and then “solve” the equation for T(n), to get an expression for T(n) in terms: of nalone (not T). BASIS: The basis case is when n = 0; that is, the list is empty. In this case the first pattern, nil, matches, and nil is returned. The whole process takes only some constant amount of time, so we shall say T(0) = a for some constant a. INDUCTION: Suppose n > 1. There are a number of steps that the program will go Uhrough to process a list of length n 2 1: 1. ‘The first pattern doesn’t match, and it takes some constant amount of time for the ML system to determine that nil doesn’t match the argu- ment. 2. It takes another constant amount of time to match the pattern x::xs and assign the head of the list to x and the tail to xs. 3. It takes time T(n — 1) to compute the value of reverse (xs). The reason is that xs is surcly a list of length n 1 if x::x8 is of length n. 4. To compute the return value reverse(xs) @ [x] requires that we copy the list reverse(xs) and append the final element x as we do. ‘This process takes time proportional to n, the length of the resulting list. The constant time taken by the first two steps is dominated by the linear time taken by the last step. Thus, T(n) is approximately T(n — 1) + bn for some constant 6; the T'(n — 1) represents the time for the recursive call and bn represents the time for the other steps. The recurrence equation is thus:3.5. CASE STUDY: LINEAR-TIME REVERSE 85 T(0) =a T(n) = T(n— 1) + 6n for n = 1,2,... There are several ways to solve this equation. Perhaps the simplest to to check that T(n) = a + bn(n + 1)/2 satisfies the equations and is therefore the solution. Since a + bn(n + 1)/2 is proportional to n? as n gets large, we see the justification for our claim that reverse takes time proportional to n? on lists of length n A more intuitive arguinent is Lo observe that ou a list of leugih n, reverse gets called recursively on lists of length n—1, n—2, and so on, down to 0. Each call on a list of length i results in work bi for some constant b, except the call 1 bi =a + bn(n + 1)/2 which, as we observed, is proportional to n? 3.5.2 ML’s Representation of Lists be de: it takes time proportional to the length of the first list to concatenate lists, we can cons a head and a tail in constant time. Thus, before giving the proper design for reverse, we must understand something of how ML represents lists internally. Lists are represented in a conventional, linked list fashion, as suggested by Fig. 3.24. Cells consist of a pair of pointers, the first to an element of the list and the second to the next cell. If a list is bound to a variable L, then in the ML environment there is an entry in which the identifier L is associated with a pointer to the first cell of the list. Figure 3.24: Representing a linked list ixe given values of head x and tail Suppose we wish to construct the list x: s to the xs. We have only to create a new cell C. The first pointer of C point: head of the list, that is, to the value of x, and the second pointer in C points86 CHAPTER 3. DEFINING FUNCTIONS lo the value of xs. In this way, C becomes the first cell on the linked list tat represents the value of x::xs. The process is suggested by Fig. 3.25. Notice that creating cell C and setting its pointers to refer to the values of x and xs. is. There is no need for ML to “look inside” the values of the head or tail. Similarly, we can invert this process. In constant time we can find the head and tail of a list. No copying is necessary. SOE AU Vv ce ff Tail Head ——) Figure 3.25: Applying the cons operator 3.5.3 A Reversal Function Using Difference Lists ‘There is a trick known wo LISP programmers as difference fists, in which one manipulates lists more efficiently by keeping, as an extra parameter of your function, a list that represents in some way what you have already accomplished. The idea comes up in a number of different applications; we hope that seeing it used to reverse lists will illustrate the technique sufficiently that its use will be apparent when you need it.3.9. CASE STUDY: LINEAR-TIME REVERSE = We design an auxiliary function rev1(L,M) whose job is to return L? AL, that is, the reverse of list L followed by the list M (not reversed). Note that we use the superscript R as a convenient way to indicate the reverse of a list. y if o ha. “ el 5 If we w , We have only to call revi (Lyn: result is L® concatenated with the empty list, which is just L®. (1) fun revi(nil, M) = M (2) | revi(x::xs, ys) = revi(xs, x::ys); val revi = fn: ‘a list * ‘a list > ‘a list fun reverse(L) = revi(L,nil); val reverse = fn: ‘a list + a list Figure 3.26: List reversal using difference lists Figure 3.26 shows the function revi and its use to define a linear-time list reversal function. Line (1) of rev1 handles the basis case. when there is nothing left to reverse. Then, the result is just a copy of the second argument. Line (2) handles the inductive case, where we need to reverse a list of one or more elements. We move the head of the list we need to reverse to the beginning of the list that is not to be reversed. We then call revi recursively on the new pair of lists. Eventually, all the elements of the first list are moved to the front of the second list, in reverse order. At that point, the basis case applies and the recursion ends. To see why the technique works, suppose we call revi([ar,a2,.++,an], [b1,b2,-++50m}) Then the desired output is the list [an,a@n—1,-+.,@1,01,b2,-..;m]. When we move the head of the first list to the second, we call bm]) The result of this call is [an.an—1,...,.@2] followed by the element ai, followed by [bi,b2,---;6m], which is the result desired for the original call to rev1. revi([a2,a3,...,4n], [ar,b1,b2, Example 3.29: Herc is the sequence of calls that results when we try to reverse the list (1,2,3]: reverse((1,2,3]) revi([1,2,3], nil) revi([2,3], [1]) revi((3], [2,1]) revi(nil, [3,2,1]) At this point, the basis applies, and the result [3,2,1] is produced. O" 88 CHAPTER 3. DEFINING FUNCTIONS 3.5.4 Analysis of Fast Reverse We can argue that the reversal programm of Fig. 3.26 takes time proportional to the length of the list, as follows. 1. Function revi calls itself with a first argument that is shorter by 1 than its parameter, so with a first argument of length n, rev1 makes n recursive calls 2. Each recursive call to revi takes a constant amount of time lo break apart a head and tail and then cons a head and tail, until we get to the basis (1) of Pig. 3.96. The 3. Thus, revi takes time proportional to the length of its first argument. 4. The time taken by reverse on a list of length n is essentially the time taken by revi when given a first argument of length n. Thus, reverse takes time proportional to the length of the list it is to reverse. 3.5.5 Exercises for Section 3.5 Exercise 3.5.1: Write a function cat(L,M) that produces the concatenation LOM of the lists L and M. However, your function should not use the @ operator; only the cons operator :+ should he used. Your funetion mst run in time ne length of L, inc pi Exercise 3.5.2: Write a function cycle(L, i) that cycles list L by i positions, as in Exercise 3.2.1(b). Ilowever, your function must take time proportional to the length of L (which we assume is at least i). Hint: You need to break this function into a sequence of steps performed by auxiliary functions. 3.6 Case Study: Polynomial Multiplication In this section we shall show one useful way to represent polynomials in a single variable. We shall consider ways to perform polynomial multiplication, which is also the important signal-proccssing operation known as convolution. We begin with some simple functions that get the job done, but take time proportional to n? to multiply polynomials of length n. Then, we exhibit a more complicated algorithm that mult wal to n!-59. We do not show the algorithm that is asymptotically most efficient — the “Fast. Fourier ‘Transform” approach. ‘That algorithm takes time proportional to n logn."! ¢ Aho, Hoperaft, and Ullman, Design and Analysis of Computer Algorithms, Addison- . 1974, for a disenssion of efficient polynomial multiplication, including both the FFT. and the Karatsuba-Ofman approach discussed in Section 3.6.5.3.6. CASE STUDY: POLYNOMIAL MULTIPLICATION 89 3.6.1 Representing Polynomials by Lists We shall use lists of reals to represent polynomials by their coefficients, lowest degree first. For instance, the polynomial «* + 42 — 5 is represented by the list (5.0, 4.0, 0.0, 1.0]. In general, the polynomial 7, aiz' is represented by the list of n + 1 elements [a9,@1,...,2,,]. Conventionally, we shall take the empty list to represent the polynomial 0, but this polynomial also has other representations such as [0.0] and [0.0, 0.0]. An important observation is that if L is a list representing polynomial P, and L is of the form a : M (that is, L has head a and tail M), and the tail represents polynomial Q, then P = a+Qz. That is, multiplication by z in effect shifts the elements of the corresponding list. one position right. For instanco, if P=a 440-5 then we observed that the representing list is [“5.0, 4.0, 0.0, 1.0]. Thus, ais ~5.0and M is [4.0, 0.0, 1.0]. M represents the polynomial Q = x* +4. Note that P = a+ Qz, that is, P= —5 + (2? +4)2. In Fig. 3.27 we see three functions that perform common operations on polyno- mials in this representation. The first, padd (P,Q), adds polynomials P and Q. We recursively define the sum of two lists P and Q that represent polynomials by: BASIS: If either P or Q is the empty list, then the sum is the other. Note that if both are empty, the result is the polynomial 0 represented by the empty list. INDUCTION: For the induction, assume that neither list is empty. Suppose P has head p and a tail representing polynomial R, while Q has head q and a tail representing polynomial S. Then the sum P + Q is the list with head element p+4q and tail equal to the result of applying padd to the two tails. The nd Q = 44 Sr, then oft P+Q=(ptq)t+(K+S)x In line (1) of Fig. 3.27 we sce one part of the basis. Whenever the second polynomial is the empty list, the result is the first polynomial. Line (2) handles the other part of the basis. If the first polynomial is empty, the result is the second. neither of the first two patterns match the argument both polynomials are nonempty lists. Thus, in line (3) the pattern to match the first argument, and q: :qs will surely match the second argument. Notice we have attached type real to the variable p of this pattern. That is enough for ML to figure out the type of all variables and to disambiguate the use of + in line (3).90 CHAPTER 3. DEFINING FUNCTIONS ‘As a result of the match, p acquires the value of the first element of the first polynomial, and q acquires the value of the first element of the second polynomial. Their sum becomes the first element of the result, and padd is (= padd(P,Q) produces the polynomial sum P+Q +) (1) fun padd(P,nil) = P (2) | padd(nil,Q) = Q (3) | padd((p:real)::ps, q::qs) = (ptq)::padd(ps,qs); (* smult(P,q) multiplies polynomial P by scalar q *) (A) fun emult(nil,q) = nil (5) | smult((p:real)::ps,q) = (p+q): :smult(ps,q) 5 (+ pmuii(?,G) produces PG +: (6) fun pmult(P,nil) = nil (7) | pmult(P,q::qs) = padd(smult(P,q), 0.0::pmuit(P,qs)); Figure 3.27: Polynomial addition and wultiplication In lines (4) and (5) of Fig. 3.27 we see the function smult that multiplies a polynomial P by a scalar q. That is, each term in the polynomial is multiplied by q. ‘The recursive definition of this operation is: BASIS: If P is empty, then the product is the empty list representing U. INDUCTION: If P has head p, then the head of the result is pq. The tail of the result is found by recursively applying smult to the tail of P and the scalar q. Line (4) handles the basis and line (5) handles the inductive step. The justification for this algorithm is that if P =p 4 Rx, then Pq = pq | Raz. Now let us consider the function pmu1t of lines (6) and (7) of Fig. 3.27. This function multiplies polynomials P and Q using a recursion on the length of the second polynomial. BASIS: If the second polynomial is empty, then the result is empty. INDUCTION: If the second polynomial Q can be written as q+ S:r, then PQ=Pq+PSu ‘The product Pq is a scalar multiplication. PS is a recursive application of the polynomial multiplication with a smaller second argument.3.6. CASE STUDY: POLYNOMIAL MULTIPLICATION 91 The basis is implemented by line (6). In line (7) we see the inductive step; smult (P,q) produces Pq, while pmult(P ,qs) produces the polynomial product we called PS in the inductive formula above. To multiply this product. by 2, that rep, that rep- resents PS. That shift is the purpose of the subexpression 0.0: : pmult (P,qs). Finally, we use padd to add the lists representing Pq and PSz. 3.6.3 Analysis of Simple Multiplication Let us analyze the running time of each of the three functions in Fig. 3.27. Analysis of padd First, we claim that the function padd takes time proportional to the shorter of its two arguments. To see why, observe that both arguments decrease in length Ly 1 at cach recursive call un line (3) of Pig. 3.27. When either agument reaches length 0, line (1) or (2) will stop the recursion. ‘hus, the number of recursive calls equals the length of the shorter argument. Ilowever, the work done at each call, exclusive of the recursive call, takes only a constant amount of time independent of the lengths of the lists. As we discussed in Section 3.5.2, each of the pattern-matching steps is done without. looking past the first cons operator, and building the result in line (3) by ap- plying the cons operator likewise takes a constant amount of time. Finally, the addition p + q at line (3) also takes constant time independent of the list hy Our conclusion is that padd takes time that is a constant per call times the number of calls, which is the length of the shorter list. That is, the time for padd is proportional to the length of the shorter list. Analysis of smult The number of recursive calls is equal to the length of the list in the first argument, because the leugil decreases by one ai each call, aud when the be reaches 0), the first pattern, in line (4), matches and the recursion stops. As for padd, it is easy to see that the only steps performed at each call, other than the recursive call, are constant-time operations of matching a cons operator or nil, applying a cons operator, and an arithmetic step, multiplying two integers. The running time of smu1t is thus proportional to the length of its first argument — the polynomial being scalar-multiplicd. Analysis of pmult Suppose we execute pmult (P,Q), where P and Q are polynomials (lists) of length n and m, respectively. Since the recursion is on the second argument, whose length decreases by 1 at each call, the number of recursive calls is m. We must calculate the work done at each call, exclusive of the recursive call.y2 CHAPTER 3. DEFINING FUNCTIONS 1. The pattern matching at lines (6) and (7) takes constant time. The call to smult at line (7) takes time proportional to n. The cons with head 0.0 at line (7) takes constant time. Be N ‘The application of padd at line (7) takes time proportional to its shorter argument. The first argument is always of length n (it is a scalar mul- tiplication of polynomial P), while the second argument is never shorter than n. Thus, the application of padd takes time proportional to n. The calls to smult and padd dominate the work, which is thus proportional to n at each call. Since there are m recursive calis to pmuit, the total work is nm for polynomials of length n and m, respectively, or n? if the polynomials are of the same length n. 3.6.4 Auxiliary Functions for a Faster Multiplication It turns out that polynomial multiplication of length-n polynomials does not have to take time proportional to n®. If we use the “fast Fourier transform,” we can actually do the job in time proportional to nlogn. We shall not give this algorithm here. Rather, we shall show an intermediate approach that takes time proportional to n° (the constant 1.59 approximates log, 3) called the Karatsuba-Ofman algorithm. To begin, there are a number of auxiliary functions that we shall need. We show them first and analyze their running times. The functions are shown in Fig. 3.28. The Function psub The purpose of psub(P,Q) is to compute P — @ for polynomials P and Q. It does so by negating Q, i.e., scalar-multiplying it by —1, and then adding. The running time of this function is no greater than the length of the longer polynomial, since the call to smut Lakes Lime proportional to the length of Q and the call to padd takes time proportional to the shorter length. The Function length This function, taking the length of a list, is actually a built-in function of ML. However, we write it here so we can confirm its running time. Notice that the number of recursive calls equals the length of the list to which it is applied, and the work done at each call is a constant, independent of the list. Thus, length requires time proportional to the length of its argument. ‘The Function bestSplit ‘This function serves a technical purpose that will become clear when we see how the Karatsuba-Ofman algorithm works. The arguments n and m are the3.6. CASE STUDY: POLYNOMIAL MULTIPLICATION 93 (* psub(P,Q) computes the difference of polynomials P-Q *) y= @ (* length(P) computes length (degree+1) of polynomial P «) fun length(nil) = 0 1 1+length(ps) ; (* bestSplit(n,m) computes an appropriate size for the low-order "half" of polynomials of length n and m. It is the smaller of n and m should one be less than half the other. If they are approximately the same size, then it is half the larger. *) fun bestSplit(n,m) = if Qen <= m then n else if 2«m <= n then m else if n <= m then m div 2 else (* n/2
... (ps psn) > « The compiler has correctly pointed out that we have made an assumption about the relationship between the arguments P and n of carve(P,n): n will never be greater than the length of P. Thus, the first pattern looks for n = 0, and the second pattern assumes that if n > 0, then P must not be nil. If we were to call carve(nil,n), where n > 0, then neither pattern would match and the function would fail. Fortunately, when we use carve in the Karatsnha-Ofman algorithm, onr assumptions are certain to be met. However: © It is generally a bad practice to write functions whose patterns do not. caver all possible cases, even cases for which the function was not intended. the polynomials. That is, we can write a recurrence equation for T(n) the time it takes to multiply polynomials of length n using Formula 3.1 directly: T(l)=a T(n) = 4T(n/2) +n The solution to this equation is (a +b)n? — bn O= Vv Figure 3.29: Breaking polynomials into half-sized niecoe96 CHAPTER 3. DEFINING FUNCTIONS That is, T(n) is proportional tu n”, exactly as for the straightforward polyno- mial multiplication method. To design a faster algorithm, we need to reduce the number of times we mul- number of operations that take time linear in the size of the polynomials, such as adding or subtracting polynomials, “shifting” (multiplying by a power of z), or “carving” polynomials into two. We can reduce the number of half-siced mulliplivativus tu hive if we cum pute TV and UW as in Formula 3.1, but write the middle term as: TW +UV =(T+U)(V+W)-TV-UW (3.2) Since TV and UW are already computed, Formula 3.2 uses only one additional half-sized multiplication, (T+U) times (V +1), rather than the two additional multiplications nceded if we computed TW + UV directly. Notice that the fact Formula 3.2 uses two additions and two subtractions in place of a single addition is not a real problem. Intuitively, multiplication takes time that grows faster than linear in n, so the cost of the multiplications swamps out the cost of the additions for large n. (* komult(P,Q) computes the product of polynomials PQ using the Karateuba-Ofman method that only calls itself three times rather than four on half-sized polynomials. *) (1) fun komult(P,nil) = nil (2) | womult(nil,Q) = nil (3) | komult(P,[q]) = smult(P,q) (4) | komult ([p],Q) = smult(Q,p) | (8) komult (P,Q) = let (6) = length(P); wm v = length(q); (8) val s = bestSplit(n,m); @) val (T,U) = carve(P,s); (10) val (V,W) = carve(Q,s); (41) val TV = komult(T,V); (12) val UW = komult(U,W); (13) val TUVW = komult(padd(T,U), padd(V,W)); (14) val middle = psub(psub(TUVW,TV), UW); in (15) padd(padd(TV, shift (middle,s)), shift (UW,2*s)) end; Figure 3.30: The Karatsuba-Ofman multiplication algorithm.3.6. CASE STUDY: POLYNOMIAL MULTIPLICATION 97 Figure 3.30 implements this idea in a recursive ML function. Lines (1) and (2) handle the basis cases where one of the polynomials is the empty list. In these cases, the empty list is returned. Lines (2) and (4) handle additional a polynomial time scalar multiplication algorithm to is a constant, so we can use the linear handles these cases. Line (5) begins the inductive case. We use each of the auxiliary functions fium Fig. 3.28 at Icast once in a sequence of val-declarations. Lines (6) aud (7) compute the lengths of the two polynomials, and line (8) picks the value of s using the bestSplit function. The role of s, the length of the low-order pieces T and V, was illustrated in Fig. 3.29. Then, iines (9) and (i0) divide the two poiynomiais into low-order and high-order pieces, as suggested by Fig. 3.29. Line (11) computes the first. half. sized product, TW, and line (12) computes the second: UW’. Lines (13) and (14) implement the expression of Formula 3.2. That is, line (13) computes (I +U)(V + W), and line (14) subtracts from this expression the terms TV and UW Finally, the result of the function is computed in line (15). This expression implements Formula 3.1. However, the middle term, TV + UW, has been computed by Formula 3.2, rather than directly. 3.6.6 Analysis of the Karatsuba-Ofman Algorithm We can show that the dominant cost of the algorithm of Fig. 3.30 is the three halt-sized multiplications. Let T(n) be the running time of this function on two polynomials of length n. For the hasis, where n = 1, one of the basis cases of lines (3) or (4) applies. The running time is thus some constant, say T(1) = a. For the induction, let n > 1. Then the inductive case starting at line (5) applies. The following is a list of the running times for each of steps (6) through (15): 6: Proportional to n. 7: Proportional to n 8: Constant. 9: Proportional to n. 10: Proportional to n. 11: T(n/2). 12: T(n/2). 13: A term proportional to n for the calls to padd plus T(n/2) for the call to komult.98 CHAPTER 3. DEFINING FUNCTIONS 14: Proportional to n. 15: Proportional to n. The sum of the times is thus 3T(n/2) plus a term that is proportional ton. We may write the recurrence equation as: T(L T(n) = 3(n/2) +n The solution to this equation is T(n) = (a + 20)ni625 — zon as you may check by substitution in both equations. Thus, the running time of the Karatsuba-Ofman algorithm is proportional to n!°#2, or n!-®9, significantly less than the n* of more straightforward algorithms. 3.6.7 Exercises for Section 3.6 Exercise 3.6.1: Write a function genPoly(n) that generates a polynomial of length n (degree n — 1), all of whose coefficients are 1.0. Measure the running time of the straightforward algorithm pmult of Fig. 3.27 and the algorithm of Fig. 3.30 with its attendant auxiliaries from Figs. 3.27 and 3.28. The code can Consider polynomials of length n aPoly. fr does the running time of komult drop below the running time of pmult? Exercise 3.6.2: One problem with komult is that for small n it wastes time, compared with the straightforward approach to polynomial multiplication. Re- write komult so it calls pmult to multiply polynomials whose length is below some limit. Experiment with running times as in Exercise 3.6.1 to find the limit below which it makes sense to use pmult, and adjust your function accordingly. Exercise 3.6.3: Write a function to evaluate a polynomial at a given real value a. That is, define a function eval (P,a) that takes a list (polynomial) P and a real number a, and computes P(a). Exercise 3.6.4: Given a list of reals [a1,a2,...,@n), find the polynomial whose roots are a1,42,...,@,. Hint: Note that this polynomial is the product of (2 -aj) for i=1,2,...,0. Exercise 3.6.5: We can represent polynomials in two variables, x and y, by a list of lists. Think of such a polynomial as a polynomial in x, whose coef- ficients, instead of being real numbers, are polynomials in y. Represent these polynomials in y by lists as we did in Section 3.6.1. Then use the lists repre- senting these polynomials as the elements of a list representing the polynomial3.6. CASE STUDY: POLYNOMIAL MULTIPLICATION 99 in «. For example, the polynomial 1+ 2xy + 3xy? + 4x%y can we written as 1+ (2y + 3y)a + (4y)x°. The polynomial 2y + 3y? is represented by the list (0.0, 2.0, 3.01 and the polynomial 4y is represented hy [0.0, 4.0]. Thus, al be written [1.0], [0.0,2.0,3.0], 0, [0.0,4.0]] Write functions to add polynomials in two variables. scalar-multiply such poly- nomials, and polynomial-multiply these polynomials. You need not usc a “Karatsuba-Ofman” type trick to improve efficiency.Chapter 4 In this chapter we shall learn how to read and write information from files. ML offers us a number of tools, ranging from a simple function that prints strings to the standard output to more complex functions that perform UNIX-style input/output and more. Our study of input and output forces us to learn a number of additional features of ML. In this chapter we shalll find discussions of the following topics in addition to input/output: 1. The unit type, which is similar to “void” in C. 2. The type constructor option, which allows us to express values that are either present or absent. 3. Lists of statements. 4. A way to access functions that are in the standard basis of ML but not in the top-level environment 4.1 Simple Output. ML provides a print operator that writes a character string to the standard output. ‘his function is simple to use and can do most of what we need for typical output operations. Thus, it is a good point to begin our study of I/O. 4.1.1 The Print Function ‘The expression print (x) causes the value of a character string x to be printed on the “standard output,” which would be the terminal unless you have called SML/NJ with another standard output designated (via the UNTX > operator). The value returned by the print function is the unit (). This symbol, whichLoz CHAPTER 4. INPUT AND OUTPUT we have not seen before, is the lone value of the type unit, which we have also not encountered previously. One purpose of the unit is to serve as the value returned by a function, such +t, that docs its ML encountered so far, print has an effect on more than the ML environment; it changes the external world: either what appears on the user’s terminal or the contents of the file that is the current standard output. ork by aside offcct, Not! by aside off ¢ Note that print does not return the value printed as its own value. Example 4.1: In Fig. 4.1 is a function called testZero, which tests whether or not its integer argument is 0 and prints one of the strings "zero" or "not zero" as appropriate. Notice that ML responds by saying that testZero is a function from the type integer to the Lype unit, because the unit is the “value” produced by the print function. The fact that a string is produced as a side-effect is not reflected in the type of the function. fun testZero(0) | testZero(_) print(“zero\n") print("not zero\n’ val testZero = fn: int + unit testZero(2); nui zero val it = () : unit Figure 4.1: A function that uses the print function We also see in Fig. 4.1 a use of testZero(2) and ML’s response. We first see the printed response not zero on the standard output. Following immediately is the normal response of ML after evaluating a function: val it = () : unit Notice that the value of the expression testZero(2) is the unit (). That is what print returns, and therefore that is what testZero returns. « Remember from Section 2.1.1 that in strings we can use the sequence \n to represent a newline. Had we omitted printing this character in the print statements of Fig. 4.1, the output would have run together, as not zeroual it = () : unit4.1. SIMPLE OUTPUT 103 The Type unit The unit type is another of the basic types of the ML system. like int. In a sense it is like the C type void. However, while there is no value for a “void” in C, the ML unit type has exactly one value, (. The unit appears in a surprising place in ML: as the argument of a seemingly zero-argument, function. ‘Thus, if we were to write a zero. argument function that when called returns the string hello world, it would appear as follows: fun hello() = "hello world" val hello = fn : unit + string That is, function hello has an argument after all; the unit. It would be called by applying it to the unit, ac: hello(); val it = “hello world” : string 4.1.2 Printing Nonstring Values i 3 other thi rst th a string. For example, we learned in Section 2.2.4 that the function str will change a character into a string of length 1. Thus, we could write Ii val ¢ = #"a"; print(str(c)); to print an a on the standard output. we would like to print integers or real numbers, or perhaps values of other types. There is a function toString associated with integers, reals, and some other types that converts valucs of those types to appropriate character strings. The identifier toString denotes one of several rather different functions, and in order to tell which one is meant, it is necessary to prefix the identifier toString by the name of the “structure” to which it belongs, and a dot. We shall take up structures, both user-defined structures and structures provided by ML, in Section 8.2. However, roughly, for each type there is a structure with the same name but with the first letter capitalized. For example, the structures Int, Real, and Bool are associated with the types int, real, and bool, respectively. Example 4.2: Here is an example of printing the value of a real number as a string:
You might also like
(Daniel I. A. Cohen) Introduction To Computer Theo (BookSee - Org) 2
PDF
No ratings yet
(Daniel I. A. Cohen) Introduction To Computer Theo (BookSee - Org) 2
649 pages
200 Problems On Languages, Automata, and Computation 1316513467
PDF
No ratings yet
200 Problems On Languages, Automata, and Computation 1316513467
267 pages
Functional Programming Using F# PDF
PDF
No ratings yet
Functional Programming Using F# PDF
376 pages
(International Computer Science Series) Fethi A. Rabhi, Guy Lapalme - Algorithms - A Functional Programming Approach-Addison Wesley (1999) 2 PDF
PDF
100% (2)
(International Computer Science Series) Fethi A. Rabhi, Guy Lapalme - Algorithms - A Functional Programming Approach-Addison Wesley (1999) 2 PDF
247 pages
Elements of ML Programming (Ullman)
PDF
100% (3)
Elements of ML Programming (Ullman)
399 pages
Peter Kogge - The Architecture of Symbolic Computers
PDF
100% (2)
Peter Kogge - The Architecture of Symbolic Computers
764 pages
Concurrent Programming in ML PDF
PDF
100% (1)
Concurrent Programming in ML PDF
325 pages
An Introduction To Formal Languages and Automata Peter Linz Solution Manual
PDF
0% (2)
An Introduction To Formal Languages and Automata Peter Linz Solution Manual
2 pages
O89n6 T o C
PDF
100% (1)
O89n6 T o C
303 pages
Compiler Design
PDF
No ratings yet
Compiler Design
188 pages
Data Structure Using C
PDF
58% (12)
Data Structure Using C
250 pages
ThinkingElixir - Pattern Matching Resource
PDF
No ratings yet
ThinkingElixir - Pattern Matching Resource
74 pages
Structured Programming, Dahl, Dijkstra, Hoare, Academic Press 1972
PDF
100% (7)
Structured Programming, Dahl, Dijkstra, Hoare, Academic Press 1972
234 pages
Lab-9 Abstract Data Types
PDF
No ratings yet
Lab-9 Abstract Data Types
10 pages
Evolution of Programming Language
PDF
No ratings yet
Evolution of Programming Language
18 pages
Principles of Programming Languages Lecture Notes Unit 1
PDF
100% (1)
Principles of Programming Languages Lecture Notes Unit 1
7 pages
Advanced TOC: Dfa, Nfa, TG, GTG
PDF
No ratings yet
Advanced TOC: Dfa, Nfa, TG, GTG
14 pages
Theory Introduction To Programming Languages
PDF
100% (2)
Theory Introduction To Programming Languages
233 pages
Guarded Commands
PDF
No ratings yet
Guarded Commands
9 pages
Cse Flat Digital Notes Full 2020 21
PDF
No ratings yet
Cse Flat Digital Notes Full 2020 21
195 pages
DAA Notes PDF
PDF
No ratings yet
DAA Notes PDF
55 pages
CD Unit - 4
PDF
No ratings yet
CD Unit - 4
39 pages
C++ Lab Manual
PDF
No ratings yet
C++ Lab Manual
153 pages
1 - Design and Analysis of Algorithms by Karamagi, Robert
PDF
No ratings yet
1 - Design and Analysis of Algorithms by Karamagi, Robert
346 pages
Design and Analysis of Algorithms
PDF
No ratings yet
Design and Analysis of Algorithms
2 pages
Transition-Graphs - Chapter 6
PDF
No ratings yet
Transition-Graphs - Chapter 6
38 pages
LL1 Parser Implementation in C
PDF
No ratings yet
LL1 Parser Implementation in C
6 pages
OOP With C++ Lab
PDF
No ratings yet
OOP With C++ Lab
4 pages
Chapter 1 Software and Software Engineering. Pressman
PDF
No ratings yet
Chapter 1 Software and Software Engineering. Pressman
45 pages
Barbara Liskov, Programming With Abstract Data Types
PDF
100% (1)
Barbara Liskov, Programming With Abstract Data Types
10 pages
NLP Unit 1 and 2
PDF
No ratings yet
NLP Unit 1 and 2
106 pages
System Software: An Introduction To Systems Programming Leland L. Beck 3rd Edition Addison-Wesley, 1997
PDF
50% (4)
System Software: An Introduction To Systems Programming Leland L. Beck 3rd Edition Addison-Wesley, 1997
40 pages
Solution Manual To Computer Networking A Top Down Approach 4th Edition
PDF
No ratings yet
Solution Manual To Computer Networking A Top Down Approach 4th Edition
3 pages
PPL Unit-I
PDF
No ratings yet
PPL Unit-I
76 pages
LLVM
PDF
No ratings yet
LLVM
1,703 pages
Cs6660 Compiler Design Appasami
PDF
100% (1)
Cs6660 Compiler Design Appasami
189 pages
Compiler Design Notes
PDF
100% (1)
Compiler Design Notes
156 pages
(D.E. Stevenson) Programming Language Fundamentals PDF
PDF
No ratings yet
(D.E. Stevenson) Programming Language Fundamentals PDF
218 pages
Discrete Mathematical Structures
PDF
No ratings yet
Discrete Mathematical Structures
3 pages
PDF
PDF
0% (1)
PDF
36 pages
Lab Pratice First Lab Manual
PDF
No ratings yet
Lab Pratice First Lab Manual
81 pages
Object Oriented Programming Through Java
PDF
No ratings yet
Object Oriented Programming Through Java
130 pages
Compiler Design: Computer Science
PDF
No ratings yet
Compiler Design: Computer Science
117 pages
Notes Spos Unit 1
PDF
No ratings yet
Notes Spos Unit 1
27 pages
1 - Data Structures and Algorithms Assignment After IV
PDF
No ratings yet
1 - Data Structures and Algorithms Assignment After IV
7 pages
Sir Syed University of Engineering and Technology
PDF
No ratings yet
Sir Syed University of Engineering and Technology
4 pages
PPL Notes
PDF
No ratings yet
PPL Notes
126 pages
The LLVM Compiler Framework and Infrastructure
PDF
No ratings yet
The LLVM Compiler Framework and Infrastructure
61 pages
Theory of Computation Lecture Notes
PDF
No ratings yet
Theory of Computation Lecture Notes
38 pages
Data Structures by D Samantha PDF
PDF
No ratings yet
Data Structures by D Samantha PDF
167 pages
Daa-r22-Unit 1&2-Digital Notes Cse Dept (A.y 2024-25) @DR.K
PDF
No ratings yet
Daa-r22-Unit 1&2-Digital Notes Cse Dept (A.y 2024-25) @DR.K
50 pages
contents
PDF
No ratings yet
contents
10 pages
SML chapter2
PDF
No ratings yet
SML chapter2
51 pages
SML chapter1
PDF
No ratings yet
SML chapter1
16 pages
ML For The Working Programmer
PDF
100% (2)
ML For The Working Programmer
493 pages
CMSC 22100/32100: Programming Languages An Overview of Standard ML M. Blume October 2, 2008
PDF
No ratings yet
CMSC 22100/32100: Programming Languages An Overview of Standard ML M. Blume October 2, 2008
24 pages
Coursera Programming Languages Course Section 1 Summary
PDF
No ratings yet
Coursera Programming Languages Course Section 1 Summary
14 pages
Notes On Programming Standard ML of New Jersey: (Version 110.0.6)
PDF
No ratings yet
Notes On Programming Standard ML of New Jersey: (Version 110.0.6)
249 pages
CSE341: Programming Languages Spring 2019 Unit 1 Summary Dan Grossman, University of Washington
PDF
No ratings yet
CSE341: Programming Languages Spring 2019 Unit 1 Summary Dan Grossman, University of Washington
13 pages
Prog Lang Notes2019
PDF
No ratings yet
Prog Lang Notes2019
184 pages
Related titles
Click to expand Related Titles
Carousel Previous
Carousel Next
(Daniel I. A. Cohen) Introduction To Computer Theo (BookSee - Org) 2
PDF
(Daniel I. A. Cohen) Introduction To Computer Theo (BookSee - Org) 2
200 Problems On Languages, Automata, and Computation 1316513467
PDF
200 Problems On Languages, Automata, and Computation 1316513467
Functional Programming Using F# PDF
PDF
Functional Programming Using F# PDF
(International Computer Science Series) Fethi A. Rabhi, Guy Lapalme - Algorithms - A Functional Programming Approach-Addison Wesley (1999) 2 PDF
PDF
(International Computer Science Series) Fethi A. Rabhi, Guy Lapalme - Algorithms - A Functional Programming Approach-Addison Wesley (1999) 2 PDF
Elements of ML Programming (Ullman)
PDF
Elements of ML Programming (Ullman)
Peter Kogge - The Architecture of Symbolic Computers
PDF
Peter Kogge - The Architecture of Symbolic Computers
Concurrent Programming in ML PDF
PDF
Concurrent Programming in ML PDF
An Introduction To Formal Languages and Automata Peter Linz Solution Manual
PDF
An Introduction To Formal Languages and Automata Peter Linz Solution Manual
O89n6 T o C
PDF
O89n6 T o C
Compiler Design
PDF
Compiler Design
Data Structure Using C
PDF
Data Structure Using C
ThinkingElixir - Pattern Matching Resource
PDF
ThinkingElixir - Pattern Matching Resource
Structured Programming, Dahl, Dijkstra, Hoare, Academic Press 1972
PDF
Structured Programming, Dahl, Dijkstra, Hoare, Academic Press 1972
Lab-9 Abstract Data Types
PDF
Lab-9 Abstract Data Types
Evolution of Programming Language
PDF
Evolution of Programming Language
Principles of Programming Languages Lecture Notes Unit 1
PDF
Principles of Programming Languages Lecture Notes Unit 1
Advanced TOC: Dfa, Nfa, TG, GTG
PDF
Advanced TOC: Dfa, Nfa, TG, GTG
Theory Introduction To Programming Languages
PDF
Theory Introduction To Programming Languages
Guarded Commands
PDF
Guarded Commands
Cse Flat Digital Notes Full 2020 21
PDF
Cse Flat Digital Notes Full 2020 21
DAA Notes PDF
PDF
DAA Notes PDF
CD Unit - 4
PDF
CD Unit - 4
C++ Lab Manual
PDF
C++ Lab Manual
1 - Design and Analysis of Algorithms by Karamagi, Robert
PDF
1 - Design and Analysis of Algorithms by Karamagi, Robert
Design and Analysis of Algorithms
PDF
Design and Analysis of Algorithms
Transition-Graphs - Chapter 6
PDF
Transition-Graphs - Chapter 6
LL1 Parser Implementation in C
PDF
LL1 Parser Implementation in C
OOP With C++ Lab
PDF
OOP With C++ Lab
Chapter 1 Software and Software Engineering. Pressman
PDF
Chapter 1 Software and Software Engineering. Pressman
Barbara Liskov, Programming With Abstract Data Types
PDF
Barbara Liskov, Programming With Abstract Data Types
NLP Unit 1 and 2
PDF
NLP Unit 1 and 2
System Software: An Introduction To Systems Programming Leland L. Beck 3rd Edition Addison-Wesley, 1997
PDF
System Software: An Introduction To Systems Programming Leland L. Beck 3rd Edition Addison-Wesley, 1997
Solution Manual To Computer Networking A Top Down Approach 4th Edition
PDF
Solution Manual To Computer Networking A Top Down Approach 4th Edition
PPL Unit-I
PDF
PPL Unit-I
LLVM
PDF
LLVM
Cs6660 Compiler Design Appasami
PDF
Cs6660 Compiler Design Appasami
Compiler Design Notes
PDF
Compiler Design Notes
(D.E. Stevenson) Programming Language Fundamentals PDF
PDF
(D.E. Stevenson) Programming Language Fundamentals PDF
Discrete Mathematical Structures
PDF
Discrete Mathematical Structures
PDF
PDF
PDF
Lab Pratice First Lab Manual
PDF
Lab Pratice First Lab Manual
Object Oriented Programming Through Java
PDF
Object Oriented Programming Through Java
Compiler Design: Computer Science
PDF
Compiler Design: Computer Science
Notes Spos Unit 1
PDF
Notes Spos Unit 1
1 - Data Structures and Algorithms Assignment After IV
PDF
1 - Data Structures and Algorithms Assignment After IV
Sir Syed University of Engineering and Technology
PDF
Sir Syed University of Engineering and Technology
PPL Notes
PDF
PPL Notes
The LLVM Compiler Framework and Infrastructure
PDF
The LLVM Compiler Framework and Infrastructure
Theory of Computation Lecture Notes
PDF
Theory of Computation Lecture Notes
Data Structures by D Samantha PDF
PDF
Data Structures by D Samantha PDF
Daa-r22-Unit 1&2-Digital Notes Cse Dept (A.y 2024-25) @DR.K
PDF
Daa-r22-Unit 1&2-Digital Notes Cse Dept (A.y 2024-25) @DR.K
contents
PDF
contents
SML chapter2
PDF
SML chapter2
SML chapter1
PDF
SML chapter1
ML For The Working Programmer
PDF
ML For The Working Programmer
CMSC 22100/32100: Programming Languages An Overview of Standard ML M. Blume October 2, 2008
PDF
CMSC 22100/32100: Programming Languages An Overview of Standard ML M. Blume October 2, 2008
Coursera Programming Languages Course Section 1 Summary
PDF
Coursera Programming Languages Course Section 1 Summary
Notes On Programming Standard ML of New Jersey: (Version 110.0.6)
PDF
Notes On Programming Standard ML of New Jersey: (Version 110.0.6)
CSE341: Programming Languages Spring 2019 Unit 1 Summary Dan Grossman, University of Washington
PDF
CSE341: Programming Languages Spring 2019 Unit 1 Summary Dan Grossman, University of Washington
Prog Lang Notes2019
PDF
Prog Lang Notes2019