THE LIBRARY Tel: +44 1203 523523 Fax: +44 1203 524211 AUTHOR: TITLE: Gary Meehan DEGREE: Ph.D. Aspets of Funtional Programming DATE OF DEPOSIT: ............................ I agree that this thesis shall be available in aordane with the regulations governing the University of Warwik theses. I agree that the summary of this thesis may be submitted for publiation. I agree that the thesis may be photoopied (single opies for study purposes only). Theses with no restrition on photoopying will also be made available to the British Library for mirolming. The British Library may supply opies to individuals or libraries. subjet to a statement from them that the opy is supplied for non-publishing purposes. All opies supplied by the British Library will arry the following statement: \Attention is drawn to the fat that the opyright of this thesis rests with its author. This opy of the thesis has been supplied on the ondition that anyone who onsults it is understood to reognise that its opyright rests with its author and that no quotation from the thesis and no information derived from it may be published without the author's written onsent." AUTHOR'S SIGNATURE: ..................................................... USER'S DECLARATION 1. I undertake not to quote or make use of any information from this thesis without making aknowledgement to the author. 2. I further undertake to allow no-one else to use this thesis while it is in my are. DATE SIGNATURE ADDRESS ............................................................................. ............................................................................. ............................................................................. ............................................................................. ............................................................................. THE UNIVERSITY OF WARWICK, COVENTRY CV4 7AL Aspets of Funtional Programming by Gary Meehan Thesis Submitted to the University of Warwik for the degree of Dotor of Philosophy Computer Siene August 1999 Contents List of Tables viii List of Figures ix Aknowledgments xi Delarations xii Abstrat xiii Chapter 1 Introdution 1 Chapter 2 Bakground 7 2.1 Funtions, The -alulus and Superombinators . . . . . . . . . . . 7 2.2 Stritness and Laziness . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.3 Graph Redution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.4 Types, Polymorphism and Overloading . . . . . . . . . . . . . . . . . 13 2.5 Abstrat Mahines . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Chapter 3 Fuzzy Funtional Programming 20 3.1 Fuzzy Logi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2 Fuzzy Subsets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.2.1 The Domain, Support and Fuzziness of a Fuzzy Subset . . . . 28 iii 3.2.2 Fuzzy Subset Operations . . . . . . . . . . . . . . . . . . . . 30 3.2.3 Hedges and Fuzzy Numbers . . . . . . . . . . . . . . . . . . . 32 3.2.4 Defuzziation . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.2.5 An Example | Fuzzy Database Queries . . . . . . . . . . . . 38 3.3 Fuzzy Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.4 Further Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.4.1 Controlling a Shower . . . . . . . . . . . . . . . . . . . . . . . 47 3.4.2 Priing Goods . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 Chapter 4 Compiling Lazy Funtional Programs to Java Byte-ode 58 4.1 The Java Virtual Mahine . . . . . . . . . . . . . . . . . . . . . . . . 59 4.2 The Ginger Language . . . . . . . . . . . . . . . . . . . . . . . . . . 63 4.3 Representation of Graph Nodes . . . . . . . . . . . . . . . . . . . . . 64 4.3.1 Updating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 4.3.2 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.3.3 Printing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 4.4 Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 4.4.1 Fuzzy Primitives . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.5 Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.5.1 Compiling Superombinators . . . . . . . . . . . . . . . . . . 82 4.5.2 The R Compilation Sheme . . . . . . . . . . . . . . . . . . . 83 4.5.3 The C Compilation Sheme . . . . . . . . . . . . . . . . . . . 90 4.5.4 The E Compilation Sheme . . . . . . . . . . . . . . . . . . . 90 4.5.5 Tail Reursion . . . . . . . . . . . . . . . . . . . . . . . . . . 91 4.5.6 Initial Results . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 4.6 Optimisations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 iv 4.6.1 Diret Funtion Invoations . . . . . . . . . . . . . . . . . . . 93 4.6.2 Single-instane Constants . . . . . . . . . . . . . . . . . . . . 94 4.7 Results and Other Work . . . . . . . . . . . . . . . . . . . . . . . . . 96 4.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Chapter 5 The Aladin Abstrat Mahine 102 5.1 The Semantis of the AAM . . . . . . . . . . . . . . . . . . . . . . . 103 5.1.1 The Original Denotational Semantis . . . . . . . . . . . . . . 104 5.1.2 The Denotational Semantis with Expliit Updates . . . . . . 105 5.1.3 The Operational Semantis . . . . . . . . . . . . . . . . . . . 111 5.1.4 An Example of the Operational Semantis . . . . . . . . . . . 116 5.2 A Sripting Language for Aladin . . . . . . . . . . . . . . . . . . . . 120 5.2.1 Pakage Delarations . . . . . . . . . . . . . . . . . . . . . . . 122 5.2.2 Import Delarations . . . . . . . . . . . . . . . . . . . . . . . 123 5.2.3 Stritness Signatures . . . . . . . . . . . . . . . . . . . . . . . 123 5.2.4 Denitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 5.3 Representation and Evaluation of Aladin Programs . . . . . . . . . . 128 5.3.1 Aladin Programs . . . . . . . . . . . . . . . . . . . . . . . . . 130 5.3.2 Data Strutures . . . . . . . . . . . . . . . . . . . . . . . . . . 132 5.3.3 The Evaluation Mehanism . . . . . . . . . . . . . . . . . . . 135 5.4 Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 5.4.1 Java and Aladin Primitives . . . . . . . . . . . . . . . . . . . 141 5.4.2 C Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 5.4.3 Primitives in Ginger . . . . . . . . . . . . . . . . . . . . . . . 145 5.4.4 Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 5.4.5 Fuzzy Primitives . . . . . . . . . . . . . . . . . . . . . . . . . 150 5.4.6 Importing Primitives . . . . . . . . . . . . . . . . . . . . . . . 154 v 5.5 Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 5.5.1 Compiling Sripts . . . . . . . . . . . . . . . . . . . . . . . . 156 5.5.2 Compiling Constants and Stritness Signatures . . . . . . . . 158 5.5.3 Compiling Stritness Delarations . . . . . . . . . . . . . . . 159 5.5.4 Compiling Imports . . . . . . . . . . . . . . . . . . . . . . . . 160 5.5.5 Compiling and Optimising Loal Bloks . . . . . . . . . . . . 160 5.5.6 Compiling Simple Programs . . . . . . . . . . . . . . . . . . . 164 5.5.7 Compiling the Target Code and Using the Resultant Classes 165 5.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 Chapter 6 Partial Evaluation in Aladin 170 6.1 The Denotational Semantis . . . . . . . . . . . . . . . . . . . . . . . 171 6.2 The Operational Semantis . . . . . . . . . . . . . . . . . . . . . . . 172 6.3 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 6.4 Partially Evaluating Programs . . . . . . . . . . . . . . . . . . . . . 178 6.5 Primitives and Partial Evaluation . . . . . . . . . . . . . . . . . . . . 181 6.5.1 Partial Data Strutures . . . . . . . . . . . . . . . . . . . . . 186 6.5.2 Partial Evaluation and C Primitives . . . . . . . . . . . . . . 192 6.5.3 Partial Evaluation and Ginger Primitives . . . . . . . . . . . 194 6.6 Further Examples and Results . . . . . . . . . . . . . . . . . . . . . 196 6.6.1 Matrix Multipliation . . . . . . . . . . . . . . . . . . . . . . 196 6.6.2 Gaussian Elimination . . . . . . . . . . . . . . . . . . . . . . 200 6.6.3 Exponentiation . . . . . . . . . . . . . . . . . . . . . . . . . . 206 6.6.4 Polynomials . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 6.6.5 Integration by Simpson's Rule . . . . . . . . . . . . . . . . . 209 6.6.6 Fuzzy Systems . . . . . . . . . . . . . . . . . . . . . . . . . . 212 6.6.7 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 vi 6.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220 Chapter 7 Conlusion and Further Work 221 7.1 Integrating Aladin and Ginger . . . . . . . . . . . . . . . . . . . . . 223 7.2 Ginger and Type Systems . . . . . . . . . . . . . . . . . . . . . . . . 223 7.3 Parallel Aladin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 7.4 Other Aladin Enhanements . . . . . . . . . . . . . . . . . . . . . . . 228 Bibliography 230 vii List of Tables 4.1 Initial running times of programs produed by the Ginger ompiler . 92 4.2 Running times (s) of programs produed by the Ginger ompiler with and without diret funtion invoation. . . . . . . . . . . . . . . . . . 94 4.3 Running times (s) of programs produed by the Ginger ompiler with and without single-instane onstants. . . . . . . . . . . . . . . . . . 97 4.4 Comparisons of running times of various lazy funtional language implementations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 4.5 Running times (s) of programs produed by the Ginger ompiler with initial heap sizes of 1 and 4 megabytes . . . . . . . . . . . . . . . . . 100 5.1 Aladin inx operators and their prex form . . . . . . . . . . . . . . 126 5.2 Aladin `dot-dot' lists and their prex form . . . . . . . . . . . . . . . 128 6.1 Comparisons of running times of Aladin programs with and without partial evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 6.2 Compile + Run times of Aladin programs with and without partial evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 6.3 Comparisons of Running times of Ginger programs with and without partial evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 viii List of Figures 2.1 The steps taken by an Abstrat Mahine . . . . . . . . . . . . . . . . 18 3.1 Crisp denition of Prot. . . . . . . . . . . . . . . . . . . . . . . . . 26 3.2 Fuzzy denition of Prot. . . . . . . . . . . . . . . . . . . . . . . . . 26 3.3 Standard fuzzy subset distributions . . . . . . . . . . . . . . . . . . . 27 3.4 Operations on fuzzy subsets. . . . . . . . . . . . . . . . . . . . . . . 30 3.5 Very protable and Somewhat protable. . . . . . . . . . . . . . . . . 33 3.6 Fuzzy approximations to 20. . . . . . . . . . . . . . . . . . . . . . . . 35 3.7 Defuzzifying a fuzzy subset . . . . . . . . . . . . . . . . . . . . . . . 36 3.8 The fuzzy rule base for the height ! shoe size expert system . . . . 42 3.9 Weighting, adding and defuzzifying the rules for a height of 1.75m . 44 3.10 Fuzzy subsets of temperature, ow and tap hange . . . . . . . . . . 48 4.1 The EBNF of the Ginger language after lambda-lifting and dependeny analysis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 5.1 An generi unwound appliation . . . . . . . . . . . . . . . . . . . . 108 5.2 Appliation of a funtion to too many arguments . . . . . . . . . . . 109 5.3 Appliation of a funtion to too many arguments after evaluation of inner appliation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 5.4 The fp.aladin pakage . . . . . . . . . . . . . . . . . . . . . . . . . 129 ix 5.5 A GUI for Aladin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 x Aknowledgments I would primarily like to thank my supervisor, Mike Joy, for his advie and help throughout the duration of my researh, for getting me on the ourse in the rst plae, and not being too uptight about the size of my oee bill. I'd also like to thank Steve Matthews for his enouragement, espeially with regards to the fuzzy stu (surely I'm allowed one olloquialism, Mike); Tom Axford, who along with Mike Joy, laid the groundwork for Aladin; and Ananda Amatya for various omments and papers over the years. In addition, I'd like to thank Mike and Steve for their omments and editorial advie on this thesis, and their orretions of my lousy typing. Finally, I'd like to say to Rihard Gault: `Beat you!' xi Delarations The work on fuzzy logi and funtional programming, desribed in Chapter 3, is based largely on that detailed in [77℄ published in the Journal of Funtional programming and its preeding tehnial report [75℄. The published paper was written in ollaboration with my supervisor, Dr Mike Joy, who ated in an advisory and editorial apaity; the tehnial report was authored by me alone. The Ginger ompiler desribed in Chapter 4 has its origins in the tehnial report written by Dr Joy [53℄. The ompiler was written from srath by me alone, apart from the parser for the language whih is based on one developed by Dr Joy for a Ginger interpreter written in Java. The text of Chapter 4 is based on [78℄ published in Software | Pratie and Experiene, o-authored with Dr Joy who ated in an advisory and editorial apaity. An earlier version of the ompiler is desribed in a tehnial report [74℄, written solely by me. My work on Aladin detailed in Chapters 5 and 6 is based on that done by Dr Joy and Dr Tom Axford of Birmingham University [10, 11℄. I developed the updating denotational semantis for Aladin, with and without partial evaluation, from whih an operational semantis for Aladin was obtained. My implementation of the Aladin without partial evaluation, based on this semantis, has previously been desribed in a tehnial report [76℄. Nothing has yet been published on my work on partial evaluation and Aladin. xii Abstrat This thesis explores the appliation of funtional programming in new areas and its implementation using new tehnologies. We show how funtional languages an be used to implement solutions to problems in fuzzy logi using a number of languages: Haskell, Ginger and Aladin. A ompiler for the weakly-typed, lazy language Ginger is developed using Java byte-ode as its target ode. This is used as the inspiration for an implementation of Aladin, a simple funtional language whih has two novel features: its primitives are designed to be written in any language, and evaluation is ontrolled by delaring the stritness of all funtions. EÆient denotational and operational semantis are given for this mahine and an implementation is developed using these semantis. We then show that by using the advantages of Aladin (simpliity and stritness ontrol) we an employ partial evaluation to ahieve onsiderable speed-ups in the running times of Aladin programs. xiii Chapter 1 Introdution Funtional programming [15, 26, 40, 93℄ is, as its name suggests, programming with funtions, by dening them and applying them to arguments. Funtions an be passed as arguments to other funtions and returned as the result of a funtion. The denition of a funtion in funtional programming is an expression rather than a sequene of ommands. Funtional programming is delarative in that we say what we want rather than how we get it. Funtional languages have found appliations in theorem proving, telephone ontrollers, database management, expert systems, ontrol of distributed appliations, workfore management and geometri modelling amongst other things [97, 116℄. Funtional programming has its roots in the work of Alonzo Churh, Haskell Curry and Moses Shonnkel into the -alulus and ombinatory logi in the 1920s and 30s [13, 42℄. The rst funtional language, Lisp (List Proessing), was invented by John MCarthy in the early 60s [72, 73, 121℄ and found wide use in the eld of Artiial Intelligene. John Bakus [12℄ alled for a funtional style of programming to be used instead of the imperative (or von Neumann) style whih he ritiised in sathing terms for the bloated size of its languages, dependeny on `one word at a time' operations and historial state and the fat that its programs are not amenable 1 to mathematial reasoning. He desribed a funtional language alled FP, similar to the purely funtional subset of Lisp, and gave an algebra whih ould be used to reason about programs written in this language. Robin Milner's work on strong polymorphi typing in the late seventies [80℄, following on from earlier work by J. Roger Hindley [43℄, led to the Hindley-Milner type-system, the basis for the type systems of most modern funtional languages. The rst language to use this type system was ML (Meta Language) [81, 112℄. David Turner showed how a funtional language ould be implemented using a xed set of basi funtions known as ombinators [109℄ and used this idea to implement Miranda [45, 106, 110, 111℄. Other funtional languages suh as Hope [18℄ and Orwell [113℄ also appeared at this time. The inreasing proliferation of funtional languages led in September 1987 to the start of development of the language Haskell [14, 85, 107℄ whih aimed to onentrate the work being done in a number of disparate (lazy) languages into a single one. Haskell is a powerful language, with a sophistiated type and module system, yet is surprisingly lean with few of the idiosynrasies found in other languages, both funtional and imperative. This is due in part to its use of monads to ope with side-eets and I/O in a purely funtional manner [114, 115℄ and its use of type lasses to organise the overloading of funtion names in a systemati way [32, 88℄. There are other funtional languages in use today, notably Clean [91, 92℄ and Erlang [4, 5, 6℄, and ML and Lisp in their many variants ontinue to have a strong following. It is hard to quantify preisely what failities a funtional language should oer. From the basi denition given above, an imperative language an be used funtionally (using pointers to funtions) [38℄, though the syntax of C does tend to ght against this. However, we shall attempt to enumerate the features and advantages [46, 64℄ one might expet of a funtional language, drawing the reader's attention to the list of exeptions that follows the list. 2 Funtional languages are pure in that they: { are referentially transparent, that is the result of a funtion depends only on its arguments; { are side-eet free; { do not allow the destrutive update of variables. This means that a variable in one part of a program an't have its value hanged unexpetedly in another. This means that the various parts of a funtional program an be exeuted in any order. This is a great advantage when it omes to program optimisation and makes it simpler to evaluate the various parts of a program in parallel. The purity of funtional languages also makes them more amenable to mathematial reasoning. We an fator out ommonly-used strutures of funtion denitions into higherorder funtions (that is funtions whih take funtions as arguments) leading to greater modularity, abstration and brevity of programs. Funtional programs are polymorphi, that is funtions an be dened to work over many types rather than just one. One single funtion an be used in plae of many, essentially equivalent ones. It is ommonly easier and neater to introdue new types into funtional languages than their imperative equivalents, espeially if we exploit polymorphism. Instanes of suh types an be leanly and simply taken apart using pattern mathing. Funtional programs are elegant and far learer than their imperative ounterparts. They're usually far shorter, too. 3 Funtional programs an be evaluated lazily, that is we only evaluate those parts of a program that are atually needed and thus we only do the minimum amount of work neessary. This laziness is transparent to the user. Funtional programs are strongly typed and an never `go wrong' [43, 80℄ in the sense that any illegal operations an be aught at ompile-time. Moreover, the user doesn't have to provide the types of funtions expliitly: typing an be ompletely inferred by the implementation at ompile-time. Funtional languages oer automati memory management. This is atually a neessity in a funtional language sine the programmer's lak of ontrol means that they annot insert manual memory (de)alloation operations into their programs. Dierent funtional languages do not onform with all the points above, either due to a philosophial viewpoint or due to reasons of eÆieny. Standard ML, Erlang and Lisp are all strit (though there is a lazy variant of ML [8℄) and non-pure, though in the former two the use of non-pure operations is disouraged and usually only used to failitate Input/Output. Lisp is also ompletely type-less. Why, given all the above advantages, are funtional languages not more prevalent? Philip Wadler reently addressed this problem [117℄. Some reasons he gave for the lak of adoption of funtional languages are: It is hard to use funtional languages in o-operation with other languages, in partiular C/C++ libraries and omponents. However, there are projets urrently being undertaken to solve this problem, suh as Green Card [90℄ whih interfaes Haskell with C libraries, and HaskellSript whih interfaes Haskell with the COM/AtiveX framework [63℄ Libraries for funtional languages (in partiular GUI ones) are still not fully 4 developed. Implementations of funtional languages are not available on many mahines. The ubiquitousness of C means that it is often preferred despite the fat that it is hardly ever the best language for the job. Funtional language implementations tend to be provided by universities and are hard to install. They are also usually under ative development and ontinually hanging. Commerial users tend to prefer a stable system with plenty of support. There are some ommerial oerings suh as Miranda [110, 111℄ and implementations of ML [37℄ and Lisp [28, 36℄. Erisson also support Erlang [24℄ and eorts are being made to produe a stable version of Haskell | Haskell 98 [39℄. Stand alone appliations implemented using a funtional language tend to have an unaeptably large memory footprint aused by the need to inorporate the entire runtime pakage for the library in the program. There is a lak of tools for funtional languages, in partiular debuggers, prolers and integrated development environments. It is hard to train programmers used to imperative programming in some of the aspets of funtional programming [54℄. Wadler also gave a ouple of non-reasons: Funtional programs are not as slow as they are pereived: modern ompilers an get performane that is as good as C in some ases and within a fator of two slower on average. Besides, the popularity of Visual Basi [108℄ and Java [7℄, neither of whih has partiularly fast implementations, shows that performane is not everything. 5 Funtional programming isn't hard for programmers used to other paradigms to understand | it may take time but one they understand it they are usually impressed with its larity and elegane. Muh eort is being made to alleviate the disadvantages of funtional programming given above, and it is omforting to know that obtaining solutions to these problems is probably only a matter of time. Indeed, given the fat that most implementations are the work of a handful of individuals ompared to the hundreds or even thousands who work on the language produts of Mirosoft and Borland it is an ahievement that funtional languages have ome so far. The above provides the motivation for our researh in that we aim to takle some of the downsides of funtional programming. First of all, we investigate a new appliation eld for funtional programming | fuzzy logi, sets and systems | and show how these onepts an be learly and elegantly implemented in a funtional language, primarily Haskell, but we also implement fuzzy onepts in Ginger and Aladin (Chapter 3). We then address the problem of portability of funtional programs in Chapter 4 and oer a solution in the form of a ompiler for the funtional language Ginger [53, 78℄ whih produes ode for the Java Virtual Mahine (JVM) [7, 79℄. The problem of interfaing funtional languages with other languages is takled in Chapter 5 by means of Aladin [10, 76℄. This a stripped-down funtional language whih oers ne ontrol over the stritness of funtions. We develop an implementation of Aladin, whih produes ode for the JVM as in our Ginger ompiler. Aladin has other appliations too, and we show in Chapter 6 how it an be used to partially evaluate programs [51℄ and, oming full irle, how partially evaluating funtional implementations of problems in fuzzy logi an yield substantial performane benets. 6 Chapter 2 Bakground In this hapter we aim to give a brief introdution to some of the key onepts of funtional programming whih we will enounter in the further hapters of this thesis. We do not aim to give an introdution to funtional programming itself; instead the reader is direted to the exellent works by Bird and Wadler [14, 15℄ or Thompson [106, 107℄. Note that all examples will be given using Haskell syntax unless stated otherwise. 2.1 Funtions, The -alulus and Superombinators A funtional program normally onsists of a list of funtion denitions. What happens when the program is exeuted depends on the language and implementation. A funtional language intepreter will allow the user to evaluate the appliation of funtions to arguments on demand in the interpretive enivronment. A ompiler for a funtional language typially requires the denition of standard funtion whih takes no arguments, usually named main, whih will be evaluated when the program is exeuted. The denition of a funtion is essentially a -expression [13, 42℄, though the 7 syntax of the partiular language usually disguises this. A -expression is either a basi expression, suh as a variable, onstant or a primitive funtion (for example addition), an appliation of one -expression to another, or a -abstration. A abstration provides a way of dening a funtion without naming it. For example, the funtion square dened as: square x = x * x is equivalent to the -abstration (x:x * x). Here the variable x is said to be bound by the -abstration; variables whih aren't bound are said to be free. We do not onsider the -alulus in this thesis, however one partiular lass of -expression is important to us and that is the superombinator [86℄. A superombinator is a -expression of the form -abstration, E x1 : : : : :xn0 :E where E is not a no free variables our in the expression, and any -abstrations in are also superombinators. For example, 2 + 5, x:x * are all superombinators, whereas x:y, x:y - x x and f:f ( x:x * x) and f:f ( x:f x) are not. Su- perombinators are important sine they an be ompiled into eÆent ode [25, 86℄ and form the basis of our Ginger ompiler (Chapter 4). 2.2 Stritness and Laziness A funtion does not always need to know the value of some or all of its parameters for it to be able to return a result. For example, the funtion: forty_two x = 42 always returns 42 no matter what the value of x is. In partiular, we would expet the answer to be 42 even when x is undened, for example, when it is the (undened) result of 1 / 0. Less trivially, in the ase of boolean onjuntion dened as: x && y = if x then y else False 8 if we know that x has the value False then the result of the onjuntion must also be False no matter what the value of y is, even if it is undened. Also, if the value of y is the result of some, potentially expensive, omputation then we would expet to be able to obtain the result of the onjuntion without performing this omputation when x is False. Languages whih do not evaluate their arguments before applying funtions are said to be lazy and use lazy evaluation; languages whih do evaluate their arguments are said to be strit and use strit evaluation. In a lazy language, we an expet the result of forty two (1 / 0) to be 42, sine lazy languages only evaluate their arguments only when needed. In a strit language, the result of forty two (1 / 0) gives an error, sine the language implementation would attempt to rst evaluate 1 / 0 whih is undened. See the next setion for further details of strit and lazy evaluation. Formally, a funtion f is said to be strit in its argument if and only if: f?=? It is said to be non-strit or lazy otherwise. All funtions an be thought of as having 1 (or 0) arguments via urrying. Here ? an be thought of as a non-terminating omputation or an undened or error value. So, forty two is lazy in its argument sine forty two ? = 42 6= ?. A lazy language an take full advantage of laziness without any intervention by the user; strit languages in general an't, though a partiular language implementation might provide mehanisms where the user an simulate laziness. In addition, strit languages might provide primitives whih are in eet lazy in some of their arguments. For example, the strit language Standard ML provides the operators andalso and orelse whih implement logial onjuntion and disjuntion respe- tively, but whih do not evaluate their seond arguments unless neessary. This 9 feature isn't restrited to funtional languages: the && and || operators of C and its derivatives have the same eet as Standard ML's andalso and orelse. In C parlane, the laziness is referred to as short-iruiting. While lazy evaluation allows us to irumvent some evaluation and leads to more general programs, strit evaluation is generally more eÆient as lazy evaluation an lead to the proliferation of unevaluated expressions (see Setion 7.5 of [14℄, for example) and a lever ompiler an exploit stritness to produe more eÆient ode. For this reason, stritness analysis (see Chapter 22 of [86℄, for example) is often used to determine when the arguments of a funtion an be safely treated as strit. This however is an undeidable problem, though good approximations an be obtained. 2.3 Graph Redution Graph redution provides a way to implement funtional languages [48, 86, 89℄. The evaluation of an expression involves the appliation of redution rules, that is, the denition of funtions, until no more redution rules are appliable, at whih point the expression is said to be in normal form. For instane, suppose we have the denition: square x = x * x and we want to evaluate the expression square (3 + 4). This is stored as the graph: rib spine @ base of spine square @ @ + 10 4 3 Note that we represent inx operators like + (whih ome between their arguments) as prex ones (whih ome before their arguments). Sine values in pure funtional languages are immutable, we an share sub-expressions saving both time and memory. We an evaluate the expression by reduing the graph[14, 86℄. For example, if we redue square (3 + 4) we get the graph: ribs @ @ base of spine @ * @ + 4 3 The rst time 3 + 4 is evaluated the graph redues to: @ @ * 7 Hene we only need to evaluate 3 + 4 one as the result is shared between the nodes that point to it. Redution of the multipliation yields the answer 49 whih is the normal form of square (3 + 4). Usually when we are evaluating an expression there is more than one subexpression that an be redued (eah suh subexpression is alled a redex ). Implementations of lazy funtional languages hoose to evaluate the outermost redex rst; that is apply the redution rule of the funtion at the base of the spine of the graph as in the example above. So we evaluate the funtion itself before its arguments. This is lazy evaluation, also known as outermost graph redution. For instane, we redue square (3 + 4) to (3 + 4) * (3 + 4) rather than square 7. Doing the latter is strit evaluation (also known as innnermost graph redution). Of ourse, eventually we may need to evaluate the arguments of a funtion, but suh an operation is 11 only usually done expliitly by primitives of the language, suh as * in our square example. In a lazy funtional language, a program is not usually evaluated to Normal Form but instead only to Weak Head Normal Form (WHNF). An expression is said to be in WHNF if it is of the form f e1 : : : en where either n 0 and f is either a data objet, suh as an integer or a type onstrutor (see below) or is a funtion whih requires more than n arguments. Lazy and strit evaluation will always redue an expression to the same normal form, however evaluating an expression using strit evaluation may not terminate in some ases where lazy evaluation does. For example, onsider the program: from x = x : from (x + 1) main = head (from 0) Here : (ons) is the list onstrutor and head selets the head of the list, that is, the expression to the left of the :. Note that the expression h : t is in normal form no matter what h and t are (sine ons does not evaluate its arguments). The expression from x yields the list [x, x + 1, x + 2,...℄, and hene evaluating the expression head (from 0) should give the answer 0. With lazy evaluation, we have the redution sequene: main =) head (from 0) =) head (0 : from (0 + 1)) =) 0 Like *, head evaluates its argument (to normal form). The result of the evaluation should be a ons, otherwise we have an error, at whih point head selets the leftmost 12 argument of the ons. Now suppose we evaluate the expression stritly: main =) head (from 0) =) head (0 : from (0 + 1)) =) head (0 : from 1) =) head (0 : 1 : from (1 + 1)) =) head (0 : 1 : from 2) =) head (0 : 1 : 2 : from (2 + 1)) ::: The reursion would never terminate as the innermost expression an always be redued. 2.4 Types, Polymorphism and Overloading Every expression in a strongly-typed funtional language, suh as Haskell, ML or Miranda, has a type whih an be inferred at run-time [43, 80℄. Using Haskell's syntax, we an dene a type as: A basi type, suh as Int, Char or Bool. For instane, 1 + 3 has type Int and 1 <= 3 has type Bool. A type variable, for example, a or b. These variables stand for any type with repeated ourrenes in a type standing for the same type. A funtion type, written as d -> r where d is the domain of the funtion and r is the range. Funtions are urried and -> assoiates to the right. For instane, toUpper whih onverts haraters into their upper ase equivalents has type Char -> Char. 13 A ompound type, suh as a list or a tuple. For instane, the list [1, 2, 3℄ has type [Int℄ while (1, 'g') has the type (Int, Char). A type whih ontains type variables is said to be polymorphi ; otherwise it is said to be monomorphi. Funtions whih have a polymorphi type are themselves referred to as polymorphi. The simplest example of a polymorphi funtion is seen with the identity funtion: ident x = x Clearly it does not matter what the type of x is, be it an integer a string or a funtion, sine all ident does is return it. We assign x the polymorphi type a and therefore, sine ident returns a value with the same type of its argument, we an write the type of ident as a -> a. We an have more than one type variable in a type, for instane, the funtion map dened as: map f [℄ = [℄ map f (x:xs) = f x : map f xs has type (a -> b) -> [a℄ -> [b℄. So the expression map (+ 1) [1, 2, 3℄ has the value [2, 3, 4℄ of type [Int℄ and the expression map toUpper "gary" has the value "GARY" of type String whih in Haskell is equivalent to [Char℄. The above form of polymorphism is known as parametri polymorphism. In parametri polymorphism, the same ode for eah funtion is used no matter what the type of the arguments are. The other form of polymorphism is known as ad ho polymorphism. This form of polymorphism is used to enable funtion names to be reused for arguments of dierent types, for example, to allow + to represent integer and real addition, two operations whih have fundamentally dierent implementations. Haskell uses type lasses [32℄ to organise the overloading in a systemati and powerful way whih retains the ability of programs to be strongly typed. A type 14 lass is a olletion of types all of whih provide implementations for the funtions dened by the lass. For instane, onsider the Eq lass: lass Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x==y) This states that any type, a, whih is a member of this lass must implement the funtions == and /= of the given type. A default implementation, whih the user is free to override, is provided for /= in terms of not and ==. To make a type a member of the lass, we delare it as an instane of the lass and provide any neessary implementations. In the standard prelude (set of funtions whih are automatially imported into a user program) of the Haskell interpreter Hugs [50℄, the type Char is made an instane of the Eq lass by the following delaration: instane Eq Char where (==) = primEqChar Here primEqChar is the Hugs primitive funtion whih is used to ompare two haraters for equality. Note that the default implementation for /= is used. We are not restrited to overloading on basi types, either. For instane, for any a whih is an instane of the Eq lass we an make the type [a℄ an instane of the Eq lass, viz : instane Eq a => Eq [a℄ where [℄ == [℄ = True (x:xs) == (y:ys) = x==y && xs==ys _ == _ = False At ompile time, the ompiler substitutes all overloaded funtions with their nonoverloaded denitions aording to the types of the expressions in whih they our. This is known as resolving the overloading. Sometimes the ompiler may not have 15 enough type information to resolve the overloading, in whih ase expliit types have to be supplied. Funtions whose denitions ontain overloaded funtions have to have their types onstrained. For instane, onsider the funtion member: member x [℄ = False member x (y:ys) = x == y || member x ys One would expet this funtion to have type a -> [a℄ -> Bool. However sine we use the == operator to ompare x and y the type a has to be an instane of the Eq lass and hene the type of member is written as Eq a => a -> [a℄ -> Bool. Here the Eq a restrits the types to whih a an range over to preisely those whih are instanes of Eq. Haskell uses lasses to overload a large number of funtions. For instane, the Ord lass is used to overload the omparison operators, the Num lass to overload the arithmeti operators, and the Show lass is used to group all those types whose members an be onverted into strings (that is, shown on a terminal). Other funtional languages handle overloading in dierent ways. Miranda has a single numeri type, thus it does not need to overload the arithmeti operators, and treats the omparison operators as polymorphi operators, for example the operator <= has the Miranda type * -> * -> bool (equivalent to the Haskell type a -> a -> Bool). ML overloads its arithmeti and omparison operators but requires that the overloading be resolved at ompile time, by the user giving expliit monotypes for the funtions in whih they are used if neessary. It however treats equality as a speial ase and uses an equality type. In ML, polymorphi types are given by preeeding the type variable by a single quote, for example 'a, 'b, et., but if the type is expeted to have equality dened over it then it preeeded by two quotes. For example, the member funtion desribed above would have the type 16 ''a -> ''a list -> bool in ML, where 'a list is ML's equivalent of Haskell's [a℄. 2.5 Abstrat Mahines The traditional onept of ompilation involves taking a soure program and produing a sequene of mahine ode instrutions. However, sine dierent proessors have dierent instrution sets this mahine ode will only run on the mahine it was ompiled for; to run the program on another mahine means we have to reompile the soure program. This dierene between proessor instrution sets is not a trivial one, either [41, 122℄. The CISC (Complex Instrution Set Computer) philosophy of the Intel 80x86 series (whih inludes the Pentium and its progeny) and the Motorola M680x0 series has a large omplex set of instrutions some of whih an be quite large, measured in the number of bits they oupy on the mahine, and speialised. The alternative, known as RISC (Redued Instrution Set Computer) is to use a small, but fast, set of instrutions and is used by Sun's SPARC, Aorn's ARM and the PowerPC of IBM and Motorola. Proessors are onstantly evolving, too: as a single example, in the past few years, the Pentium has been sueeded in turn by the Pentium MMX, the Pentium II and the Pentium III, eah of whih has more instrutions than the last. The above mitigates against produing a ompiler whih ompiles a high-level language straight down to the mahine ode suitable for a partiular proessor. Instead, an intermediate abstrat mahine is typially used. An abstrat mahine (AM) an be viewed as a simplied, and more importantly portable, version of a proessor, omplete with its own instrution set and memory stores suh as heaps, staks and registers. The rst step of an implementation of a high-level language that uses an abstrat 17 Compile (1) Source Code Compile (2) Abstract Machine Code Concrete Machine Code Interpret (3) Interpreter Figure 2.1: The steps taken by an Abstrat Mahine mahine is to ompile the soure ode down to AM ode (step 1 in Figure 2.1). This step should be ompletely portable. There are then two ways we an run this AM ode: either using an interpreter (step 3) or by ompiling the AM ode to the mahine ode for a partiular mahine (step 2). Sine the AM ode is simpler and loser to the proessor than the high-level soure, this task is easier than produing a ompiler whih produes onrete mahine ode diretly from the soure ode. The simpliity and platform-independene of abstrat mahines make them relatively easy to implement, optimise and port to dierent mahines. The downside is that programs ompiled using an abstrat mahine are generally slower than those whih do not. The rst suessful abstrat mahine for a funtional language was Landin's SECD mahine [60℄ whih provided a platform for evaluating strit funtional programs. This onsisted of four omponents: The Stak used to hold intermediate results. The Environment used to map variable names to values. The Control where the instrutions to be exeuted are stored. The Dump used to store previous states while other expressions are being evaluated, for example when a funtion alls another funtion. 18 This inspired variations suh as Krivine's mahine [34℄ and lazy mahines suh as the Categorial Abstrat Mahine [93℄ and the CASE mahine [22℄. Turner [109℄ produed an abstrat mahine based on ombinatory logi named the SKI mahine (the S, K and I ombinators form the basis of ombinatory logi). The G-Mahine [8, 86℄, whih forms the basis for our implementations of Ginger and Aladin (see Chapters 4 and 5), is an abstrat mahine based on graph redution (see above). It is similar to the SECD mahine exept that it evaluates expressions lazily and the environment of the SECD is replaed by the graph of the expression being evaluated. Other abstrat mahines for funtional languages inlude the Three Instrution Mahine (TIM) [25℄, the Spineless, Tagless G-Mahine [87℄ and the lazy abstrat mahine derived from Launhbury's semantis for a lazy funtional language [61℄ by Sestoft [99℄. Abstrat mahines are not restrited to funtional languages, either. The rst implementations of Pasal, for example, used an abstrat ode alled Pode, and today the Java Virtual Mahine [79℄ provides a mahine-independent platform for the objet-oriented language Java. 19 Chapter 3 Fuzzy Funtional Programming Fuzzy logi, developed by Lot Zadeh [124, 125℄, is a form of multi-valued logi whih has its grounds in Lukasiewiz's work on suh logis [66, 67℄. It nds many appliations in expert systems (in partiular ontrol problems) [21, 68, 96, 120℄, neural nets [23℄, formal reasoning [82, 104℄, deision making [21, 82, 126℄, database enquiries [82℄ and many other areas. The use of fuzzy logi in suh appliations not only makes their solutions simpler and more readable but an also make them more eÆient, stable and aurate (see, for example, Chapter 2 of [120℄, or Chapter 3 of [123℄). Fuzzy logi has been applied to many languages | both in extending standard languages suh as Prolog [70℄, Fortran [44℄, APL [82℄ and Java [3℄, and in ustomdesigned languages suh as Fuzzy CLIPS [83℄, FIL [1, 2℄, and FLINT [65℄. However no one, to the author's knowledge, has ombined fuzziness with a funtional language. In this hapter we aim to give an introdution to fuzzy logi using the language Haskell [85℄ to implement our solutions. Throughout the hapter we shall give examples of using the programs we develop using the Haskell interpreter Hugs [107℄. We shall see how the high-level, delarative nature of a funtional language allows 20 us to implement easily and eÆiently solutions to problems using fuzzy logi and, in partiular, how the presene of funtions as rst-lass values allows us to model the key onept of a fuzzy subset (see Setion 3.2) in a natural way. We have hosen Haskell for the implementation language beause of its strong typing and its failities for overloading operators (in partiular the logial ones). In later hapters, we will show how fuzzy onepts an be implemented in the weakly-typed language, Ginger (see Setion 4.4.1), and how implementing fuzzy primitives in Aladin (see Setions 5.4.5 and 6.6.6) opens up the possibility for fuzzy programs to be partially evaluated and how, by doing so, signiant performane benets an be ahieved. 3.1 Fuzzy Logi In fuzzy logi, the two-valued truth set of boolean logi is replaed by a multi-valued one, usually the unit interval [0; 1℄. Truth sets taking values in this range are said to be normalised. In this set, 0 represents absolute falsehood and 1 absolute truth, with the values in between representing inreasing degrees of truthness from 0 to 1. So we an say that 0.9 is `nearly true', 0.5 is `as true as it is false' and 0.05 is `very nearly false'. The nearer a value is to 0 or 1 the risper it is; the nearer it is to 0.5 (the middle value of the range) the fuzzier it is. The standard onnetives of boolean logi | ^, _ and : | are adapted so that they work with the fuzzy truth set. There are many ways in whih this an be done, but whatever denition we hoose we expet the following to hold [27, 126℄: 1. ^ and _ should be assoiative and ommutative. 2. ^ and _ should be monotoni. That is, if a ^ b ^ and similarly for _. a; b; 2 [0; 1℄ and a b then 3. 1 and 0 are the identities of ^ and _ respetively. From this and monotoniity 21 we dedue that 1 and 0 are annihilators of _ and ^ respetively. 4. : should be anti-monotoni. That is if a; b 2 [0; 1℄ and a b then :b :a. In the majority of fuzzy logis this is strit monotoniity, that is if a < b then :b < :a. 5. : should be its own inverse, that is if a 2 [0; 1℄ then : :a = a. 6. If we restrit the truth set to just 0 and 1, then our logi should behave exatly as boolean logi. Denitions of _ and ^ that satisfy the above are also known as t-norms and t- onorms (or s-norms ) respetively. We would also expet the onnetives to be ontinuous and to satisfy DeMorgan's laws: :x ^ :y = :(x _ y) :x _ :y = :(x ^ y) Two denitions whih take values in the unit range [0; 1℄ and whih satisfy the above onditions are Zadeh's original denition [124, 125℄ using minimum and maximum operators: x ^ y = min(x; y) x _ y = max(x; y) :x = 1 x and an alternative using sum and produt denitions: x ^y = xy x _y = x+y :x = 1 xy x 22 Note that p ^ :p = 0 () p 2 f0; 1g in both these and most other denitions of fuzzy logi. For instane, 0:3 ^ :0:3 = 0:3 ^ 0:7 = 0:3 using Zadeh's denition, and 0:21 if we use the produt denition of ^. This is only an elementary introdution to fuzzy logi, and we have not mentioned more esoteri onnetives suh as averaging operators. For more information we refer the reader to [56℄, [126℄ and [27℄. From now on we shall assume that all fuzzy truth values lie in [0; 1℄. We shall now set about implementing these ideas in Haskell. We shall plae all our denitions in a module alled Fuzzy whih will redene some of the funtions dened in the Haskell prelude. This is done by shadowing the previous denitions (see Setion 5.3.2 of the Haskell report [85℄). Thus the Fuzzy module and any module whih wishes to import it should ontain the delaration: import Prelude hiding ((&&), (||), not, and, or, any, all) This fores an expliit import of the prelude (whih is normally impliitly imported), but hides the funtions whih we want to redene. An example of the importing proedure an be seen Setion 3.2.5. Fuzzy truth values are represented using the Haskell type Double. The onnetives are implemented by overloading the operators &&, ||, et. so that they work on fuzzy values as well as boolean ones. This is done by shadowing the onnetives (see above) and plaing the onnetives in a lass (see Setion 2.4): lass Logi a where true, false :: a (&&), (||) :: a -> a -> a not :: a -> a The funtions and, or, any and all are then also overloaded so that they now operate on instanes of the Logi lass, rather than just the Bool type as before: 23 and, or :: Logi a => [a℄ -> a and = foldr (&&) true or = foldr (||) false any, all :: Logi b => (a -> b) -> [a℄ -> b any p = or . map p all p = and . map p We an then delare instanes of this lass. Bool is delared in the obvious way, with true = True, false = False, et. For fuzzy truth values (values of type Double) we have: instane Logi Double where true = 1 false = 0 (&&) = min (||) = max not x = 1 - x Note that as with the Bool ase, true is the identity of && and false is the identity of || (provided we stik with values in [0; 1℄, of ourse). So, for example, 0:5 ^ (0:3 _ :0:8) an be evaluated in Hugs as: Fuzzy> 0.5 && (0.3 || not 0.8) :: Double 0.3 where `Fuzzy>' is the Hugs prompt. The expliit typing is neessary to resolve the overloading. 24 3.2 Fuzzy Subsets Given a set A and a subset of it, B say, we an dene a harateristi (membership) funtion B : A ! f0; 1g dened suh that: B (x) = 1; if x 2B = 0; otherwise This harateristi funtion determines whih elements of A are in B and whih are not. Now suppose we replae the two-valued range of B with the unit interval, just as we replaed the boolean truth set with this interval. Then membership of the subset B of A is no longer an absolute but rather something whih takes varying degrees of truthness. For x 2 A, the loser B (x) is to 1, the more we an regard x as belonging to B , with B (x) = 1 holding if x denitely is in B . Conversely, the loser B (x) is to 0, the more we an regard x as not belonging to B . The subset B is no longer a risp set but a fuzzy one. A fuzzy subset B of a set A is a set of pairs with eah element of A assoiated with the degree to whih it belongs to B (determined by B ). Formally, B A [0; 1℄ where B = fhx; B (x)i j x 2 Ag Given the set-theoreti denition of a funtion, that is a set of domain-range pairs, we note that the denition of B and its harateristi funtion are synony- mous. This is the key fat that motivates our use of a funtional language as an implementation language | by representing a fuzzy subset by its membership funtion, a funtional language allows us to manipulate suh sets/funtions with ease. We shall thus use the notion of a fuzzy subset and that of a (fuzzy) harateristi funtion interhangeably. In partiular, if we have a fuzzy subset F of a set X then we shall denote X as the domain of F . To give a onrete example, onsider the problem of determining whether a ompany is protable based, say, on the prot expressed as a perentage of total osts. 25 T 1 Truth value profitable Truth value profitable 0.67 F 0 -10 0 10 20 30 -10 Profit (% of costs) Figure 3.1: Prot. 0 10 20 30 Profit (% of costs) Crisp denition of Figure 3.2: Prot. Fuzzy denition of Using normal set theory, given a set of perentages, P , we would have to determine an arbitrary ut-o point at and above whih we would onsider protable, 15% say (see Figure 3.1). So we an dene protable P as: protable = fp j p 2 P ^ p 15g This means however that a prot of 14.9% is not onsidered protable, whih is somewhat ounter-intuitive onsidering its proximity to the ut-o point. Contrast this with a fuzzy denition of protable (see Figure 3.2). As before, prots above 15% are onsidered denitely protable and those below 0% denitely not protable; however between these two gures the degree of protability inreases linearly. For example, a prot of 10% an be regarded as protable to a degree of 0.67 (that is, protable = 0:67) and a prot of 14.9% is protable to a degree of 0.993, in other words, almost denitely protable. As funtions and fuzzy subsets are synonymous, we represent a fuzzy subset in Haskell as a funtion from some domain to the fuzzy truth value set. We dene the following type synonym: type Fuzzy a = a -> Double A number of funtions representing the shapes of ommon fuzzy subsets are provided (see Figure 3.3). For instane, up has the following denition: 26 1 1 1 atri a b c tri a b 0 0 a b c 0 a b 1 1 b c b 1 0 a a singleton a trap a b c d 0 up a b down a b 0 a d a b Figure 3.3: Standard fuzzy subset distributions up :: Double -> Double -> Fuzzy Double up a b x | x < a = 0.0 | x < b = (x - a) / (b - a) | otherwise = 1.0 The other subsets in Figure 3.3 an be dened similarly. We an now dene the fuzzy subset protable as follows: type Perentage = Double profitable :: Fuzzy Perentage profitable = up 0 15 Membership testing is then merely funtion appliation. For example: Profit> profitable 10 0.666667 27 3.2.1 The Domain, Support and Fuzziness of a Fuzzy Subset Knowing the domain of a fuzzy subset is neessary when defuzzifying it (see Setion 3.2.4) and for evaluating its fuzziness (see below). We an also dene fuzzy numbers in terms of their fuzziness (see Setion 3.2.3) for whih again we need to know the domain over whih we are approximating. Both disrete and ontinuous domains are represented using ordered lists (in the latter ase we only have an approximation). We introdue the type synonym: type Domain a = [a℄ As the list is ordered, the upper and lower bounds of the domain are just the last and rst elements of the domain list respetively: lb, ub :: Domain a -> a lb = head ub = last The `dot-dot' method of dening lists an be used to dene domains in a ompat and easily-understandable way. So, for example, we an represent the domain of protable, whih is the range [ 10; 30℄ as the list [-10..30℄. The support, whih we shall denote as (B ) (also written as supp(B )), of a fuzzy subset B is the set of those members of its domain, A say, whih are in the fuzzy subset with non-zero truth value: (B ) = fB (x) 6= 0 j x 2 Ag For example, if we take the domain of protable as [ 10; 30℄ then its support is (0; 30℄ = fx j 0 < x 30g. This has a simple translation into Haskell: supp :: Domain a -> Fuzzy a -> [a℄ supp dom f = filter (\x -> f x > 0) dom 28 For example, we an evaluate the support of profitable (dened above): Profit> supp [-10..30℄ profitable [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0℄ The fuzziness, , of a fuzzy subset is the degree to whih the values of its membership funtion luster around 0.5. It is dened in terms of the funtion Æ measures the distane of a truth value to the nearest extreme, 0 or 1: Æ (x) = if x; = 1 x; x < 0:5 otherwise For example Æ(0:3) = 0:3, Æ(0:8) = 0:2 and Æ(0) = Æ(1) = 0. If the domain of our fuzzy subset B is a ontinuous range, [a; b℄ say, then we an dene as: 2 Zb (B ) = Æ (B (x)) dx b a a If the domain is a disrete set of points, x1 ; : : : ; xn say, then the integral beomes a summation: n 2X (B ) = Æ (B (xi )) n i=1 For example, the fuzziness of protable (again over [ 10; 30℄) is 0.1875. Note that for any risp set, A, in whih the membership funtion returns only the values 0 or 1, (A) = 0 as 8x 2 A : Æ(A (x)) = 0. The maximum possible fuzziness of a fuzzy subset is 1, whih ours when the membership funtion of a fuzzy subset always returns 0.5. Translating the above into Haskell yields the following funtion: fuzziness :: Domain a -> Fuzzy a -> Double fuzziness dom f = (2.0 / size_dom) * sum (map (delta . f) dom) 29 1 1 B 1 1 Ac A UB A U 0 0 B A+B A B 0 c 0 Figure 3.4: Operations on fuzzy subsets. where size_dom = fromInt (length dom) delta x | x < 0.5 = x | otherwise = 1 - x For example, we an alulate the fuzziness of profitable, viz : Profit> fuzziness [-10..30℄ profitable 0.182114 The value that Haskell returns is only an approximation, of ourse. A better approximation an be obtained by using a domain with more elements, for example: Profit> fuzziness [-10,-9.75..30℄ profitable 0.186335 3.2.2 Fuzzy Subset Operations Standard set operations | suh as union, intersetion and omplement | an be used with fuzzy subsets. For fuzzy subsets, A; B of a set X , we have: A [ B = fhx; A (x) _ B (x)i j x 2 X g A \ B = fhx; A (x) ^ B (x)i j x 2 X g A = fhx; :A(x)i j x 2 X g 30 This an be seen graphially in Figure 3.4, where the logial onnetives are dened using Zadeh's method. A slightly unorthodox operation is addition dened as: A+B = fhx; A (x) + B (x)i j x 2 X g This leads to fuzzy subsets whose membership funtions return values outside the range [0; 1℄. This operation is generally only used in fuzzy systems (see below) where the resultant set is only used as an intermediate value and will be defuzzied (see Setion 3.2.4) to yield a typial value. If fuzzy subsets are Haskell funtions, then the fuzzy subset operators are higherorder funtions. If we look at the denition of intersetion, for example, we see that we an regard it as a way of dening logial onjuntion over sets. This onept holds for both fuzzy and risp sets. Taking this to its logial onlusion we have: instane (Logi b) => Logi (a -> b) where true = \x -> true -- everything false = \x -> false -- empty f && g = \x -> f x && g x -- intersetion f || g = \x -> f x || g x -- union not f = \x -> not (f x) -- omplement This instane represents a generalised set, where true represents the set that everything is a member of and false is the empty set. If true is an identity for the && over the type b then true it also an identity for && over the type a -> b, and similarly for false and ||. In the ontext of fuzzy subsets, that is the type Fuzzy a (whih in turn is the type a -> Double), true is the fuzzy subset, T say, with membership funtion T (x) = 1 and false is the fuzzy subset, F say, with membership funtion F (x) = 0. The funtion true remains the identity of && and false the identity of ||. We also need to be able to perform addition on fuzzy subsets. This is done by making the 31 type a -> b, whih remember is a generalisation of the type Fuzzy a a member of the Num lass (whih is used to overload the numeri operators +, -, et.): instane (Num b) => Num (a -> b) where f + g = \x -> f x + g x f * g = \x -> f x * g x abs f = \x -> abs (f x) signum f = \x -> signum (f x) negate f = \x -> negate (f x) fromInteger i = \x -> fromInteger i We will also nd it useful to use the operators of the Logi lass over tuples, for instane in the shower ontroller desribed in Setion 3.4.1 whih groups its output variables in tuples. This is done pointwise, for example, for pairs we have: instane (Logi a, Logi b) => Logi (a, b) where true = (true, true) false = (false, false) (a, b) && (a', b') = (a && a', b && b') (a, b) || (a', b') = (a || a', b || b') not (a, b) = (not a, not b) We also delare tuples to be instanes of the Num lass in a similar manner. 3.2.3 Hedges and Fuzzy Numbers Just as adjetives suh as protable an be qualied by terms suh as very and somewhat, so an fuzzy subsets. Terms suh as these, known as hedges, alter the membership funtion by intensifying it (normally by raising it to a power greater than 1) in the ase of very and similar terms suh as extremely, or diluting it 32 (normally by raising it to a power between 0 and 1) in the ase of somewhat. Usually we have: very F (x) = F (x)2 somewhat F (x) = F (x)1=2 The eet of very and somewhat on protable an be seen in Figure 3.5. We see that a prot of 10% is protable with truth value 0.67, very protable by truth value 0.44, and somewhat protable by degree 0.82. In Haskell, we represent hedges as higher-order funtions. We rst dene a generi hedge whih will raise the value of a funtion to a speied power: hedge :: Double -> Fuzzy a -> Fuzzy a hedge p f x = if fx == 0 then 0 else fx ** p where fx = f x Note that Hugs denes its power operator ** in terms of logarithms and hene we need to hek if f x is zero before attempting to raise it to the given power, otherwise we will attempt to take the logarithm of zero. We an now dene more spei hedges as follows: Truth value very, extremely, somewhat, slightly :: Fuzzy a -> Fuzzy a 1 0.82 somewhat profitable profitable very profitable 0.67 0.44 0 -10 0 10 20 Profit (% of costs) 30 Figure 3.5: Very protable and Somewhat protable. 33 very = hedge 2 extremely = hedge 3 somewhat = hedge 0.5 slightly = hedge (1 / 3) The user is free to redene these funtions with dierent numbers if they want, of ourse. An example of these in use, using the same sets and denitions in Figure 3.5: Profit> very profitable 10 0.444444 Profit> somewhat profitable 10 0.816497 Hedges an also be used to approximate numbers by onverting them into fuzzy subsets (also known as fuzzy numbers in this ontext) using suh terms as around 20, roughly 20 and nearly 20. One typial way of dening these subsets is by symmetrial triangular fuzzy subsets, entred on the number, say, that we are approximating and with base of width 2w. The membership funtion of this set is thus: (x) = 1 = 0; jx j ; w if w x+w otherwise The tighter the approximation we want, the less fuzzy the fuzzy subset is, and hene the smaller the base of the triangular fuzzy subset is. In general, roughly is a looser approximation than around whih in turn is looser than nearly. For example, onsider the fuzzy numbers in Figure 3.6, whih approximate 20 over the domain [0; 40℄ using triangular fuzzy subsets entred on 20. Here we see that nearly 20 has a base of length 5 and a fuzziness of 0.125; around 20 has a base of length 10 and a fuzziness of 0.25; and roughly 20 has a base of length 15 and a 34 1 0.83 0.75 roughly 20 around 20 nearly 20 0.5 0 0 17.5 20 40 Figure 3.6: Fuzzy approximations to 20. fuzziness of 0.375. So, for example, 17.5 is nearly 20 with truth value 0.5, around 20 with truth value 0.75 and roughly 20 with truth value 0.83. As with hedges, to implement fuzzy numbers in Haskell we dene a generi fuzzy number funtion, whih approximates a number on a spei domain by a triangular fuzzy subset (see Figure 3.3) of speied fuzziness: approximate :: Double -> Double -> Domain Double -> Fuzzy Double approximate fuzziness n dom = tri (n - hw) (n + hw) where hw = fuzziness * (ub dom - lb dom) We now dene the fuzzy number generators near, around and roughly as: near, around, roughly :: Double -> Domain Double -> Fuzzy Double near = approximate 0.125 around = approximate 0.25 roughly = approximate 0.375 This leads to the same sets as in Figure 3.6 if we approximate 20 over the domain [0; 40℄. For example: Profit> near 20 [0..40℄ 17.5 0.5 Profit> roughly 20 [0..40℄ 17.5 35 m e ce dm nt ax ro id m ax m ax ax in m m Truth Value 1 0 0 2 4 6 8 10 Figure 3.7: Defuzzifying a fuzzy subset 0.833333 Profit> around 20 [0..40℄ 17.5 0.75 3.2.4 Defuzziation In a real-world situation, we often need a onrete value rather than a fuzzy subset. The proess of extrating a typial value from a fuzzy subset is known as defuzziation and there are many methods for doing this. Two suh methods are nding the entroid (or entre of gravity) of a fuzzy subset, or nding the maxima of a fuzzy subset and returning a member of this set. If we have a fuzzy subset A with membership funtion A over a domain X then the entroid of A is dened as: R RX xA (x) dx X A (x) dx if X is a ontinuous domain. If X is disrete then the entroid is dened as: P x (x) PX A(x) X A The latter is the denition we use in our implementation. We dene the entroid funtion as: 36 entroid :: Domain Double -> Fuzzy Double -> Double entroid dom f = (sum (zipWith (*) dom fdom)) / (sum fdom) where fdom = map f dom For example, the entroid of the trapezoid fuzzy subset in Figure 3.7 an be evaluated viz Profit> entroid [0, 0.5 .. 10℄ (trap 2 3 6 9) 5.06667 The maxima of a fuzzy subset A over a domain X are those elements m 2 X suh that 8x 2 X : A (m) > A(x). This an be implemented using the ompare funtion from the Haskell prelude: maxima :: Domain a -> Fuzzy a -> [a℄ maxima dom f = foldl m [℄ dom where m [℄ x = [x℄ m ys x = ase ompare (f x) (f (head ys)) of GT -> [x℄ EQ -> x:ys LT -> ys We then typially defuzzify A by returning the minimum, the median or the maximum of the maxima of A: minmax, medmax, maxmax :: Ord a => Domain a -> Fuzzy a -> a minmax dom f = minimum (maxima dom f) maxmax dom f = maximum (maxima dom f) medmax dom f = median (maxima dom f) where 37 -- N.B. Domains are represented by *ordered* lists median ms = head (drop (length ms `div` 2) ms) Defuzzifying the fuzzy subset in Figure 3.7 using these three methods we get: Profit> minmax [0, 0.5 .. 10℄ (trap 2 3 6 9) 3.0 Profit> medmax [0, 0.5 .. 10℄ (trap 2 3 6 9) 4.5 Profit> maxmax [0, 0.5 .. 10℄ (trap 2 3 6 9) 6.0 3.2.5 An Example | Fuzzy Database Queries The linguisti nature of fuzzy subsets make them ideal in database enquiries. In a funtional language this is akin to applying a lter to a list of information. We dene a variant of the standard lter funtion, whih takes a fuzzy prediate (that is, a funtion whih returns a fuzzy truth value) and returns those members of the list that satisfy the prediate to a non-zero degree, along with the degree to whih they satisfy the prediate: ffilter :: Fuzzy a -> [a℄ -> [(a, Double)℄ ffilter p xs = filter ((/=) 0 . snd) (map (\x -> (x, p x)) xs) Referring bak to our prot example, based originally on an example in [82℄, suppose we have the following module: module Profit where import Prelude hiding ((&&), (||), not, and, or, any, all) import Fuzzy 38 type Perentage = Double type Sales = Double -- thousands of pounds type Company = (String, Sales, Perentage) sales :: Company -> Sales sales (_, s, _) = s profit :: Company -> Perentage profit (_, _, p) = p perentages :: [Perentage℄ perentages = [-10..30℄ profitable :: Fuzzy Perentage profitable = up 0 15 high :: Fuzzy Sales high = up 600 1150 ompanies :: [Company℄ ompanies = [("A", 500, 7), ("B", 600, -9), ("C", 800, 17), ("D", 850, 12), ("E", 900, -11), ("F", 1000, 15), ("G", 1100, 14), ("H", 1200, 1), ("I", 1300, -2), ("J", 1400, -6), ("K", 1500, 12)℄ 39 So, we have a list of ompanies, funtions to extrat their prot and sales, and fuzzy subsets profitable of Perentage (using the same denition as before) and high of Sales. To extrat all the protable ompanies from ompanies, we rst dene the fuzzy prediate p1: p1 o = profitable (profit o) and ffilter it over ompanies, viz : Profit> ffilter p1 ompanies [(("A",500.0,7.0), 0.466667), (("C",800.0,17.0),1.0), (("D",850.0,12.0), 0.8), (("F",1000.0,15.0),1.0), (("G",1100.0,14.0),0.933333), (("H",1200.0,1.0),0.0666667), (("K",1500.0,12.0),0.8)℄ So, of the original 11 ompanies, 7 are onsidered protable with C and F being the most protable. Protability by itself might not be enough | we may also want high sales. Dening: p2 o = profitable (profit o) && high (sales o) we an then nd all protable ompanies with high sales: Profit> ffilter p2 ompanies [(("C",800.0,17.0),0.363636), (("D",850.0,12.0),0.454545), (("F",1000.0,15.0),0.727273), (("G",1100.0,14.0),0.909091), (("H",1200.0,1.0),0.0666667), (("K",1500.0,12.0),0.8)℄ Six ompanies satisfy the prediate, with G satisfying it the most. We an use hedges to tighten or loosen the onditions, for example, dening p3 o = somewhat profitable (profit o) && very high (sales o) 40 we an nd those ompanies whih have very high sales and are somewhat protable: Profit> ffilter p3 ompanies [(("C",800.0,17.0),0.132231), (("D",850.0,12.0),0.206612), (("F",1000.0,15.0),0.528926), (("G",1100.0,14.0),0.826446), (("H",1200.0,1.0),0.258199), (("K",1500.0,12.0),0.894427)℄ Here the inreased emphasis on sales, and dereased emphasis on protability means that ompany K now satises the prediate we pass to ffilter to the highest degree. 3.3 Fuzzy Systems Expert Systems [98℄ are used to model real-world situations in many areas of expertise. One ommon way of implementing these systems is as a set of rules and an inferene engine whih manages these rules. Rules are omposed of two parts: an anteedent, whih is a logial expression; and a onsequent whih is an ation whih is performed when the anteedent is true. When this happens we say that the rule res. As a simple example, onsider prediting the shoe size, using British shoe sizes, of a man given his height in metres. In a standard expert system we might have rules like: if 1.65 <= height & height <= 1.72 then shoe_size := 9 These rules are absolutes | if and only if the anteedent holds will the ation be red and red ompletely. In a rule-based fuzzy system, the anteedent is a fuzzy logi expression the value of whih ditates the degree to whih the ation res, the ation being the assignment of a variable to a fuzzy subset. If we have a rule suh as if p then a := F then a is assigned to the fuzzy subset F 0 where F 0 is linearly weighted by the value of p and has 41 1 0 4 small if short then small if medium then average 7 average 10 big Shoe Size (British) if tall then big very_big 13 1 if very_tall then very_big short medium tall very_tall 0 1.5 1.65 1.8 1.95 Height (m) Figure 3.8: The fuzzy rule base for the height ! shoe size expert system 42 membership funtion F (x) = pF (x). This an be extended to multiple variable 0 assignments. Note that if the value of the anteedent is 0, then the membership funtion of the onsequent fuzzy subset will be onstantly 0 (the empty set) and we regard the rule as not having been red. In our shoe size example, the rules are: if height is short then shoe_size := small if height is medium then shoe_size := average if height is tall then shoe_size := tall if height is very_tall then shoe_size := very_big Here is serves as a membership test for height. These rules an be thought of as forming pathes (see Figure 3.8) with the larger the path the fuzzier the rule [58℄. More input variables require more dimensions to the pathes. As an be seen, these pathes overlap, whih in pratial terms means that more than one rule an re, that is, we have more than one possible assignment to shoe size. Rather than seleting one of the possible assignments to a we selet them all, ombining the subsets into one set using an operation suh as union or addition. Addition has the property that, unlike union, when ombining many sets the membership funtion of the result does not approah the onstant funtion 1. Also all the sets that are part of the addition ontribute to the nal result, whereas in the ase of union, large sets (measured by both their support and their height (truth values)) subsume smaller ones. One we have ombined all the resultant sets, we then defuzzify them (see Setion 3.2.4) to obtain a nal result. For instane, if we have a height of 1.75m then this is tall to degree 0.6 and medium to degree 0.2. If we weight the relevant onsequents, sum the sets and defuzzify using the entroid method we obtain an estimated shoe size of 9 41 , while defuzzifying with any of the maxima methods yields a shoe size of 10, sine 10 is the only element of the resultant fuzzy subset whih yields the 43 1 centroid maximum average big 0.6 0.2 0 4 7 9.25 10 13 Shoe Size (British) Figure 3.9: Weighting, adding and defuzzifying the rules for a height of 1.75m largest truth value, in this ase 0.6 (see Figure 3.9). Of ourse, this is a very simple example. More omplex ones an be found in Setion 3.4. We introdue a new operator ==>, whih has the leastmost binding, to the Logi lass: infix 0 ==> lass Logi a where (==>) :: Double -> a -> a -- other defs as before This operator linearly weights its right-hand side by the value on its left-hand side. On fuzzy values, it is simply multipliation: instane Logi Double where w ==> x = if w == 0 then 0 else w * x The onditional is there for eÆieny purposes (we an use it to avoid evaluating x, see below). There are a number of denitions over Bool. One suh denition is: instane Logi Bool where w ==> False = False w ==> True = w > 0.5 -- other defs as before 44 The ==> funtion used over fuzzy truth values is useful in its own right as a fuzzy if-then funtion; an example of its use an be seen in Setion 3.4.2. However its major use is to represent a rule in a fuzzy rulebase, where we normally expet the value on the RHS of the operator to be a fuzzy subset or a tuple of suh sets. On fuzzy subsets, this operator has the denition: instane (Logi b) => Logi (a -> b) where w ==> f = \x -> w ==> f x -- other defs as before Note that by the denition of ==> on fuzzy values, and the fat that Haskell is a lazy language, if w is zero then we know the result is zero without having to evaluate f x. This an have a signiant eet on run-time performane (roughly 20{25% fewer redutions in the ase of our shower example below) if f is a ompliated expression. On tuples we weight eah element of the tuple individually. For pairs we have: instane (Logi b) => Logi (a -> b) where w ==> (a, b) = (w ==> a, w ==> b) -- other defs as before The LHS of the ==> is thus the anteedent of the rule and the RHS of the rule is the onsequent. The result of the funtion is the onsequent linearly weighted by the anteedent, whih will usually be the result of evaluating fuzzy logi expression. To ombine the weighted subsets we dene a funtion whih takes a list of subsets and a funtion to ombine (two of) them with, and returns the result of ombining all the weighted subsets. We thus just have: rulebase :: Logi a => (a -> a -> a) -> [a℄ -> a rulebase = foldr1 Note that we an not apply rulebase to the empty list, but this would imply we had an empty set of rules. The resultant set an then be defuzzied using one the defuzzifying funtions from Setion 3.2.4. 45 Putting this all together, we have the following Haskell module whih implements our shoe-size expert system from above: module Shoe where import Prelude hiding ((&&), (||), not, and, or, any, all) import Fuzzy type Height = Double -- Metres type ShoeSize = Double -- British size sizes :: Domain ShoeSize sizes = [4, 4.5..13℄ short, medium, tall, very_tall :: Fuzzy Height short = down 1.5 1.625 medium = tri 1.525 1.775 tall = tri 1.675 1.925 very_tall = up 1.825 1.95 small, average, big, very_big :: Fuzzy ShoeSize small = down 4 6 average = tri 5 9 big = tri 8 12 very_big = up 11 13 -- alulate the shoe size from a given height 46 shoe_size :: Height -> ShoeSize shoe_size h = entroid sizes ( rulebase (+) [ short h ==> small, medium h ==> average, tall h ==> big, very_tall h ==> very_big℄) Consider the use of the rulebase funtion inside the shoe size funtion. Its rst argument is +, so we are using fuzzy subset addition to ombine the weighted subsets. Its seond argument is the set of rules, written using the ==> operator. During evaluation of the rulebase funtion, eah of these rules will be evaluated, giving the required weighted set, whih will all then be ombined, in this ase using +. This set is then defuzzied using the entroid funtion over the domain sizes. 3.4 Further Examples We now give two further examples of problems solved using fuzzy logi: another fuzzy system (more omplex than our shoe size example), and a deision-making exerise. 3.4.1 Controlling a Shower Consider the problem of ontrolling a shower [83℄. We wish to get the temperature to between 34Æ C and 38Æ C and the ow of the water between 11 l/min and 13 l/min. To do this we have two taps, one hot and one old, whih take values between 0 (fully o) and 1 (fully on). We divide the temperature into the fuzzy subsets hot, ok and old; the ow into the fuzzy subsets weak, right and strong; and the possible tap hanges (ranging from 0:2 to 0:2) into seven fuzzy subsets: pb (big 47 1 Cold Hot OK 0 0 20 40 60 80 Temperature (Celsius) 1 Weak Strong Right 0 0 5 10 15 20 25 Flow (l/min) NB -0.2 NM -0.1 NS 1 Z PS 0 PM PB 0.1 0.2 Tap Change Figure 3.10: Fuzzy subsets of temperature, ow and tap hange positive hange), pm (medium positive hange), ps (small positive hange), z (zero hange), ns (small negative hange), nm (medium negative hange) and nb (big negative hange). These fuzzy subsets an be seen in Figure 3.10. Unlike our shoe size example, the shower is not meant to be a one-use funtion but rather to be ontinually iterated until the temperature and the ow are in the orret range. So we are ontinually making hanges (with suitable gaps in between these hanges to let the shower settle into its new settings) until the water beomes aeptable. We have the following system (note that these are not the original sets used in the Fuzzy CLIPS example, whih used urved rather than polygonal fuzzy sets, and hene we have adjusted the numbers to get a better performane): module Shower where import Prelude hiding ((&&), (||), not, and, or, any, all) import Fuzzy 48 type Temp = Double type Flow = Double type Change = Double old, ok, hot :: Fuzzy Temp old = down 15 36 ok = tri 32 40 hot = up 36 75 weak, right, strong :: Fuzzy Flow weak = down 0 12 right = tri strong = up 9 15 12 25 nb, nm, ns, z, ps, pm, pb :: Fuzzy Change nb = down (-0.2) (-0.05) nm = tri (-0.1) (-0.025) ns = tri (-0.05) z 0.0 = tri (-0.025) 0.025 ps = tri 0.0 0.05 pm = tri 0.025 0.1 pb = up 0.05 0.2 hange_valves :: (Temp, Flow) -> (Change, Change) hange_valves (temp, flow) = (defuz hv, defuz v) where 49 defuz = entroid [-0.2, -0.195..0.2℄ (hv, v) = rulebase (+) [ old temp && weak flow ==> (pm, z), old temp && right flow ==> (pm, z), old temp && strong flow ==> (z, nb), ok temp && weak flow ==> (ps, ps), ok temp && strong flow ==> (ns, ns), hot temp && weak flow ==> (z, pb), hot temp && right flow ==> (nm, z), hot temp && strong flow ==> (nb, z)℄ Normally suh a system would be used in a real-word environment. However, for testing purposes, we provide a simple simulation in Haskell. The simulation reates a new shower, setting the temperatures of the hot and old taps to a random value in the ranges [67Æ C; 77Æ C℄ and [10Æ C; 20Æ C℄ respetively and setting the ows of both taps are set to a random value in the range [10 l/min; 14 l/min℄ using the Random pakage from the Haskell 1.4 standard library. Note the use of the IO Monad [114, 115℄ to allow printing as a side-eet and the generation of random numbers using a system-dependent seed number. The total temperature and ow is then alulated, assuming perfetly ylindrial pipes, and the hanges to the valves required alulated using the fuzzy rulebase above. These hanges are then made, the new temperature and ow alulated and further hanges made if neessary: type Valve = Double -- should be in the range [0..1℄ -- Eah tap ontains the absolute temperature and flow of the water -- it ontrols plus the tap setting data Shower = Shower {hot_valve, old_valve :: Valve, 50 hot_temp, old_temp :: Temp, hot_flow, old_flow :: Flow} deriving Show -- Creates a new shower using the given settings of the hot -- and old valves mkShower :: Valve -> Valve -> IO Shower mkShower hv v = do rs <- randomIO (-100, 100) return (Shower {hot_valve = hv, old_valve = v, hot_temp = 72 + dht rs, old_temp = 15 + dt rs, hot_flow = 12 + dhf rs, old_flow = 12 + df rs}) where dht rs = 5 * fromInteger (rs !! 0) / 100.0 dt rs = 5 * fromInteger (rs !! 1) / 100.0 dhf rs = 2 * fromInteger (rs !! 2) / 100.0 df rs = 2 * fromInteger (rs !! 3) / 100.0 -- Calulates the temperature and flow of the given shower getTempFlow :: Shower -> (Temp, Flow) getTempFlow shower | flow > 0 = (temp, flow) | otherwise = error "Shower.getTempFlow: non-positive flow" where 51 f = propOpen (old_valve shower) * old_flow shower hf = propOpen (hot_valve shower) * hot_flow shower flow = hf + f temp = (hf / flow) * hot_temp shower + (f / flow) * old_temp shower propOpen :: Valve -> Double propOpen x | x < 0 = 0 | x > 1 = 1 | x <= 0.5 = (two_theta - sin (two_theta)) / (2 * pi) | otherwise = 1 - propOpen (1 - x) where two_theta = 2 * aos (1 - 2 * x) -- applies the given hanges to the shower adjustValves :: (Change, Change) -> Shower -> IO Shower adjustValves (dh, d) shower = return (shower {old_valve = restrit (old_valve shower + d), hot_valve = restrit (hot_valve shower + dh)}) where restrit x | x < 0.0 = 0.0 | x > 1.0 = 1.0 | otherwise = x -- repeatedly adjusts the given shower until its temp and flow are -- satisfatory 52 adjustShower :: Shower -> IO Shower adjustShower shower = do putStr ("Hot = " ++ show (hot_valve shower) ++ ", Cold = " ++ show (old_valve shower) ++ ", Temp = " ++ show t ++ ", Flow = " ++ show f ++ "\nHot hange = " ++ show dh ++ ", Cold hange = " ++ show d ++ "\n\n") shower' <- adjustValves (dh, d) shower if satisfatory then return shower' else adjustShower shower' where (dh, d) = hange_valves (t, f) (t, f) = getTempFlow shower satisfatory = 34.0 <= t && t <= 38.0 && 11.0 <= f && f <= 13.0 shower :: Valve -> Valve -> IO () shower hv v = do shower <- mkShower hv v putStr ("Initial shower: " ++ show shower ++ "\n") final_shower <- adjustShower shower putStr ("Final shower: " ++ show final_shower ++ "\n") So, for example, if we have an initial valve setting of 0.2 and 0.4 for the hot and old valves respetively, then the adjustment sequene is: Shower> shower 0.2 0.4 Initial shower: Shower{hot_valve=0.2, old_valve=0.4, 53 hot_temp=72.8, old_temp=19.65, hot_flow=11.6, old_flow=12.12} Hot = 0.2, Cold = 0.4, Temp = 33.857, Flow = 6.17877 Hot hange = 0.0342672, Cold hange = 0.0204951 Hot = 0.234267, Cold = 0.420495, Temp = 35.5691, Flow = 6.90705 Hot hange = 0.0275251, Cold hange = 0.0238471 Hot = 0.261792, Cold = 0.444342, Temp = 36.5224, Flow = 7.62269 Hot hange = 0.0241146, Cold hange = 0.0368691 Hot = 0.285907, Cold = 0.481211, Temp = 36.7517, Flow = 8.50753 Hot hange = 0.0234472, Cold hange = 0.0448707 Hot = 0.309354, Cold = 0.526082, Temp = 36.7072, Flow = 9.51635 Hot hange = 0.0138068, Cold hange = 0.0467235 Hot = 0.323161, Cold = 0.572806, Temp = 36.1902, Flow = 10.4232 Hot hange = 0.0197123, Cold hange = 0.0357849 Hot = 0.342873, Cold = 0.60859, Temp = 36.285, Flow = 11.2405 Hot hange = 0.0110444, Cold hange = 0.0514842 Final shower: Shower{hot_valve=0.353918, old_valve=0.660075, hot_temp=72.8, old_temp=19.65, hot_flow=11.6, old_flow=12.12} 54 3.4.2 Priing Goods The fat that fuzzy logi is inherently ontraditory, that is we have truth values whih are non-zero and whose negation is also non-zero, is useful in deision making proesses where the deisions we have to make are based on oniting demands or requirements. Fuzzy logi an be used to resolve these ontraditions in a natural, simple and eÆient way. Consider the problem of priing goods [21℄. The prie should be as high as possible to maximise takings but as low as possible to maximise sales. We also want to make a healthy prot, say a 100% mark-up on the ost prie. Then we have to onsider what the ompetition is harging. We an formalise these requirements as rules: 1. Our prie must be high. 2. Our prie must be low. 3. Our prie must be around 2 manufaturing osts, in other words, a 100% mark-up. 4. If the ompetition prie is not very high then our prie must be around the ompetition prie (we do not want to indulge in a prie war). A boolean system may have diÆulties trying to resolve the requirements that the prie must be high and low, not to mention the other two requirements, but a fuzzy system has no suh diÆulties. Suppose possible pries are in the range $15 to $35. We dene fuzzy subsets high and low on this range, viz : type Prie = Double -- Pounds Sterling 55 pries :: Domain Prie pries = [15.00, 15.50 .. 35.00℄ high, low :: Fuzzy Prie high = up 15.00 35.00 low = not high So if we want a prie that is high and low (Rules 1 and 2) then we an alulate this by taking the intersetion of high and low and defuzzifying the resultant set to get a typial value, viz : our_prie = entroid pries (high && low) Evaluating our prie we get: Pries> our_prie 25.0 Rule 3 suggests that we an approximate the prie by a fuzzy number entred on 2 manufaturing osts. Taking the manufaturing osts as a parameter to our prie and ombining this with what we have so far, we dene our_prie' man_osts = entroid pries (high && low && around (2.0 * man_osts) pries) Assuming manufaturing osts of $13.25, say, we have: Pries> our_prie' 13.25 26.252 Rule 4 is a onditional rule. The more that the ompetition prie is not very high, the more it aets the alulation of our prie. Using the ==> operator and taking the ompetition prie as another parameter, we get: 56 our_prie'' man_osts omp_prie = entroid pries (high && low && around (2.0 * man_osts) pries && ((not.very high) omp_prie ==> around omp_prie pries)) Assuming the same manufaturing osts as before and a ompetition prie of $29.99 we have: Pries> our_prie'' 13.25 29.99 28.5893 So our nal retail prie is $28.59. 3.5 Summary We have introdued and explored the use of fuzzy logi in funtional programming. The natural equivalene between fuzzy subsets and their membership funtions motivates our idea to use a single funtion to model them both. We have shown how a funtional language an be extended so that it provides failities for the use of fuzzy logi and fuzzy subsets, ahieved by overloading pre-existing operators and funtions, and introduing new ones. We have also shown how fuzzy systems, used in a variety of ontrol and deision making problems, an be implemented in a funtional language in a natural and eÆient way. 57 Chapter 4 Compiling Lazy Funtional Programs to Java Byte-ode The Java Virtual Mahine (JVM) [79℄ provides a mahine-independent exeution environment whih exeutes Java byte-ode, whih is essentially a mahine ode for objet-oriented programs. It was designed as the target of Java ompilers, but there is no reason why ompilers of other languages annot produe ode that will run on it. We are interested in using it to run funtional programs, in partiular pure lazy ones. This approah has several advantages: Java byteode will run on any mahine for whih an interpreter is available. Java programs an be run as applets in web-browsers, or in embedded systems. Java has a built-in garbage-olletor, hene any language whih targets Java byteode has no need to handle garbage olletion itself. Our aim is to reate a ompiler whih will translate a funtional program into byteode, with eah funtion being translated into a stati method of the generated lass le. If the funtional program we are ompiling is designed to be exeuted (as 58 opposed to being a set of library funtions, for example) then we also generate a main method whih will, when the lass le is exeuted, perform any initialisation neessary and evaluate the program whih we have ompiled. Our soure language is Ginger [53℄, a simple, pure, lazy, weakly-typed funtional language. We base our evaluation methods on those of the G-mahine (see Setion 2.5). We assume that the reader is familiar with programming in Java, and has some understanding of how Java lasses are strutured, and also how to program using a lazy funtional language, but we assume no prior knowledge of the JVM (whih we desribe in Setion 4.1) itself or of implementing funtional languages. Referene is made bak to Setion 2.3 for an introdution to graph redution. 4.1 The Java Virtual Mahine The JVM [79℄ provides an environment for exeuting objet-oriented programs; that is, for reating objets, invoking their methods, manipulating their elds, as well as the usual basi operations, suh as adding integers. The format of the byteode resembles that of Java programs [7℄. For eah new lass, there is a header delaring the lass, its superlass and its pakage and the delaration of the elds and methods. Eah method provides a separate environment onsisting of a stak, whih is used as a working spae, and a set of loal variables. In the ase of an instane method, register 0 holds a referene to the objet that the method was invoked on (the this referene), and variables 1; : : : ; n hold the n parameters of the method. Stati methods are slightly dierent in that sine there is no objet to invoke the method on, the n parameters of the method are stored in variables 0; : : : ; n 1. Eah lass also has a onstant pool where all the symboli data used by the lass | elds, methods, lass, interfaes, et. | is stored. For example, onsider the following lass denition: 59 publi lass ExampleClass { publi int value; private stati ExampleClass one = new ExampleClass(1); publi ExampleClass(int v) { value = v; } publi stati ExampleClass getOne() { return one; } publi void add(ExampleClass e) { value += e.value; } } This lass an be ompiled into a lass le of byteodes using a Java ompiler, java say. The lass le an then be examined using a disassembler, suh as javap [103℄. If we examine the output of javap, rst of all we have the header of the le whih delares the lass's super-lass and its members: Compiled from ExampleClass.java publi synhronized lass ExampleClass extends java.lang.Objet { publi int value; private stati ExampleClass one; publi ExampleClass(int); 60 publi stati ExampleClass getOne(); publi void add(ExampleClass); stati stati {}; } We then have the byteode for the onstrutor funtion: Method ExampleClass(int) 0 aload_0 1 invokespeial #3 <Method java.lang.Objet()> 4 aload_0 5 iload_1 6 putfield #6 <Field int value> 9 return A referene to the the objet that is reated by the onstrutor is held in register 0. This is plaed on the stak by the aload 0 instrution. The invokespeial #3 pops the top objet o the stak (this) and invokes on it the zero-argument onstrutor of its superlass whih is Objet (item number 3 in the onstant pool). When this has ompleted, this is again loaded onto the stak and then the integer 1 is loaded onto the stak by the iload 1 instrution. The putfield #6 instrution pops the top two values of the stak and stores the value that was held at the top of the stak (the integer 1) in the value eld (item number 6 in the onstant pool) of the objet that was the seond topmost item in the stak (this). The work is now done, the stak is empty, and the onstrutor funtion returns to the method that alled it, with a void result, using the return instrution. We then have the two method delarations. The method getOne merely loads the stati eld one onto the stak and returns it using the areturn instrution: Method ExampleClass getOne() 61 0 getstati #5 <Field ExampleClass one> 3 areturn The add method is a little more ompliated: Method void add(ExampleClass) 0 aload_0 1 dup 2 getfield #6 <Field int value> 5 aload_1 6 getfield #6 <Field int value> 9 iadd 10 putfield #6 <Field int value> 13 return This rst loads the this referene onto the stak and dupliates it using the dup instrution, leaving the opy on the top of the stak. The getfield #6 instrution pops the top of the stak and gets the value of its value eld whih it puts on top of the stak. We then load the argument of the method (named e in the original ode) whih is in register 1 and get the value of its value eld. The top two items on the stak are now the two integers equal to the value elds of the objet the method was invoked on and the argument of the method. These integers are popped o the stak and added together using the iadd instrution whih plaes the result on the stak. This value is then stored in the value eld of the objet referened by this | reall the dup instrution | and the method returns to its aller, returning a void value. The nal method is the lass's stati initialiser whih is exeuted when the lass is loaded. This has the job of reating a new ExampleClass whose value eld is set to 1 and storing it in the stati eld one: 62 Method stati {} 0 new #1 <Class ExampleClass> 3 dup 4 ionst_1 5 invokespeial #4 <Method ExampleClass(int)> 8 putstati #5 <Field ExampleClass one> 11 return 4.2 The Ginger Language The Ginger language [53℄ was developed at the University of Warwik as a means of investigating parallelism in funtional languages (we do not onsider the parallel features of the language in this thesis). It is a simple, weakly-typed, pure lazy funtional language with no pattern-mathing or user-dened types. After parsing, our Ginger ompiler transforms the soure tree into a set of superombinators (see Setion 2.1) by lifting any -abstrations into separate funtion denitions [49℄. Then dependeny analysis [86℄ is performed, whih transforms loal variable denitions into bloks of simple, non-reursive let expressions and bloks of minimally mutually-reursive letre bloks. The soure at this stage is strutured as in Figure 4.1. We have an optional pakage delaration in whih the lass we eventually reate will be plaed. Then ome a number of import delarations whih deal with the importing of Ginger funtions (stored in Java lasses) from other soures followed by the denitions of the superombinators, both those dened by the user and those reated by lambda-lifting. 63 hprogram i ::= (pakage hpakage i;)? (import hlass i;) hdenition i hdenition i ::= hidentier i hidentier i hexpr i ::= j j j j j j j j = hexpr i; hinteger i j hboolean i j hoat i j hharater i j hstring i hidentier i [℄ (empty list) (hexpr i:hexpr i) (list onstrutor) (hexpr i hexpr i) (appliation) (hexpr i, : : : ,hexpr i) if hexpr i then hexpr i else hexpr i endif let hidentier i = hexpr i in hexpr i endlet letre hidentier i = hexpr i ::: hidentier i = hexpr i in hexpr i endletre Figure 4.1: The EBNF of the Ginger language after lambda-lifting and dependeny analysis. 4.3 Representation of Graph Nodes Sine we are reating a Java lass le, it makes sense to represent graph nodes by Java objets. The ve simple types | integers, oats, booleans, haraters and strings | are represented using the Java lasses Long, Double, Charater, Boolean and String found in the java.lang pakage. The rst four are the objet equivalent of the primitive Java types long, double, har and boolean respetively. Note that we use the largest size possible for integers and oats and we do not represent strings as list of haraters. Lists are represented using the List lass, whih is just an empty lass whih 64 its two sublasses Cons (list onstrutor) and EmptyList sublass. The Cons lass has the following skeleton denition: publi final lass Cons extends List { publi Objet head; publi Objet tail; // ... } The representation of funtions utilises the java.lang.reflet pakage whih provides lasses that `reet' the members of the lass, in partiular there is a Method lass the instanes of whih reet a partiular method of a partiular lass. This lass has an instane method invoke: publi Objet invoke(Objet obj, Objet[℄ args) This invokes the the method reeted by the instane on the objet obj with the arguments in the array args. If the method being reeted is stati then obj is ignored and an be null. Our ompiler will translate all the superombinators in a le into stati methods of the generated lass le (see Setion 4.5). This gives us a maximum of 255 arguments and loal variables per funtion. We use stati methods as these only operate on their arguments whereas instane methods also require an instane of the lass of whih they were dened in. When we import a lass le we thus store eah of its methods (funtions of the original program) as Method objets whih are held inside Fun objets: publi lass Fun { publi final Method method; 65 publi final int arity; publi final boolean isCAF; publi Fun(Method m) { method = m; arity = m.getParameterTypes().length; isCAF = arity == 0; } // ... } Our representation of appliations is guided by the type of the invoke method of the Method lass, whih will be alled every time we apply a funtion. Sine this method expets its arguments to be paked into an array, it makes sense to store the arguments of an appliation in an array, rather than in some intermediate data struture from whih we make an array. In partiular, we use multiple-argument appliations. The App lass has the following denition: publi final lass App { publi Objet funtor; publi Objet[℄ args; publi boolean in_nf = false; publi boolean total_app = false; // ... } 66 This represents the appliation funtor args[0℄ : : : args[args.length-1℄. The eld in nf is set when the App is in normal form, that is when the funtor is a funtion and there are not enough arguments present, or the App is ating as an indiretion to a non-appliation (see Setion 4.3.1). If funtor is a funtion and it is applied to exatly the right number of arguments then we set the total app eld. The use of the in nf and total app elds prevents unneessary work being done. 4.3.1 Updating As we saw in the Setion 2.3, we need to update the original appliation with the result of the appliation. This is to prevent the unneessary re-evaluation. For instane, the appliation square ((+) 3 4) beomes (*) ((+) 3 4) ((+) 3 4) (with the instanes of (+) 3 4 being shared) and (+) 3 4 beomes 7. In the ase where we update one appliation with another, we simply opy the result eld-by-eld onto the original appliation. However, if the result is not an appliation, as in the seond ase, then things are not so simple as we annot opy an objet of one type onto an objet of a dierent type. Instead, we turn the App into an indiretion by setting its args eld to the null referene and setting its funtor eld to the result in question. We an view any App with a null args eld as serving as an indiretion or a wrapper to its atual argument. Note that one the result of an appliation beomes a non-appliation it is in normal form and thus no further evaluation is neessary and so we do not get hains of indiretions. Updating is done in the method App.update: private void update(Objet o) { if (o instaneof App) { // opy o onto this App App a = (App) o; 67 funtor = a.funtor; args = a.args; in_nf = a.in_nf; total_app = a.total_app; } else if (o instaneof Fun) { funtor = o; args = empty; if (((Fun) o).isCAF) { total_app = true; in_nf = false; } else { total_app = false; in_nf = true; } } else { // we have an indiretion funtor = o; args = null; in_nf = true; total_app = false; } } Here empty is a eld of App whih is set to b an empty array of Objets. Realling our original example, square ((+) 3 4) redues to the expression (*) ((+) 3 4) ((+) 3 4). Before updating we have: 68 Original Expression Reduction Expression App App square * { , } App + {3, 4} Updating the original expression involves reassigning its funtor and args elds (ignoring the other two boolean elds for the moment): Original Expression Reduction Expression App App * { , } App + {3, 4} After the redution of (*) ((+) 3 4) ((+) 3 4) our App beomes an indiretion to 49: App 49 null Appliations are not the only graph nodes that an be updated, there is one other ase, namely CAFs, that is funtions taking zero arguments. These an be treated as appliations of the CAF in question to zero arguments and we do just this by storing all CAFs as Apps whose funtor is the atual CAF in question and whose 69 args is the empty array (note that if we wish to invoke a method that takes no arguments then we need to pass an empty array to Method.invoke). 4.3.2 Evaluation The evaluation of graph nodes is ontrolled by the method eval in the lass Node whih ontains various stati methods used for the evaluation and printing of graph nodes. The method only has to do something when it is alled to evaluate an App, otherwise it simply returns its argument. The instane method App.eval repeatedly evaluates and updates itself until it is in normal form. One it beomes so it returns its funtor if it is an indiretion, or itself otherwise: publi Objet eval() { while (! in_nf) { if (total_app) // ... else if (funtor instaneof Fun) // ... else // ... } // if we have an indiretion, return the funtor, // else return the whole App return (args == null) ? funtor : this; } The App being evaluated forms the spine of the graph, with the funtor being the base of the spine and the args forming the ribs. If the App is not in normal 70 form then we have one of three ases, orresponding to the three branhes of the if-then-else ladder inside the while loop. If we have a funtion applied to the exat number of arguments (that is when total app is true) we simply need to apply the funtion to its arguments and update the App: if (total_app) update(((Fun) funtor).apply(args)); The method Fun.apply is where all the work is done. In this method we unpak any indiretions from Apps and then invoke the funtion on these unpaked arguments: publi Objet apply(Objet[℄ as) { for (int i = 0; i < as.length; i++) if (as[i℄ instaneof App) { App a = (App) as[i℄; if (a.args == null) // N.B. we keep CAFS inside Apps as[i℄ = a.funtor; } return method.invoke(null, as); } If we have a funtion applied to too many arguments, that is, the funtor is a funtion and both total app and in nf are false, we split the args array into two, apply the funtor to the number of arguments it needs and update the funtor and args elds appropriately: else if (funtor instaneof Fun) { // we must have more arguments than the funtion takes 71 Fun f = (Fun) funtor; // split this array into two parts. int unused = args.length - f.arity; Objet[℄ first = new Objet[f.arity℄; Objet[℄ rest = new Objet[unused℄; split(args, f.arity, first, rest); // the funtor beomes the result of apply f to the first // arity arguments funtor = f.apply(first); // and the args beome the rest of the args args = rest; setType(); } The method App.setType examines the funtor and args elds of the App and sets total app and in nf appropriately. The last ase is when the funtor is another App in whih ase we need to unwind the App at the funtor onto this one. If the funtor is a funtion applied to the orret number of arguments, then we do the appliation before unwinding: else { // funtor instaneof App // need to unwind App a = (App) funtor; 72 if (a.total_app) // the funtor ontains a funtion applied to the orret // number of arguments, so we apply it and ontinue unwinding a.update(((Fun) a.funtor).apply(a.args)); else { funtor = a.funtor; args = at(a.args, args); } setType(); } The stati method App.at joins together two arrays in a manner similar to list onatenation in funtional languages. 4.3.3 Printing The evaluation of graph nodes is initially triggered by the Node.print method whih evaluates a node and prints it on standard output: publi final stati void print(Objet node) { node = eval(node); if (node instaneof Cons) { System.out.print("["); boolean not_at_end = true; do { Cons = (Cons) node; 73 .head = eval(.head); // evaluate and update head .tail = eval(.tail); // and tail printNoEval(.head); if (.tail instaneof Cons) { System.out.print(", "); node = .tail; } else not_at_end = false; } while (not_at_end); System.out.print("℄"); } else if (node instaneof Evaluatable) ((Evaluatable) node).print(); else System.out.print(node); } The method Node.printNoEval is similar to print exept that it presumes that its argument is in normal form and thus does not need to evaluate the objet before printing it. If the node to be printed is a Cons then we iteratively print out eah element of the list. It is done this way, rather than farming it out to some method of Cons, whih would be the standard objet-oriented way, as we do not wish to keep an unneessary referene to the head of the list (note that we repeatedly overwrite the head in Node.print), whih ould lead us to keeping the whole of the list that is being printed in memory when in reality it ould be garbage and the memory 74 it oupies ould be freed. We also reassign .head and .tail after evaluating them; this enables us to bypass any indiretion introdued by any evaluation whih needed to be done sine the eval method will return the evaluated objet unpaked from the appliation whih is serving as an indiretion. The Evaluatable lass is implemented by all Ginger lasses whih have omponents whih need to be evaluated before being printed, in partiular it is implemented by the various tuple lasses. It speies a print method whih evaluates the omponents of an objet and prints them out on standard output. Note that the toString method on these objets does not evaluate the omponents before printing (this is used for debugging purposes). So, node is an instane of this interfae, we print it out using the print method. Finally, if node is neither a Cons objet or implements the Evaluatable interfae, we simply print out the node using the toString method (whih is impliitly alled by the System.out.print method). 4.4 Primitives Like user-dened funtions we store primitives, suh as arithmeti operators, omparison operators, list onstrutors and deonstrutors, in Java lass les. The primitives are spread over a number of lasses, whih are all sublasses of the Node lass whih ontains the top-level evaluation and print routines. The strit primitives are kept in dierent lasses to the lazy ones, thus giving us a simple, if restrited, way of determining if a primitive is strit. We also ensure that the result of all strit primitives is in normal form. We allow overloading on primitives, for example, we use - for integer and real subtration, but this is done in an ad ho way inside the primitive itself making use of the Java instaneof operator rather than in any systemati kind of way suh 75 as the use of type lasses in Haskell (see Setion 2.4). For example, subtration is performed using the minus method in the lass StritPrimitives: publi lass StritPrimitives extends Node { publi final stati Class TYPE = StritPrimitives.lass; publi stati Objet _minus(Objet lhs, Objet rhs) { lhs = eval(lhs); rhs = eval(rhs); if (lhs instaneof Long && rhs instaneof Long) return new Long(((Long) lhs).longValue() ((Long) rhs).longValue()); else return new Double(((Number) lhs).doubleValue() ((Number) rhs).doubleValue()); } publi final stati Objet _minus = Funtion.make(TYPE, "_minus", 2); // ... } The StritPrimitives lass has a TYPE eld whih stores the Class representation of itself whih is used to onstrut the Objet equivalent of eah method. The minus method rst evaluates its two arguments and reassigns them (remember that the eval method unpaks any indiretions). It then determines whether it is integer or real subtration is required by means of the instaneof operator (integers are ast to reals if neessary), does the neessary subtration and returns a new objet ontaining the result of the subtration. 76 The struture of the primitive lasses and the lasses reated by the ompiler are idential and hene we an treat primitives exatly the same way as we do user-dened funtions exept in the ase of a tail reursion (see Setion 4.5.5). 4.4.1 Fuzzy Primitives As with our Haskell implementation, we overload the logial operators so that they work on fuzzy values as well as boolean ones, and also so that they at as set operators when used on funtions. This is supplied as an optional extra with the ompiler sine the inreased overloading leads to a small performane penalty. Sine Ginger has no systemati method of overloading, the overloading is done inside the primitive denition and overloading resolution done at run-time. For instane, logial onjuntion (set intersetion) is dened in the method and: publi stati Objet _and(Objet lhs, Objet rhs) { lhs = eval(lhs); if (lhs instaneof Boolean) return (FALSE.equals(lhs)) ? FALSE : rhs; else if (lhs instaneof Number) return new Double(Math.min(((Number) lhs).doubleValue(), ((Number) eval(rhs)).doubleValue())); else if (lhs instaneof Tuple) return ((Tuple) lhs).apply(_and, (Tuple) eval(rhs)); else return new App(new Objet[℄ {rhs, lhs, _and}, ombine); } publi stati final Objet _and = Funtion.make(TYPE, "_and", 2); 77 The rst two branhes of the onditional should be fairly self-explanatory: they implement boolean and fuzzy onjuntion respetively. The next branh deals with onjuntion over tuples. The method Tuple.apply is used to ombine two tuples of the same size into a new one, ombining orresponding elements with the supplied funtion, here and. The nal ase assumes we have a funtion, that is, a fuzzy subset. The two sets and the primitive and are joined together with the funtion ombine whih is equivalent to the S0 ombinator dened as: S0 op f g x = op (f x) (g x) We an dene the other primitives, disjuntion, negation and addition, similarly. The rest of the denitions in Chapter 3 have the obvious translations from Haskell into Ginger. 4.5 Compilation In this setion we shall deal with the reation of Java lass les from our Ginger soure whih has been lambda-lifted and had dependeny analysis performed on it (see Figure 4.1). Rather than reating the lass les diretly, or using the Java language itself as a soure (whih would ompliate matters viz loal variables), we target the Jasmin assembly language [79℄. This language is very similar to the byteode used by the Java Virtual Mahine, but is easier to program in as it deals with suh things as the Java onstant pool (where all onstants and objet referenes as suh) and alulating osets for jumps automatially. Our Ginger program, in the le prog .g, is ompiled into an intermediate Jasmin le prog .j (whih may be disarded after use), whih is assembled into a Java lass le prog .lass by Jasmin. Eah superombinator denition of our Ginger program is ompiled into a stati method of the lass le we are reating. Funtions are not ompiled by reating ode to reate a new instane eah time one ours, but rather a single instane is 78 reated and is stored it as a stati eld of the lass we are reating. These elds will be set up in a stati initialiser of the lass we are reating. Therefore, whenever we want a funtion we just aess the relevant eld. This method also applies when we want to aess a funtion dened in another lass. The job of the various import delarations is thus just to tell us in whih lass to nd eah funtion that we use. There is a limit on the number of elds (in our ase, funtions) in a lass (65,535) but if this limit is reahed then program an be split up into smaller segments (a program whih hit this limit must have been fairly big and unwieldy anyhow). Our ompilation shemes are based on those presented in [86, 89℄. We view our soure as a triple hl ; fs ; ss i where l is the lass we are to reate, fs is the set of all funtions dened or imported and ss is the set of superombinator denitions. Note that although the JVM has shorter, more optimal versions of some instrutions (the instrution ionst 0 is a more eÆient way of loading the integer zero onto the stak than ld2 w 0, for example) for larity we use the most general instrution in our desription of the ompilation shemes, though we do use the most eÆient instrution in our atual implementation. Our primary ompilation sheme, P, starts o as: Phl ; fs ; fs1 ; : : : ; sngi = .lass publi l .super Objet This delares our lass and its superlass. Note that Jasmin requires the full name of all lasses and members but for brevity we have omitted the pakage name where this is obvious, indiating the omission by using italis for objet names rather than teletype. We then proeed by delaring the elds orresponding to eah funtion dened in the le: .field publi stati (s1 ) LObjet ; 79 .. . .field publi stati (sn ) LObjet ; The funtion returns the name of the eld that holds the superombinator, whih is just its name of said superombinator. Note that when an objet name, obj say, is used as a type it is written as Lobj ;. Funtions imported will be delared and dened in the lass that they are imported from. P progresses by setting eah of these elds to its appropriate value inside a stati initialiser whih is a method alled linit whih takes no arguments and returns void (indiated by the V): .method <linit>()V new l dup invokespeial l /<init>()V invokevirtual l /getClass()LClass ; astore 0 D s1 .. . D sn return .end method The method rst gets the Class objet reeting the lass we are reating and stores it in register 0. This Class objet is used when reating the Fun objet representing eah superombinator (or Apps in the ase of CAFs). D reates the ode neessary to reate a new instane of its argument and store it in the relevant eld. If we have 80 a superombinator, s say, then we have: Ds = aload 0 ld n(s) ld a(s) invokestati Funtion /make(LClass ;LString ;I)LObjet ; putstati (s) LObjet ; This loads the Class objet reeting l onto the stak, then the name of the superombinator (using the funtion n) and its arity (using a). This information is then used by the method Funtion.make to reate a Fun objet (non-CAFs) or an App (CAFs) whih is then stored in the appropriate eld. Returning to the P sheme, after delaring and dening our onstants, we now need to reate the ode for eah of the superombinators. This is done using the F sheme: Phl ; fs ; f1 ; : : : ; m g; fs1 ; : : : ; sngi = .. . F fs s1 .. . F fs sn The F sheme is dened below. Finally, we need to determine if we need to reate a main method whih will make the lass le exeutable, using the java interpreter, say. This is so if we have dened a funtion alled main. The main method will print the result of evaluating the Ginger funtion main whih we rename main, so as to separate the redution rule from the ode whih does the evaluation and printing. The ode for this is: .method publi stati main([LString ;)V 81 getstati ( main) LObjet ; invokestati Node /print(LObjet ;)V return .end method Here [LString ; denotes an array of strings (the losing brae is not used) whih in the ase of the arguments to the main method represent the ommand-line arguments passed by the Java interpreter. If we do not reate a main method then we an view the generated lass as a library of funtions. 4.5.1 Compiling Superombinators We now need to give the denition of F whih reates a method from a superombinator. This method takes n arguments of type Objet, where n is the arity of the superombinator in question, and returns an objet of type Objet. The return objet will be the objet left on the top of the stak by the ode generated by the R sheme, whih ompiles the expression on the right-hand side of a superombinator denition. F [f x1 : : : xn = E ℄ fs = .method publi stati f ( LObjet ; : : : LObjet ; )LObjet ; | R E [x1 = 0; : : : ; xn = n 1℄ fs n n {z times } areturn .end method The sheme R takes as arguments the expression to ompile, an environment de- tailing whih register eah variable is in, the set of funtions dened and imported, and the next free variable register (used to store loal variables). 82 4.5.2 The R Compilation Sheme The purpose of the R sheme is to take an expression whih forms the right-hand side of a denition (the return expression) and ompile it to ode that will, when exeuted, reate the graph of the expression and leave a referene to it on top of the stak. If we have an integer, i say, then we have to reate a new Long objet to store it in: R i fs v = new Long dup ld2 w i invokespeial Long /<init>(J)V The instrution ld2 w is used to load a long or a double onto the stak (eah stak ell is 32 bits in size, but long integers and doubles take up 64 bits so they have to be spread aross two ells). A similar method is used to dene the other types of onstants. If the right-hand side of a super-ombinator onsists of a single variable then, unless we evaluate it before returning, we risk doing extra work beause some loss of sharing ours. Suppose we have an expression f x1 expression g y1 : : : ym : : : xn whih redues to an and further suppose that this latter expression is reduible. These are represented as two Apps and reall that updating the original expression involves opying the elds of the App representing the value of the redution onto the elds of the App representing the original expression. If the App g y1 : : : ym was reated during the evaluation of the redution rule for f then the App objet g y1 : : : ym is never used again (in other words, it beomes garbage), though its elds beome the elds of the original expression. However, if the App was not reated by redution rule then it must be referened by some other 83 part of the graph and hene we have two opies of the same appliation: App App g Original Expression Suppose that g y1 : : : ym { y1 , ..., ym} Reduction Expression evaluates to some expression E , say. Then if we ontinue our evaluation we have: E App g { y1 , ..., ym} Note that we have only redued one of the Apps: by opying we have lost not only the sharing of nodes but the sharing of work. This situation ours whenever we have a funtion whose return value is a single variable or funtion (or, more speially, a CAF). We an prevent the repliation of work by making sure that whenever we have suh a funtion we rst evaluate it (to normal form) before returning it. Thus if suh a funtion still returns an App it will be in normal form and although we may still make an unneessary opy of it we annot waste time by dupliating evaluation as there is no evaluation to do. 84 The R sheme for variables and CAFs is thus: R id fs v = C id fs v invokestati Node /eval(LObjet ;)LObjet ; If id is a funtion of arity greater than zero, we have: R id fs v = getstati (id ) LObjet ; Before ompiling appliations we rst unwind them, using the left-assoiativity of funtion appliation, so they are of the form f e1 : : : en where f is an identier (anything else would be a type error). If f refers to a variable then we just ompile the arguments and funtor of the appliation, paking the arguments into an array, and onstrut the App objet: R (f e1 : : : en ) fs v = new App dup ld n anewarray Objet dup ld 0 C e1 fs v aastore 9 > = Create an array of n objets > ; 9 > > > > > = Set the 0th element of the argument array > > > > > ; .. . 9 > dup > > > = ld (n 1) > Set the (n 1)th element of the argument array > C en fs v > > > > ; aastore C f fs v 85 invokespeial App /<init>([LObjet ;LObjet ;)V Here C is the generi sheme used to ompile expressions (see Setion 4.5.3). If f is a funtion then we an provide a bit more information to the onstrutor. If there are not enough arguments present then we an tell the App onstrutor to set the in nf eld. If there are exatly enough arguments present then we tell the onstrutor to set the total app eld. This is done by using a three-onstrutor of App whih as well as taking the arguments and funtor of the appliation takes an additional boolean whih if set to true sets the in nf and the total app to false and vie versa. For these two ases we have: R (f e1 : : : en ) fs v = new App dup nf .. . ompile arguments and funtor as before .. . invokespeial App /<init>(Z[LObjet ;LObjet ;)V where nf = ionst 1; = ionst 0; if n< arity of f if n = arity of f The JVM uses integers to represent booleans (the type of whih is denoted by a Z). The most eÆient way to load these onto the stak are using the instrutions ionst 1 for true and ionst 0 for false. If there are too many arguments present then we split the appliation into two: if f is of arity m and is applied to n arguments where 86 n > m then we reate the appliation of f applied to the rst m arguments applied to the other n m arguments. R (f e1 : : : em em+1 : : : en ) fs = v new App dup ld (n m) anewarray Objet dup ld 0 C em fs v aastore .. . dup ld (n m 1) C en fs v aastore C (f e1 : : : em ) fs v invokespeial App /<init>([LObjet ;LObjet ;)V Compiling if statements requires us to evaluate the anteedent and jump aordingly. R (if a then t else f endif) fs v = E a fs v hekast Boolean invokevirtual Boolean /booleanValue()Z 87 ifeq FALSE R t fs v goto ENDIF FALSE : R f fs v ENDIF : where FALSE and ENDIF are unique labels. The hekast instrution makes sure that we have a Boolean objet after evaluating the anteedent. The sheme E is used to ompile an expression whose result is known to be needed (see Setion 4.5.4). Note that the R sheme is used to ompile the two branhes of the onditional. Compiling a simple let requires us to ompile the denition, store it in the next free loal variable, updating the environment aordingly, and ompiling the body with respet to this new environment. R (let x = d in b endlet) fs v = C d fs v astore v R b [v = n℄ fs (v + 1) Compiling a letre is more omplex as eah denition in the blok will refer to at least one other one and hene if we are not areful we ould end up loading objets from registers that have not yet been lled. We thus need to rst of all put plaeholders in eah of the registers that are to be dened by the letre and update them when eah appropriate denition is ompiled. These plae-holders are Apps whose funtor and arg elds are null and they are updated using the same update method used to update Apps that have been evaluated (see Setion 4.3.1). R (letre ds in b endletre) fs 88 v = A ds v CL ds 0 fs v0 R b 0 fs v0 where (0 ; v0 ) = X ds v Here A alloates the plae-holders, CL ompiles the denitions and updates the registers and X updates the environment and the next free variable. A is dened as: A (x1 = e1 ; : : : ; xk = ek ) v = new App dup invokespeial App /<init>()V astore v .. . new App dup invokespeial App /<init>()V astore (v + k ) CL is dened as: CL (x1 = e1 ; : : : ; xk = ek ) fs aload (v v = k) C e1 fs v 89 dup invokevirtual App /update(LObjet ;)V .. . aload v C ek fs v dup invokevirtual App /update(LObjet ;)V Finally, X is dened as X [x1 = e1 ; : : : ; xk = ek ℄ v = ([x1 = v; : : : ; xk = v + k 1℄; v + k) 4.5.3 The C Compilation Sheme The C sheme, for the most part, is similar to the R sheme. The major dierene is in the handling of variables. Sine we do not have to worry about any loss of sharing, the denition of C when ompiling a single variable is: C id fs v = getstati (id ) LObjet ;; if id 2 fs otherwise = aload (id ); When ompiling onditionals with the C sheme, the branhes of the onditional are also ompiled with C sheme. Similarly, the body of loal variable delarations are ompiled with the C sheme when let letres are ompiled with the C sheme. 4.5.4 The E Compilation Sheme The E sheme is used when the we know that an expression is to be evaluated and is not a tail all (this is handled by R). Later on, we shall see how we an optimise the 90 ode produed by this sheme, but for now we shall just add a all to Node.eval: E e fs v = C e fs v invokestati Node /eval(LObjet ;)LObjet ; We an also use the E sheme to ompile the branhes of onditionals and the body of loal variable delarations. 4.5.5 Tail Reursion A tail reursion ours when the result of one funtion is the result of applying another funtion to the orret number of arguments. Any tail-reursive alls in our program will be ompiled using the R sheme. It is a property of graph redution that tail-reursive alls an be run in onstant spae no matter how deep the reursion is [109℄, and our implementation preserves this property. This is beause if we have an expression f x1 : : : xn that redues to g y1 : : : ym then the all to g is not exeuted inside the ode of f but the appliation is passed bak to the eval method whih then alls g. Thus, if we have a hain of tail-reursive alls g1 ; : : : ; gn with gi alling gi+1 then instead of reursing down the hain of gi s and bak again, and having a reursion that is O(n) levels deep, we instead `boune' between eval and eah gi and the reursion only O(1) levels deep. Hene our implementation exeutes tail-reursive funtions in onstant spae. 4.5.6 Initial Results Table 4.1 shows the running times of some programs ompiled using our Ginger ompiler. Beause the running times of Java programs an vary signiantly, we have averaged our times over three runs. All times inlude the time needed to inialise the JVM, whih is roughly 0.5{1.0 seonds. The programs were run on a 91 Program take 500 primes nfib 30 soda al edigits 250 queens 8 Time (s) 65:4 357:4 11:5 34:1 82:8 107:2 Table 4.1: Initial running times of programs produed by the Ginger ompiler Sun Enterpise 3000 with two 168MHz proessors and 512MB of memory running Solaris 2.6 and using the Sun JDK 1.1.5. Desriptions of the programs are as follows: take 500 primes outputs the rst 500 prime numbers using the sieve of Er- atosthenes method. nfib 30 alulates the number of redutions used to alulate the 30th Fi- bonai number using the nave doubly-reursive method. soda performs a serial word-searh on a 10 15 grid. al outputs alendars for the years 1990{99. edigits 250 evaluates the rst 250 digits of e (2:7182 : : : ). queens 8 prints all 92 solutions to the eight queens problem. 4.6 Optimisations In this setion we shall detail two optimisations that an inrease the performane of the ode produed by our ompiler: the diret invoation of some funtion appliations and the use of a single instane to represent eah onstant delared by a program. 92 4.6.1 Diret Funtion Invoations Implementations of lazy funtional languages use appliation nodes to store `suspended' funtion alls, that is funtion alls whose result may or may not be needed. However, in some ases it an be predited that the result of the funtion all will be needed, and we an avoid having to build the appliation representing the funtion all and instead invoke the funtion diretly. The rst plae we an use this optimisation is in the E sheme, as we know that we always need the result of an expression ompiled using this sheme. If we have a funtion, f , of arity n applied to the orret number of arguments, we have: E (f e1 : : : en ) fs v = C e1 fs v .. . C en fs v invokestati f ( LObjet ; : : : LObjet ; )LObjet ; | n {z times } invokestati Node /eval(LObjet ;)LObjet ; If f is known to be strit then we an ompile eah of the arguments using the E sheme rather than the C one. We an also use this tehnique with the R sheme, with one major aveat. It is safe to evaluate all tail alls, whih will be ompiled using the R sheme, sine their result will eventually be needed. However if the method we invoke is itself tail-reursive then we ould have a long hain of reursions and evaluation will no longer our in onstant spae and we ould be in danger of overowing the stak on whih the JVM stores the return address for eah method all. Although it is possible to optimise tail alls by replaing a method all with a jump, many JVM implementations do not do so. We annot provide this optimisation manually either, 93 Program Non-optimised Diret invoation % derease 65:4 38:2 42 357:4 140:2 61 11:5 8:4 27 34:1 25:6 25 82:8 49:1 41 107:2 73:0 32 take 500 primes nfib 30 soda al edigits 250 queens 8 Table 4.2: Running times (s) of programs produed by the Ginger ompiler with and without diret funtion invoation. as the JVM does not allow jumps between methods. We thus only diretly invoke tail alls involving funtions that are not tailreursive, whih for simpliity's sake we assume that is just our set of primitives, none of whih are tail-reursive. If we have a primitive p applied to the orret number of arguments, we have: R (p e1 : : : en ) fs v = C e1 fs v .. . C en fs v invokestati p( LObjet ; : : : LObjet ; )LObjet ; | If p {z } times is known to be strit then we an ompile eah of the arguments using the n sheme rather than the C one. In all other ases, E R remains as before. As an be seen from Table 4.2, diret invoation is a very worthwhile optimisation. 4.6.2 Single-instane Constants Sine onstants are immutable in a pure funtional language, we an represent eah onstant used by a pure funtional program by a single instane, saving both the time and spae needed to reate a new instane of a onstant eah time one is 94 enountered. We shall store eah onstant as a stati eld of the lass that our funtional program is ompiled to (f. how we store funtions) and thus eah time we need an instane of a onstant we just need to aess the relevant eld. This is similar to a tehnique used by most Java implementations to implement strings (whih are immutable in Java). It is required that the P sheme is modied to handle onstants. Suppose that 1 ; : : : ; n form the set of onstants that are program uses. Then P beomes: Phl ; fs ; f1 ; : : : ; m g; fs1 ; : : : ; sngi = .lass publi l .super Objet .field publi stati (s1 ) LObjet ; .. . .field publi stati (sn ) LObjet ; .field publi stati (1 ) LObjet ; .. . .field publi stati (m ) LObjet ; .method <linit>()V new l dup invokespeial l /<init>()V invokevirtual l /getClass()LClass ; astore 0 D s1 95 .. . D sn D 1 .. . D m return .end method where the other names are as in the original denition of P . The funtion has been extended to return the eld name of a onstant as well as that of a superombinator. The sheme D is also extended to take onstants as arguments. For example, if we have an integer, i, we have: Di = new Long dup ld2 w i invokespeial Long /<init>(J)V putstati (i) LObjet ; A similar method is used to dene the other types of onstants. As we an see from Table 4.3, this results in a modest, but signiant speed-up in the running times of our programs. 4.7 Results and Other Work The only other work we are aware of in this area is that of Wakeling, one based on the G-Mahine [119℄, whih translates the G-ode produed by HBC (the Haskell ompiler developed at Chalmers) into Java byteode; and one based on the 96 h; Gi Program take 500 primes nfib 30 soda al edigits 250 queens 8 Non-optimised Single-instane onstants % derease 65:4 47:1 28 357:4 326:0 9 11:5 11:4 1 34:1 33:4 2 82:8 67:2 19 107:2 103:1 4 Table 4.3: Running times (s) of programs produed by the Ginger ompiler with and without single-instane onstants. mahine [118℄ whih ompiles a ore language into a set of h; Gi instrutions whih are then transformed in Java byte-ode, again using HBC. Both versions use a separate lass, and hene a separate le, for eah funtion, rather than eah program as with our ompiler. There is also a ompiler for Standard ML from Persimmon (now being supported at Edinburgh Univesity at http://www.ds.ed.a.uk/home/mlj/) whih ompiles stand-alone SML programs to Java byteode [47℄, but as this is for a strit language we do not inlude it in our omparisons. A dierent approah is taken by Pizza [84℄. Instead of using the JVM as the target of funtional ode in instead introdues funtional features | namely parametri polymorphism, higherorder funtions and algebrai data types | into Pizza whih is a superset of Java. This language is then transformed into pure Java whih is then ompiled to produe byte-ode. A related issue is takled by Claus Reinke [94℄ who investigated the possibility of using Java omponents, in partiular graphial ones, in Haskell programs, with some suess. Reinke's work utilised the Java Native Interfae [30℄, whih we use to integrate C/C++ funtions into our Aladin implementation (see Setion 5.4.2). Table 4.4 gives the running times for several programs using our ompiler with both optimisations swithed on, and using both Sun's JVM and the Kae Open VM [105℄ to run the generated lass les; Wakeling's ompiler (the h; Gi-Mahine 97 Program Ginger Sun JDK Kae take 500 primes 30:5 266:2 nfib 30 118:0 638:4 soda 8:7 43:9 al 25:0 140:3 edigits 250 46:2 174:4 queens 8 72:6 369:6 Wakeling's Hugs GHC 50:5 56:6 4:2 19:4 10:0 46:9 6:3 114:6 0:9 5:0 4:0 16:2 0:8 6:7 0:1 0:3 0:5 0:9 Table 4.4: Comparisons of running times of various lazy funtional language implementations version [118℄) using Sun's JDK; the Haskell interpreter Hugs (version 1.4); and the Glasgow Haskell Compiler, GHC (version 2.10). The Haskell and Ginger soures were made as lose as possible, but all the Haskell programs ompiled using Wakeling's ompiler have been expliitly mono-typed where appropriate. This is beause if the overloading used in the programs is not resolved at ompile-time then the running times an slow down by as muh as a fator of 10 in extreme ases, beause of the need to pass around ditionaries to resolve the overloading at run-time. Both the JVM-targeting ompilers perform poorly when ompared to Hugs, with our ompiler (using the JDK to exeute the lass les) being some 4{11 time slower than Hugs, exept in the ase of nfib (whih is a somewhat artiial benhmark anyway) when performane mathed that of Hugs. Why then is our ompiler so muh slower than Hugs, when both are either interpreters or produe ode that is interpreted? First of all there are the deienies in our soure language, Ginger, when ompared to the muh more omplex Haskell. In partiular, the lak of stati type-heking and user-dened (algebrai) types and all but the rudest form of stritness analysis will have an appreiable eet on run-time performane, but Wakeling's ompiler has these and it is not muh faster than ours. There is also the ost inurred by not being able to resolve overloading until runtime; if we ould, at least partially, then we ould replae funtion alls to methods implementing 98 basi primitives suh as arithmeti and omparison operators to uses of basi JVM instrutions (see Setion 7.2). There is also the ost of using a high-level language (Java) as opposed to the lower-level C used to implement Hugs. For instane, Java does bounds heking on array aesses and heks that asts are legal whereas C does not, and both these operations our frequently in our ompiler. Wakeling [119℄ asribed the poor performane of his ompiler when ompared to Hugs to the poor memory handling in the JVM, hypothesising that memoryalloation in Java is an order of magnitude more expensive than in Hugs. Funtional programs ertainly will reate and destroy objets on a more frequent basis than an imperative objet-oriented one | both primes and edigits reate something in the order of 500,000 App nodes and 130,000 Cons nodes, for example. Unfortunately, using the -profile of the java interpreter to look at the ost of these alloations is not useful as we an only look at the time taken by the ode in the onstrutor (around 2.5 seonds for all 500,000 App objets used by primes) and not the time taken to alloate the memory, whih is done before the onstrutor is invoked. It is the alloation of objets whih is probably the reason why running our programs using the Kae VM is some 3{9 times slower than running them using the Sun JVM, despite the fat that in some ases Kae an be around 2{3 times faster than the Sun JVM. Consider the following segment of ode, whih we ompile using both the Kae and the Sun Java ompiler: Long l; for (int i = 0; i < 1000000; i++) l = new Long(i); The Kae VM takes 44 seonds to run the ode produed by both ompilers, while the Sun JVM takes only 3 seonds (both Virtual Mahines take only half a seond to run the same loop with an empty body). 99 Program take 500 primes nfib 30 soda al edigits 250 queens 8 1MB 30:5 118:0 8:7 25:0 46:2 72:6 4MB 28:0 119:2 7:8 20:7 23:2 64:2 % derease 8 -1 10 17 50 12 Table 4.5: Running times (s) of programs produed by the Ginger ompiler with initial heap sizes of 1 and 4 megabytes The large amount of mutual reursion on our programs means that we an't use the Java proler to determine how long the JVM spends exeuting eah method all. We an however examine the overhead imposed by the garbage olletor, using the -profile or the -verboseg options of the Java interpreter. This shows that using the default initial heap size of 1 megabyte garbage olletion (as in Table 4.4) takes between from 10% of the total running time to 50% in the extreme ases like edigits. If we inrease the initial heap size to 4 megabytes then we get the running times in Figure 4.5. The speed-up in running time is due to garbage olletion being run less frequently, but freeing more memory when it does. 4.8 Summary We have sueeded in produing a ompiler for a funtional language whih reates Java lass les as its objet ode, with a performane omparable to that of an approah whih used a fully-edged ompiler, with various optimisations not present in our ompiler, as its front end. However the performane of our ompiler is poor when ompared to a onventional lazy funtional language interpreter (Hugs). This leads us to suspet that we may have reahed a point where we annot ahieve any signiant speed-ups no matter how we optimise our run-time arhiteture of the graph redution on the JVM, and that any major leaps in performane an only 100 ome from optimising the JVM itself. 101 Chapter 5 The Aladin Abstrat Mahine The Aladin Abstrat Mahine (AAM) [10℄ provides an abstrat denition of a funtional language. There are no primitives built into Aladin, instead primitives are intended to be programmed in any language, funtional or imperative, and imported into the AAM. These primitives ould be simple funtions like addition; more omplex higher-order ones like map or fold ; or even omplete programs suh as grep or w . As well as the lak of primitives, Aladin has two more major features not found in the majority of funtional languages: the ability to speify the stritness of a funtion's arguments and results; and the use of streams for ordered I/O and realtime operations (whih we shall not onsider further in this thesis). Unlike the majority of other abstrat mahines for funtional languages (see Setion 2.5), the AAM is onerned only with the evaluation of programs and not their onstrution. Sine Aladin is designed to import its primitives from any language, it would be inappropriate to tie the abstrat mahine down to one partiular method of onstruting programs. It is this simpliity and the requirement for the user to speify the stritness of funtions that gives Aladin its advantages. In the next hapter, we shall see how 102 these advantages an be used to enable Aladin programs to be partially evaluated with only a small number of modiations to the mahine desribed in this hapter. The purity and simpliity of Aladin ould also be used to investigate other areas, suh as parallel evaluation of funtional programs and the eets of stritness on the time/spae requirements of funtional programs. In this hapter we shall develop an eÆient operational semantis for the AAM, starting from the original denotational semantis and going via a denotational semantis whih inludes expliit sharing and updating. We then use this semantis to develop an implementation of Aladin, using the Java language as with our Ginger implementation. Our ompiler lets the user write Aladin programs in an Aladin sripting language that we develop, and to use primitives written in C [57℄, C++ [102℄, Java [7℄ and Ginger (see the previous hapter). 5.1 The Semantis of the AAM An Aladin program is either a data objet, a funtion or an appliation of one program to another. Denoting our set of data objets as D and our set of funtions as F , our set of programs, P , is: P ::= D j F jP P Multiple-arguments to funtions are urried and appliations are left-assoiative. Hene for p1 ; p2 ; p3 2 P we have: p1 p2 p3 (p1 p2) p3 Funtions may be written in any language of the user's hoie. A funtion f of arity m is denoted as f m . The meta-funtion is used to primitively apply f to its arguments by exeuting the ode assoiated with f . The expression f m (p1 ; : : : ; pm ) denotes the result of the primitive appliation. 103 The user is required to speify the stritness of eah of its argument (see Setion 2.2). Aladin uses this information to ontrol evaluation: strit arguments are evaluated before the funtion is applied; lazy ones are not. Expanding the denition given in Setion 2.2, we extend the onept of stritness to funtions of any arity (that is, without having to urry arguments). An Aladin funtion f of arity m is strit in its ith argument, where 1 i m, if: f x1 : : : xi 1 ? xi+1 : : : xm for all possible values of xj ; 1 j =? m; j 6= i and non-strit or lazy otherwise. In a slight abuse of notation, we also apply the terms strit and lazy to the result of a funtion: a strit result does not need further evaluation; a lazy one does. For a funtion of arity m we use the notation: f :: 1 : : : m ! ; i ; 2 fs; lg to denote a funtion whih is strit in its ith argument if i = s and lazy if i = l, and strit in its result if = s and lazy if = l. 5.1.1 The Original Denotational Semantis The original evaluation rules used a meta-funtion, Eval, to return the result of evaluating a program. If we have a data objet applied to any number of programs, then Eval simply returns the original program: Eval[ (d 2 D) p1 : : : pn ℄ = d p1 (5.1) : : : pn If we have a funtion applied to too few arguments, then we return the original expression, but we evaluate any strit arguments: Eval[ (f :: 1 : : : m ! ) p1 : : : pn<m where qi : : : qn (5.2) = Eval[ pi ℄ ; if i = s = otherwise pi ; ℄ = f q1 104 If we have a funtion applied to too many arguments, then we evaluate the inner appliation, that is the funtion applied to the exat number of arguments it needs, then apply the result of this to the remaining arguments. Eval[ f m p1 ℄ = Eval[ r pm+1 where r = Eval[ f m p1 : : : pm ℄ : : : pn>m : : : pn ℄ (5.3) Finally we have the ase when we have a funtion applied to exatly the right number of arguments. We have to do three things: evaluate any strit arguments; apply the funtion using ; and evaluate the result if neessary. Eval[ (f :: 1 : : : m ! ) p1 r = if e; = Eval[ e ℄ ; e qi = : : : pm ℄ = r where =s otherwise f (q1 ; : : : ; qm ) = Eval[ pi ℄ ; if i = s = otherwise pi ; (5.4) 5.1.2 The Denotational Semantis with Expliit Updates Implementations of funtional languages use sharing and updating to avoid doing repeated work (see Setion 2.3). Our rst step towards an eÆient operational semantis for the AAM is thus a denotational semantis of the AAM whih makes expliit sharing and updating. We assoiate eah program with a variable v 2 V , hanging our syntax of Aladin programs to aommodate this: P ::= D j F jV jV (5.5) V Appliations now involve the appliation of one variable to another and a program an also be a variable, that is, an indiretion to the atual value. The expliit 105 naming of all parts of a program, in partiular funtions, means we an implement reursion diretly. These assoiations are dened in a mapping of variables to programs whih we shall all the heap. A heap provides the ontext for the evaluation of a program, and thus a program evaluated with respet to one heap an yield a dierent result to the same program evaluated with respet to a dierent heap. The expression [x0 7! p0 ; x1 7! p1 ; : : : ; xn 7! pn ℄ denotes that in the heap variable xi maps to program pi where i 2 0; : : : ; n. We may oasionally use the heap as a lookup-funtion, that is: x if [x 7! p℄ = p; = ?; otherwise Sine a program ould be a variable we ould have a hain of indiretions: [x0 7! x1 ; x1 7! x2 ; : : : ; xn 7! p 2= V ℄ In suh ases we allow ourselves to `short-iruit' the hain and write [x0 7! p℄. Note that the hain ould in fat be a yle: [x0 7! x1 ; x1 7! x2 ; : : : ; xn 7! x0 ℄ Whih is akin to writing something like: foo = let x = y; y = z; z = x; in x endlet; in Ginger. The result of evaluating any program whih tries to aess a variable in suh a yle will be ?. 106 We will allow ourselves to abuse notation and let [ 0 denote the heap updated with the mappings in heap 0 with any lashes being resolved in the favour of those dened in 0 : ( [ 0) x = p; if 0 [x 7! p℄ = q; if [x 7! q℄ ?; otherwise In partiular, [ fx 7! pg denotes a heap where x is assoiated with p and all = other variables are assoiated to the programs that they were in . The design of the semantis allows the heap to be represented as a global entity, whih an be destrutively updated, in an atual implementation, and this is the approah we use (see Setion 5.3). Sine all programs are evaluated with respet to a heap, it follows that our primitive appliation meta-funtion must also exeute with respet to a heap. The informal type of hanges from: P ::: P ! P to: Heap V : : : V ! (P Heap ) (5.6) Note that while takes a heap and variables as arguments it returns a program and a heap (we need to return a heap as the funtion may want to reate new objets in the heap). Our evaluation philosophy hanges from that used in the original semantis. Whereas before we returned a new expression whih represented the evaluation, we now evaluate a program by updating the heap whih provides the ontext for the program. The meta-funtion E provides the top-level interfae to this proedure: E p [x 7! p℄ = (U x) x (5.7) 107 root spine base p x0 xn @ x1 @ yn y1 Figure 5.1: An generi unwound appliation First we need to nd a variable assoiated to the program we want to evaluate. Sine a heap is not injetive, there ould be any number of variables assoiated with the same program but it doesn't matter whih one we pik. Then we update the heap with respet to this variable, and nally look up the value of the variable in the updated heap. The updating of the heap is done by the meta-funtion U whih updates a given heap by evaluating the program referred to by a given variable, whih we shall refer to as the root of the program. This evaluation is done by graph redution as desribed in Setion 2.3. If the root refers to an appliation then we need to unwind the spine of the appliation graph until we reah a non-appliation. In the heap [x0 7! p; x1 7! x0 y1 ; : : : ; xn 7! xn 1 yn ℄ if xn forms the root of the program then the xi ; i 2 0; : : : ; n form the spine and x0 forms the base (see Figure 5.1). The four rules for U (5.8{5.11) diretly reet those of Eval (5.1{5.4). Suppose we have a data objet applied to a number of arguments. Then sine no evaluation needs to be done, no updating of the heap has to be done either: U [x0 7! d 2 D; x1 7! x0 y1 ; : : : ; xn 7! xn 1 yn℄ xn = (5.8) If we have a funtion applied to too few arguments then we just need to evaluate the 108 xn @ xm + 1 @ xm @ x1 @ fm x0 yn ym+ 1 ym y1 Figure 5.2: Appliation of a funtion to too many arguments xn @ xm + 1 @ r xm yn ym + 1 Figure 5.3: Appliation of a funtion to too many arguments after evaluation of inner appliation strit ones. The A meta-funtion (see Rule 5.13) returns the heap resulting from evaluating the strit arguments of a funtion. U [x0 7! f m ; x1 7! x0 y1 ; : : : ; xn 7! xn 1 yn ℄ xn<m = A xn (5.9) If we have a funtion f m applied to too many arguments, x1 ; : : : ; xm say, where n>m (see Figure 5.2), then we rst obtain the heap resulting from evaluating the inner appliation (the shaded part of the gure) the root of whih is xm . In this new heap, xm will refer to the evaluated version of f m x1 : : : xm , r say (see Figure 5.3). This result beomes the new base of our program (note that we may need to do some more unwinding if r is an appliation) and we ontinue updating. U [x0 7! f m ; x1 7! x0 y1 ; : : : ; xn 7! xn 1 yn℄ xn>m = U 0 xn where 0 = U xm 109 (5.10) If we have a funtion f applied to the exat number of arguments, we rst evaluate any strit ones using A and primitively apply the funtion using . The root of the appliation then needs to be updated with the result of and nally we may need to ontinue evaluation if f returns a lazy result. 2 x0 7! f :: 1 : : : m ! U 64 x1 7! x0 y1; : : : ; xm 7! xm 1 ym 3 75 xm = 4 where 4 = if 3; = U 3 xm ; =s (5.11) otherwise 3 = update 2 xm r (r; 2 ) = f ( 1 = A 1; y1 ; : : : ; ym ) xm For eah of the appliations that form the arguments of f , the argument omponent is extrated and passed as the orresponding argument to . The funtion update takes are of the updating of a variable with a new value. First we dene a ouple of auxiliary funtions: reduible xm = ! m ^ xi i=1 = xi 1 yi ^ x0 = fm and value p = = if p; p; 2 p =V otherwise We now dene update as: update [x 7! x1 ; x1 7! x2 ; : : : ; xn 7! p 2 = V℄xr = where val = r; = value r; if reduible (value r) otherwise 110 [ fxn 7! val g (5.12) Note that it is the last variable in the indiretion hain, xn , that is updated, not the rst one, x. This allows the result of the update to be propagated to any other variables whih are referring to elements in this hain. Also, if r, the value we are updating with, is also a variable and the ultimate value of r is not reduible, then it is this value we use to update xn , not r itself. The update value is also r if r is not a variable. This allows us to irumvent any indiretion hains, possibly freeing the variables in this hain for garbage olletion in an implementation. It is safe to do this if the the value of r is not reduible sine r annot be further evaluated and thus we annot lose any shared work sine there is no work to be done. Finally we need to dene the A meta-funtion whih evaluates eah argument. This proeeds by updating eah argument in turn and passing the heap obtained by eah evaluation into the reursive all to U used to evaluate the next argument. Although the denition here implies that arguments are evaluated left to right, this ordering is arbitrary and only adopted for syntati onveniene | any ordering ould be adopted in pratie. Arguments ould even be evaluated onurrently provided appropriate are was taken. 3 2 x0 7! f :: 1 : : : m ! 7 A 64 5 xnm = n x1 7! x0 y1; : : : ; xn 7! xn 1 yn (5.13) where i = U i 1 yi ; = i 1; if i = s otherwise 5.1.3 The Operational Semantis We represent the operational semantis as transition rules of the state of the AAM. This state is a quadruple: (Control ; Stak ; Heap ; Dump ) where 111 Control is a stak of instrutions. These instrutions are EVAL, EVALARGS, EVALITH and APPLY. Stak is a stak of variables, used as a working spae to store the spine of an appliation when we are unwinding, with the head of the stak forming the base and the last element in the stak the root. Heap is a mapping from variables to programs as in the denotational ase. Dump is a stak of Control -Stak pairs, used to hold previous states while we are evaluating a dierent part of the program. Our mahine is similar to the SECD mahine and the G-Mahine (see Setion 2.5). The Stak, Control and Dump used by Aladin serve similar funtions as their ounterparts in the SECD and G mahines. The Aladin Heap is more like the G-Mahine heap, whih like Aladin's heap is a global objet where the graph being evaluated is held, than the SECD mahine's Environment whih serves a loal mapping between values and variables dependent on the expression being evaluated. The key dierene between Aladin and the SECD mahine and the G-Mahine is that arguments and the results of Aladin funtions may be strit or lazy. In the SECD mahine all arguments are treated as strit and in the G-mahine all arguments are lazy, and neither of these mahines has the onept of a strit or lazy result and must evaluate the results of all funtions. The meta-funtion F evaluates a program using the state-transition rules (see below) with respet to a given heap (f. E): F p [x 7! p℄ = 0 x where (hi; S; 0 ; hi) = T (hEVALi; hxi; ; hi) (5.14) The meta-funtion T repeatedly applies the state-transition rules (Rules 5.15{5.23 below) to a state until no more apply, returning the nal state. So, if we have a 112 transition sequene: s1 =) s2 =) : : : =) sn and no rules apply to sn then T si = sn ; i 2 1; : : : ; n. We now have to give the transition rules for a state, starting from the initial state used as the argument to T. If none of these rules applies then the mahine terminates. The rst ase is when the head of the stak referenes a data objet, that is, when we have a data objet applied to a number of arguments, f. Rules 5.1 and 5.8. We need to make no hanges and no further work an be done in this state. If the dump is non-empty we restore it (by virtue of Rule 5.23), else we terminate as no more rules apply. hi hEVALi hi hx0 ; x1; : : : ; xni =) [x0 7! d 2 D℄ (5.15) N.B. EVAL an only our in the ontrol stak as the sole element. If the head of the stak is an appliation, we need to unwind and arry on evaluating: hEVALi hEVALi x1 :S [x1 7! x0 y1 ℄ =) x0 : x1 :S (5.16) If the head of the stak refers to a funtion of arity m and there are less than m other elements on the stak, we have the ase of a funtion applied to too few arguments, f. Rules 5.2 and 5.9. In this ase, we need to trigger the evaluation of the strit 113 arguments whih is done using the EVALARGS n instrution (Rule 5.20), where n is the number of other elements on the stak. hEVALi hx0 ; x1; : : : ; xn<m i [x0 7! f m ; xi 7! xi 1 yi ℄ =) hEVALARGS ni hx0 ; y1; : : : ; yni (5.17) Note that the arguments of the funtion are extrated from the appliations in whih they reside. If the head of the stak refers to a funtion of arity m and there are stritly more than m other elements on the stak, we have the ase of a funtion applied to too many arguments, f. Rules 5.3 and 5.10. We need to evaluate the inner appliation, whih is done in a new state. After this, we evaluate the result of evaluating the inner appliation applied to the rest of the arguments. This is done by saving the ontrol-stak pair that represents this appliation on the dump. They will be restored from the dump when evaluation of the outer appliation has ompleted. hEVALi hEVALi hx0 ; x1; : : : ; xm i hx0 ; x1; : : : ; xn>m i =) [x0 7! f m ℄ (hEVALi; hxm ; : : : ; xn i) : (5.18) The nal ase for EVAL is when we have a funtion applied to exatly the right number of arguments (this, with Rule 5.22, reets Rules 5.4 and 5.11 in the denotational ases). We need to evaluate any strit arguments and apply the funtion. hEVALi hx0 ; x1; : : : ; xm i [x0 7! f m ; xi 7! xi 1 yi ℄ =) hEVALARGS m; APPLYi hx0 ; y1; : : : ; ym; xm i 114 (5.19) Note that as well as unpaking the arguments to the funtion, we keep xm , the root of the program, as the last element of the stak. It is this variable that will be updated with the result of applying f to its arguments. In partiular, if m = 0, that is f is a Constant Appliative Form (CAF), then it is the variable referring to the funtion itself that will be updated with the result. We now need to give the state transition rules for the other instrutions. First we have EVALARGS whih triggers the evaluation of any strit arguments, f. the A meta-funtion (Rule 5.13). EVALARGS n : C x0 : y1 : : : : : ynm : S [x0 7! f :: 1 : : : m ! ℄ =) e1 ++ x0 : y1 : : : : : yn : S ::: ++ en ++ C (5.20) where ei hEVALITH ii; if i = s = hi; otherwise = The EVALITH i triggers the evaluation of the ith element of the stak (where the head of the stak is the 0th element) in a new state: EVALITH i : C x0 : x1 : : : : : xi : S =) hEVALi hxii (5.21) (C; x0 : x1 : : : : : xi : S ) : By evaluating eah argument in its own state we open up the possibility of evaluating all the arguments that need evaluating onurrently, a proess whih would be more diÆult if we used the same state as the original appliation. 115 The APPLY instrution initiates the primitive appliation of a funtion to its argument, updating the root of the appliation and evaluating the result if neessary. C hAPPLYi hroot i hx0 ; y1; : : : ; ym ; root i =) update [x0 7! f :: 1 : : : m ! ℄ 0 root r where (r; 0 ) = C (5.22) f ( ; y1 ; : : : ; ym ) hi; = hEVALi; = if =s otherwise where the funtion update is the one dened in equation 5.12. The nal rule onerns the ase when the ontrol stak is empty but we have previous states on the dump. In this ase, we restore the rst previous state and ontinue. This is similar to returning from a proedure all in an imperative language. hi S0 (C; S ) : C =) S (5.23) Note that by throwing away the old stak we do not in any way return a value. 5.1.4 An Example of the Operational Semantis Consider again the expression square (3 + 4) rst enountered in Setion 2.3. Representing addition and multipliation as the prex funtions plus and times respe- 116 tively, both of stritness s s ! s, we an dene square as: square :: l ! l square ( ; x) = (r; 0 ) (5.24) where 0 = t where v = [ fv 7! t x; r 7! v xg times and r are variables undened in . Note that we have made square lazy in its argument for illustration purposes | it ould have safely been made strit | and that the result of square is the appliation t x x and not the result of this appliation. Assume that we have an initial heap : 8 > < a! 7 times ; b 7! square ; 7! plus ; d 7! 3; => : e 7! 4; f 7! d; g 7! f e; h 7! b g then we need to update this passed to T) is: (hEVALi; hhi; 9 > = > ; by evaluating h. Our initial mahine state (the one ; hi) giving us the transition sequene: Control Stak Heap Dump EVAL hhi hi hb; hi hi hb; g; hi hi =) (by Rule 5.16) EVAL =) (by Rule 5.19) EVALARGS 1 APPLY =) (by Rule 5.20 and stritness of APPLY b = square ) hb; g; hi hi =) (by Rule 5.22 and denition of square ) EVAL hhi 1 117 hi where 1 = [ fv 7! a g; r 7! v g; h 7! rg =) (by Rule 5.16) EVAL hr; hi 1 hi hv; r; hi 1 hi ha; v; r; hi 1 hi ha; g; g; hi 1 hi =) (by Rule 5.16) EVAL =) (by Rule 5.16) EVAL =) (by Rule 5.19) EVALARGS 2 APPLY =) (by Rule 5.20 and stritness of EVALITH 1 ha; g; g; hi a = times ) 1 hi EVALITH 2 APPLY =) (by Rule 5.21) hg i hÆi 1 where Æ = (hEVALITH 2; APPLYi; ha; g; g; hi) EVAL =) (by Rule 5.16) EVAL hf; gi 1 hÆ i h; f; gi 1 hÆ i h; d; e; gi 1 hÆi =) (by Rule 5.16) EVAL =) (by Rule 5.19) EVALARGS 2 APPLY 118 =) (by Rule 5.20 and stritness of EVALITH 1 h; d; e; gi = plus ) 1 hÆi EVALITH 2 APPLY =) (by Rule 5.21) hdi hÆ1 ; Æi 1 where Æ1 = (hEVALITH 2; APPLYi; h; d; e; gi) EVAL =) (by Rule 5.15) hi hi 1 hÆ1 ; Æi h; d; e; gi 1 hÆi =) (by Rule 5.23) EVALITH 2 APPLY =) (by Rule 5.21) hei 1 where Æ2 = (hAPPLYi; h; d; e; gi) EVAL hÆ2 ; Æi =) (by Rule 5.15) hi hi 1 hÆ1 ; Æi h; d; e; gi 1 hÆi =) (by Rule 5.23) APPLY =) (by Rule 5.22 and denition of plus ) hi hg i 2 hÆ i 2 hi where 2 = 1 [ fg 7! 7g =) (by Rule 5.23) EVALITH 2 ha; g; g; hi 119 APPLY =) (by Rule 5.21) EVAL hg i 2 hÆ3 i where Æ3 = (APPLY; ha; g; g; hi) =) (by Rule 5.15) hi hgi 2 hÆ3 i ha; g; g; hi 2 hi =) (by Rule 5.23) APPLY =) (by Rule 5.22 and denition of times ) hi hhi where 3 = 2 [ fh 7! 49g 3 hi In the heap omponent of the nal state, the variable whih referred to the root of the original appliation, h, now refers to 49, whih is the result of square (plus 3 4). 5.2 A Sripting Language for Aladin To enable us to write programs, we need a top-level language whih we an program in. This language should let us import primitives from a variety of soures and ombine them into programs. The syntax of this sripting language is losely tied to our implementation of the AAM and hoies in one will be reeted in eah other. We neessarily lose some of the purity of the abstrat mahine, but we shall keep this to a minimum. We hoose to implement a version of the AAM, using the above semanti rules, using Java. The approah is similar to the one we used for our Ginger ompiler in the previous hapter. Eah Aladin sript will be onverted into a single Java lass. Choosing Java as our implementation language for Aladin has the same advantages 120 as hoosing it for Ginger with the additional benet that Java an interfae with other languages, in partiular C and C++, using the JNI [30℄. For the syntax of the sript, we adopt a subset of Ginger, omitting loal funtion and lambda expressions, with added onstruts for speifying the stritness of funtions and an enhaned syntax for importing primitives. The extended BNF of this language is as follows: hsript i ::= hpakagede i? hdel i hpakagede i ::= pakage hpakage i; hdel i ::= hdef i j himport i j hid i hstritsig i himport i ::= him i (hid i hstritsig i?) ; j import? hid i ; him i ::= hstritsig i ::= import hlass i j import "hlename i" j importg hlass i :: hargstrit i? -> hstrit i hargstrit i ::= hstrit i (* hstrit i) hstrit i ::= sjl hdef i ::= hid i hid i = hprog i; hprog i ::= hlet i j hsimpleprog i + hlet i ::= let hid i = hprog i in hprog i endlet hsimpleprog i ::= hjavaid i j hdata i j (hprog i) hdata i ::= hint i j hoat i j hbool i j hhar i j hstring i 121 hjavaid i ::= (hid i .) hid i hpakage i ::= hjavaid i hlass i ::= (hpakage i .)? hid i where id is any valid Java identier ontaining no period (`.') separators, and lename is a legal le name, the syntax of whih is dependent on the operating system we are running under. An Aladin sript is thus an optional pakage delaration followed by a number of delarations. These delarations are: Import delarations used to import primitives from a lass. Stritness delarations used to speify the stritness of a primitive. Denitions used to speify a primitive funtion by giving a denition whih onstruts a graph from its arguments and other primitives. Eah sript le will be appended with the suÆx `.as'. As with our Ginger ompiler, we ompile sripts written in this language into a Java lass le, translating eah denition into a stati Java method, and generating Java ode to deal with the importing of funtions and the setting of their stritnesses. 5.2.1 Pakage Delarations A pakage delaration, whih must be the rst statement of an Aladin sript, follows the same syntax as pakage delarations in Java and has the same meaning. All primitives dened in this le will be plaed inside a lass with will be in the pakage delared by the user. If no pakage delaration is made then the empty pakage is used. For instane the delaration: 122 pakage fp.aladin; plaes all the following denitions in the pakage fp.aladin. 5.2.2 Import Delarations The various import delarations allow the user to import primitives from various lasses or les. These delarations have an optional list of primitives we wish to import from that soure, along with optional stritness signatures (see Setion 5.2.3 for an example). The import delaration is used to import primitives from atual Java lasses and Java lasses that were obtained from ompiling Aladin sripts. The importg delaration is used to import Java lasses that resulted from ompiling Gin- ger programs and import is used to import primitives from library les generated by ompiling C and C++ programs. Finally, the import? delaration is used to delare primitives that the sript expets the sript importing this one to import at some stage. This delaration is only used in library les whih an use more than one set of basi primitives, for example, either boolean or fuzzy primitives, or partial or non-partial primitives. The mehanis of importing are disussed in Setion 5.4.6. 5.2.3 Stritness Signatures A stritness delaration delares the stritness of a primitive funtion's arguments and result. If the primitive in question is dened in the sript, then this signature an appear anywhere in the sript but must be given; if the primitive is imported then a stritness signature an be given in the import delaration in whih ase it overrides the original signature (if there was one). An argument or result is either strit, denoted by an s, or lazy, denoted by an l. The arguments of a primitive dened in a stritness signature are separated using the 123 symbol *, and the arguments are separated from the result by the symbol ->, whih is present even if the primitive in question takes no arguments. It is an error to give a stritness signature to a non-funtion, or to a funtion whih is neither imported nor has no denition. As an example, we an give the stritness of the primitives imported from the lass fp.aladin.lib.List, our standard representation of lists: import fp.aladin.lib.List _op_list_ons :: l * l -> s // : _op_list_empty :: -> s // [℄ _op_list_index :: s * s -> s // ! _op_list_length :: s -> s isEmpty :: s -> s hd :: s -> l tl :: s -> l // # Thus op list ons takes two lazy arguments and returns a strit result; the funtion op list empty takes no arguments and returns a strit result; op list index takes two strit arguments and returns a strit result; op list length and isEmpty take a strit result and return a strit result; and nally hd and tl take a strit argument and return a lazy result. The number of arguments referred to in a stritness signature refer to the number of expliit arguments given, not to any implied by -onversion. For instane, if we dene: hd1 = hd; then the stritness signature of hd1 is -> s (sine it takes no arguments and returns a funtion) and not that of hd (s -> l). 124 5.2.4 Denitions Aladin denitions follow the normal syntati style found in funtional languages, in partiular the style of Ginger. The ve basi data types | integers, reals, strings, haraters and booleans | follow the normal syntax. These types are the only ones that are built into our ompiler that are not in the Aladin semantis. Appliations are written using juxtaposition (and assoiate to the left) and we allow the use of inx operators, standard funtional list notation (see below) and simple loal variable denitions via the let delaration. The use of binary prex funtions as inx ones an be ahieved by surrounding the funtion in bak-quotes as in Haskell. We use the Ginger if-then-elsif-else-endif notation for onditionals (again see below for more details). For example, the Fibonai funtion an be dened in Aladin as: fib :: s -> l fib n = if n == 0 then 1 else let f1 = fib (n - 1); f2 = fib (n - 2); in f1 + f2; endlet endif; See Setion 5.2.3 for more details on stritness signatures. This denition merely takes its arguments and produes the graph desribed in its left-hand side, hene we have to return a lazy result as we want the graph 125 Pre. Inx form Prex form 0 ++ op list at 1 2 3 4 5 6 7 8 9 : | & == ~= < <= > >= + * / % ^ . ! ~ op op op op op op op op op op op op op op op op op op list ons or and eq ne lt le gt ge plus minus times divide modulus exp ompose list index not Method N/A N/A Logial.or Logial.and Objet.equals Objet.equals Orderable.lt Orderable.le Orderable.gt Orderable.ge Num.plus Num.minus Num.times Num.div Num.mod Num.exp N/A N/A Logial.not Table 5.1: Aladin inx operators and their prex form to be evaluated. No evaluation or ompiler optimisations are performed, these being handled by the evaluation mehanism dened in the previous setion. The onstrution and evaluation of programs are ompletely divored in the sripting language. We allow the use of inx operators, though these are onverted to prex funtions during the parsing stage aording to the preedene rules dened in Table 5.1 (we inlude the already prex not, ~, for ompletion). Note that in the ase of `.', foo . bar, with spaes around the period, is funtion omposition but foo.bar, with no spaes, is a single identier. Also, - is used for both binary minus and to indiate a negative onstant, but not for general unary minus: -3 is legal but -x is not (we provide the primitive neg to negate a general program). Some of these operators are overloaded, the resolution being done at runtime. 126 In these ases, the prex form only exists to unwrap the arguments and all the interfae method. Any user-dened lass whih implements the method speied in the fourth olumn adds another overloading to the prex form. For instane, if we have a lass Foo whih implements the Orderable interfae then we an use the omparison operators on objets of that type. Overloading is explained in more detail in Setion 5.4.4. As with inx operators, the Ginger style if syntax, whih is adopted to redue exessive braketing, is onverted to a prex funtion appliation. The expression: if a1 then 1 elsif a2 then 2 : : : elsif an then n else d endif is onverted into the program: op if a1 1 ( op if a2 2 (: : : ( op if an n d) : : :)) Here op if is the boolean onditional. This isn't built into Aladin, instead the user provides their own implementation. One suh implementation an be seen in Setion 5.4.1. We also allow the use of the standard square-braket notation to denote lists, with the list [x1 , : : : , xn ℄ being translated into multiple appliations of the list onstrutor funtion op list ons: op list ons x1 (: : : ( op list ons xn op list empty) : : :) We also allow the use of the `dot-dot' notation, again onverting to a prex funtion appliation dened in Table 5.2. The user an provide any implementation of lists that they want, as long as they provide implementations of the list onstrutor and deonstrutor funtions. We desribe an implementation similar to the one normally used in funtional languages suh as Ginger and Haskell in Setion 5.3.2. As with lists, tuples are onverted into appliations of a onstrutor funtion to the omponents of the tuple. The tuple (x1 , 127 : : : , xn ) is onverted to the appli- Dot-dot form [℄ [a [a [a [a .. ℄ .. b ℄ , b .. ℄ , b .. ℄ Prex form op list empty from a fromTo a b fromThen a b fromThenTo a b Table 5.2: Aladin `dot-dot' lists and their prex form ation mkTuple n x1 : : : xn . Again, the user an provide their own implementation and one suh implementation is presented in in Setion 5.3.2. 5.3 Representation and Evaluation of Aladin Programs We rst need a Java representation of the four omponents of the AAM dened in the semantis. The Control is simply the ode struture of the evaluation mehanism. The Stak is implemented as an array whih an grow on demand (we ould use a Java Stak or Vetor but we use an array as it enables us to perform ertain operations more eÆiently and avoids exessive asting). The Heap is a ombination of the objet heap of the JVM plus a hash table in whih we an look up funtions by their name. Finally, the Dump, whih in the semantis is used to simulate funtion alls and reursion, is implemented by atual funtional alls and reursion in our implementation. The denition of programs as given in Setion 5.5 suggests that we implement programs as a lass hierarhy as seen in Figure 5.4. Some of these lasses are used only by the ompiler: Aladin is the lass ontaining the parser; AladinCompiler ontains the top-level ompilation methods; the lasses CompileTimeFuntion and CompileTimeApp are ompile-time representations of funtions and appliations whih need to ontain more information than their run-time brethren; Ident is a ompile-time identier (funtion or variable name); and Let and LoalBlok are 128 fp.aladin java.lang Object App RunTimeFunction Function GingerFunction Data CompileTimeFunction Var Prog CFunction AAM Num VarStack StrictnessSig Int Ident Real Let Char CompileTimeApp Bool Empty LocalBlock Str Cons Unknown SumProd Orderable Aladin Logical Comparable List Pair Tuple Triple AladinCompiler Quartet java.lang RuntimeException UnknownValueException EvaluationException Sextet fp.gingerc AladinFunc Func KEY Quintet CLASS FINAL CLASS Heptet ABSTRACT CLASS extends INTERFACE implements Figure 5.4: The fp.aladin pakage 129 used to store loal variable delarations and will be disappear after ompilation. In addition, the lass Unknown and the exeption lass UnknownValueExeption are only used when partially evaluating programs (Chapter 6). 5.3.1 Aladin Programs All programs are represented as sublasses of the abstrat lass Prog. Variables are represented using the Var lass: publi final lass Var extends Prog { publi Prog value; // ... } where the eld value holds the value of the variable. Data objets are represented by instanes of sublasses of the abstrat lass Data. In partiular, the ve basi types | integers, reals, haraters, booleans and strings | are represented by the lasses Int, Real, Char, Bool and Str respetively. These lasses have a eld value ontaining the atual value of the data objet of type long, double, har, boolean or String respetively. Unlike in our Ginger ompiler we do not use the wrapper lasses provided in the pakage java.lang. This is slightly ineÆient (espeially in the ase of strings) but leads to a more elegant and objet-oriented design. Funtions are represented by instanes of sublasses of the Funtion lass: publi abstrat lass Funtion extends Prog { publi String pak = ""; publi String l = ""; publi String short_name = ""; publi int arity = -1; 130 publi StritnessSig stritness; publi final boolean stritIn(int i) { // ... } publi final boolean hasStritResult() { // ... } publi abstrat Prog primApply(Var[℄ args); // ... } Java naming onventions are used in naming Aladin funtions: eah funtion has a short name (short name), lass (l) and pakage (pak). In use, the short name refers to the last suh funtion with that short name that was loaded. This an be qualied with the lass and maybe the pakage name if neessary. The other elds and methods of the Funtion lass are fairly self-explanatory. The eld arity represents the arity arity of the funtion and stritness its stritness signature. The method stritIn returns whether the funtion is strit in a ertain argument (indexing starting at 1) and hasStritResult returns whether the funtion has a strit result. Finally, primApply primitively applies the funtion to the given arguments (f. ). Finally, appliations are represented as instanes of the App lass: publi lass App extends Prog { 131 publi Var funtor; publi Var arg; // ... } 5.3.2 Data Strutures In our implementation of Aladin, data strutures are based on the sum-of-produt type used in ML, Miranda and Haskell and desribed in [86℄. We introdue a lass, SumProd, whih will be the superlass of our list, tuple and other similar lasses: publi abstrat lass SumProd extends Data implements Comparable { proteted Var[℄ fields; publi abstrat String getConstrutorName(); // ... } The Comparable interfae, used by Aladin to overload the equals and not equals funtions, is desribed in Setion 5.4.4. The array fields holds the elds of the struture, for example, in a ons objet the array would have length 2 with the rst element holding the head and the seond the tail. The method getConstrutorName returns the full name of the funtion used to onstrut instanes of that lass. For instane, we an implement lists using the following lasses: publi abstrat lass List extends SumProd { publi stati final Empty EMPTY = new Empty(); 132 publi stati Prog _op_list_empty() { return EMPTY; } publi stati Prog _op_list_ons(Var h, Var t) { return new Cons(h, t); } publi stati Var hd(Var l) { List list = (List) l.get(); if (list instaneof Empty) throw new EvaluationExeption("Can't take hd of empty list"); else return list.fields[0℄; } // ... } final lass Empty extends List { publi Empty() { fields = empty_fields; } publi String getConstrutorName() { 133 return "fp.aladin.lib.List._op_list_empty"; } // ... } final lass Cons extends List { publi Cons(Var head, Var tail) { fields = new Var[℄ {head, tail}; } publi String getConstrutorName() { return "fp.aladin.lib.List._op_list_ons"; } // ... } Here empty fields is a nal stati member of the SumProd lass whih is an array of zero Var objets. As well as ating as the superlass of Empty and Cons, the List lass ontains the denition of primitives ating on lists. The method op list empty is the alphanumeri form of [℄ and op list empty is the alphanumeri form of : (see Setion 5.2.4). These methods all the relevant onstrutor of the Empty and Cons lasses respetively. The method hd returns the head of a list; after asting the value of its argument to a List it then determines whether the value is an instane of the Empty lass, in whih ase the head does not exist, or the only other alternative, the 134 Cons lass, in whih ase we just return the rst omponent of the fields array. Other list primitives an be dened similarly. For tuples, we have a superlass Tuple from whih we sublass spei tuples, for example the Pair lass represents pairs. The user is free to sublass the SumProd lass to reate his or her own data strutures, for example, trees and other ontainer types. Note that the reation and manipulation of the these types is done entirely by funtion appliation: there is no equivalent of Haskell's data delaration and pattern mathing, for instane. Relating this implementation bak to the sum-of-produt onept, we have the fields array representing the `produt' part, the lass itself as the onstrutor tag, and sublassing used to sum types. 5.3.3 The Evaluation Mehanism The main evaluation mehanism is ontained in the VarStak lass whih is used to represent the Aladin stak. The funtion-lookup table is held as a stati eld of the AAM lass whih, for onveniene, will be sublassed by the lasses where we dene our primitives. Eah of these primitives is represented by a stati method as in our Ginger ompiler. Again we use the java.lang.reflet pakage to reet these methods as as Method objets whih are stored as a member of the Funtion lass (see above). These methods take a number of Var objets as arguments and return a value of type Prog. Unlike in the semantis given in Setion 5.1 the heap is not passed or returned as an expliit parameter sine it is a global objet whih an be destrutively updated. The evaluation of a program is triggered by a all to the eval method of the Var objet that refers to the program we wish to evaluate. This method reates a new stak and triggers the transformation of the stak (f. the meta-funtion F dened in equation 5.14) and is dened as follows: 135 publi Prog eval() throws UnknownValueExeption { Prog p = get(); if (p instaneof App || p instaneof Funtion && ((Funtion) p).arity == 0) { // only need to bother evaluating if this variable refers to an // App or a CAF. (new VarStak(this)).transform(); return get(); } else return p; } If it is possible to evaluate the value of the variable, that is if it is an appliation or a CAF, then a new VarStak is reated and we trigger the transformation of the stak. Otherwise, no evaluation needs to be done. For onveniene, the method returns the nal value of the variable using the method get (whih short-iruits hains of Var objets). The method transform in the VarStak lass repeatedly transforms the stak using Rules 5.15{5.23 as a guide. This method starts as follows: publi void transform() throws EvaluationExeption { for (;;) { Var v = head(); Objet head = v.get(); Transformation of the stak will terminated by a return statement. The method 136 head returns, without popping, the Var at the top of the stak. If the value of this objet is an App then we push its funtor onto the stak and ontinue (f. Rule 5.16): if (head instaneof App) push(((App) head).funtor); If the value is a funtion we have to determine its arity. If we have enough arguments we an trigger the appliation of the funtion to its arguments (f. Rules 5.19 and 5.22): else if (no_args < f.arity) { // Not enough arguments, so just evaluate those we an evalArgs(f); return; } The nal ase if the head is a funtion is when we have too many arguments. In this ase (desribed in Rule 5.18) we evaluate the inner appliation by popping the appropriate elements from the stak (the funtion itself and the orret number of arguments) and forming these into a new stak whih we then transform. One this new stak has been fully evaluated, we ontinue transforming the original one, the head of whih will be the result of evaluating the new one. else { // too many arguments VarStak inner = partition(f.arity); inner.transform(); } } } 137 The method partition does the job of splitting the stak up. Its denition is given below. If the head of the stak is a data objet then we an just return from the transform method: } else { // head must be a data objet or an unknown. return; } } } The method evalArgs extrats the arguments from the stak and evaluates them with respet to the stritness of the passed funtion: private Var[℄ evalArgs(Funtion f) { // we need to preserve the ordering -- the stak is held reversed // for effiieny when pushing Var[℄ args = new Var[ount - 1℄; UnknownValueExeption unknown = null; for (int i = 0; i < ount - 1; i++) { // get the argument part args[i℄ = ((App) elements[ount - (i + 2)℄.get()).arg; if (f.stritIn(i + 1)) args[i℄.eval(); } 138 return args; } The method partition splits the urrent stak in two, returning the stak representing the inner appliation and keeping the stak representing the outer appliation on the original stak. Note that the root of the inner appliation is kept as the head of the stak used in the outer appliation (thus, stritly speaking, we have not got a partition sine one element is in both halves): private VarStak partition(int num_args) { VarStak inner = new VarStak(num_args + 1); int root_index = ount - (num_args + 1); inner.ount = num_args + 1; System.arrayopy(elements, root_index, inner.elements, 0, inner.ount); ount = root_index + 1; return inner; } The method update of Var deals with the updating of variable with a new value as in Equation 5.12. Note that for simpliity we assume that p is reduible if it is an App or a CAF, rather than a funtion applied to at least the orret number of arguments as in Equation 5.12. publi final void update(Prog p) { // find the end of the indiretion hain Var v = this; while (v.value instaneof Var) 139 v = (Var) v.value; if (p instaneof Var) { // might be able to short-iruit any hains Prog p_value = ((Var) p).get(); if (p_value instaneof App) v.value = p_value; else if (p_value instaneof Funtion) v.value = (((Funtion) p_value).arity == 0) ? p : p_value; else v.value = p_value; } else v.value = p; } 5.4 Primitives As mentioned above, our basi mehanism for implementing primitives is the stati Java method. In this setion we shall only deal with how the user writes suh primitives and how primitives in written in dierent languages are handled by Java. The mehanism of importing primitives is handled in Setion 5.4.6. Sine Aladin is essentially a pure funtional language, it is expeted that the user makes the primitives dened referentially transparent, that is, depend only on the value of their arguments. 140 5.4.1 Java and Aladin Primitives Eah primitive that is written in Java (and also Aladin sine we ompile Aladin denitions to Java methods | see Setion 5.5) is represented by an instane of the RunTimeFuntion lass a sublass of Funtion (see Setion 5.3). This has a eld of type java.lang.reflet.Method whih reets the (stati) method where the atual ode is stored. To primitively apply suh a funtion to a number of arguments merely requires applying the invoke method from the Method lass to the arguments. publi lass RunTimeFuntion extends Funtion { publi Method app_rule; publi Prog primApply(Var[℄ args) { return (Prog) app_rule.invoke(null, args); } } Note that primApply only returns the result rather than the updated heap as in Rule 5.6 as any updates to the heap will be done globally. The atual writing of the Java ode an be split into three parts: 1. Extrat some or all of the values from the Var objets. 2. Do the atual work involved. 3. Wrap the result in a Prog objet if neessary and return. For example, onsider the method op if of stritness s l l ! l whih does the work of the onditional if: publi stati Prog _op_if(Var a, Var t, Var f) { 141 return (a.getBool()) ? t : f; } We rst extrat the value of a using the method Var.getBool whih presumes that the ultimate value of the variable is a boolean and returns it as a Java boolean. This method saves us doing the type ast and eld aess in the ode of the primitive. There are also methods getInt, getReal, getChar and getString for getting the Java long, double, har and String values of a variable. If evaluation needs to be done before the value is extrated than the related funtions, evalBool, evalInt, et. may be used. Depending on the value of a, we either return the true branh of the ondition, t, or the false branh, f. Note that the evaluation of the result is left to Aladin's evaluation mehanism sine op if delares that it returns a lazy result. We ould delare op if to have a strit result, in whih ase the evaluation would have to be done inside the primitive: publi stati Prog _op_if(Var a, Var t, Var f) { return (a.getBool()) ? t.eval() : f.eval(); } Note that suh an approah would have to reate a new state (more speially, a VarStak objet) in whih to evaluate either t or f; the version whih returns a lazy result will evaluate t or f in the original state. The latter approah means that not only do we avoid the work needed to reate a new state, but we do not waste memory by having two states in memory at the same time. Of ourse, primitives an be more omplex than this one-liner. Consider the following funtion to ompute fatorials (of stritness s ! s): publi stati Prog fa(Var v) { long value = v.getInt(); 142 // do the atual work long f = 1; for (long i = 2; i <= value; i++) f *= i; /* wrap the answer in an Int and return */ return new Int(f); } We rst extrat the integer whih we want to take the fatorial of. The answer is then generated in an iterative loop and paked into an Aladin Int before returning. 5.4.2 C Primitives Implementing primitives in C relies heavily on the Java Native Interfae [30℄. Eah C funtion, whih originates from some shared dynami library, has a orresponding Java method delaration whih is used by Aladin to interfae to that funtion, as with the standard use of native methods in Java. Thus, to Aladin, a C primitive is the same as a Java or Aladin one (there is a CFuntion lass for representing primitives written in C, but this is only used at ompile time). The atual ode for the primitives follows the pattern for writing primitives in Java. For example, we an dene the fatorial funtion in C, analogous to the Java one in Setion 5.4.1, as follows: JNIEXPORT jobjet JNICALL Java_update_fa (JNIEnv *env, jlass l, jobjet var) { 143 jlong value = getInt(env, var); /* do the atual work */ jlong f = 1; jlong i; for (i = 2; i <= value; i++) f *= i; /* wrap the answer in an Int */ return makeInt(env, f); } The somewhat involved-looking prototype for eah funtion is generated by Aladin from its stritness signature using the javah utility and plaed in a C header le. This an then be opied by ut and paste into the . le where the ode is to maintain onsisteny and save typing a ompliated expression. The rst argument of the funtion is a pointer to an environment representing the JVM. This allows us to all Java methods, reate Java objets, et., from C (similar to the neessity of passing the heap as the rst parameter of primitives in the semantis). The seond argument is the lass of the Java method whih this funtion provides the implementation for; it is not used by the funtion. The rest of the arguments are the atual arguments of the funtion, in this ase we have just one. The funtion getInt is the C equivalent of the Java getInt method (see Setion 5.4.1), in fat the former alls the latter during exeution: jlong getInt(JNIEnv *env, jobjet v) { 144 jobjet Var = getVar(env); jmethodID I = (*env)->GetMethodID(env, Var, "getInt", "()J"); /* get the value of var */ return (*env)->CallLongMethod(env, v, I); } This ode has to nd the Var lass, get an ID for its getInt method and nally all it. It is dened in a separate shared library (libAladin.so in UNIX) whih must be linked in when the user ompiles their ode. The other related methods, getBool, evalInt et., have similar denitions in C. After omputing the fatorial we need to wrap the result (a jlong) in an Aladin Int objet. This is done using the makeInt funtion: jobjet makeInt(JNIEnv *env, jlong val) { jobjet Var = getVar(env); jmethodID ConsInt = (*env)->GetMethodID(env, Var, "<init>", "(J)V"); /* Invoke and return */ return (*env)->NewObjet(env, Var, ConsInt, val); } The steps take are similar to the getInt funtion. We have a whole family of funtions to make Aladin programs from eah primitive Java/C type. For example, makeReal and makeApp onstrut reals and appliations respetively. 5.4.3 Primitives in Ginger Eah primitive written in Ginger is represented by a GingerFuntion objet: 145 publi lass GingerFuntion extends Funtion { private fp.ginger.Fun ginger_funtion; // ... } Instead of the app rule eld found in the RunTimeFuntion lass we have an objet of the Fun lass (we expliitly qualify eah lass from the fp.ginger pakage to make it lear where methods and lasses originate and beause there are some name lashes between pakages) reeting the Java method that the Ginger ode has been onverted to (see Setion 4.3). The primApply method is implemented to use this eld: publi Prog primApply(Var[℄ args) throws EvaluationExeption { Objet[℄ gargs = new Objet[args.length℄; for (int i = 0; i < gargs.length; i++) gargs[i℄ = args[i℄.toGinger(); Objet g_res = ginger_funtion.apply(gargs); g_res = fp.ginger.Node.eval(g_res); return fromGinger(g_res); } Before alling the funtion with the given arguments, the arguments in question must be onverted to a format Ginger will reognise, and similarly we must onvert the result bak into Aladin. Sine Aladin and Ginger are both funtional languages this onversion is fairly straightforward, with a ouple of aveats. 146 As Ginger is lazy, it is possible that an argument, or a part of it, may pass through a all to a Ginger funtion ompletely unaltered. Converting an Aladin program to a Ginger objet involves the reation of a new and distint objet, similarly with the reverse proess. Suppose we have an Aladin program is passed to a Ginger funtion f ; a a whih will be onverted to a Ginger funtion g, say. Suppose now that g is passed bak as part of the result of the Ginger funtion (for instane as part of a list); it will be onverted to an Aladin objet a0 , say. So, a and a0 are really the same objet, but during the onversion proess they have beome divored from eah other. In partiular, if we evaluate a then this evaluation is not reeted in a0 and vie versa. This an have a serious eet on performane. For instane onsider the following Ginger program. lists x = [x..℄ : lists (x + 1); hdlists n = let ns = map hd (lists 0); in if n == 0 then ns else take n ns endif endlet; Although hdlists normally runs in linear time with respet to the parameter n, if we import it into Aladin then the above onversion problem slows it down to exponential time. 147 The solution is to ahe the Aladin-Ginger onversions so that when we wish to onvert an unhanged objet bak from Ginger we an retrieve the original Aladin program, and vie versa, rather than reating a new one. This ahe stores only the most reent onversions reated or referened to prevent the mahine being logged up with long-irrelevant onversions and to enable Aladin and Ginger objets to be released for possible garbage olletion. To minimise the onversion work done, we make sure that the Ginger objet is in WHNF, by alling the Node.eval method, before onversion is done. Although the onversion of Aladin objets to and from Ginger is for the most part straightforward, onverting Aladin funtions to Ginger ones is slightly more omplex. To solve this problem we represent Aladin funtions in Ginger using the lass AladinFun, a sublass of the Ginger funtion lass, Fun. publi lass AladinFun extends Fun { proteted Var al_fun; publi Objet apply(Objet[℄ as) { Var v = al_fun; for (int i = 0; i < as.length; i++) v = new Var(v, Prog.fromGinger(as[i℄)); v.eval(); return v.toGinger(); } } 148 This lass stores as a member the original Aladin funtion (or rather, a Var objet whih refers to it). When Ginger applies suh a funtion, by alling its apply method, we form a new Aladin appliation by onverting the passed arguments from Ginger to Aladin whih are given as arguments to the original Aladin funtion. We then evaluate this funtion using Aladin, onvert the result bak to Ginger and then return. Again these onversions are ahed. 5.4.4 Overloading Our implementation of Aladin allows primitives to be overloaded in a manner that is more systemati than Ginger's approah, though not as expressive and powerful as Haskell's. This overloading is only known to the primitives and is in no way built into our implementation. We use Java interfaes to lassify lasses whose instanes an be ombined using the methods of the interfae. For instane, instanes of the Comparable interfae must implement the eq (equals) and ne (not equals) methods (f. Haskell's Eq lass desribed in Setion 2.4) dened as: publi interfae Comparable { publi Prog eq(Prog p); publi Prog ne(Prog p); } It is these methods that are alled by the op eq (prex variant of ==) and op ne (prex variant of ~=) methods (see Setion 5.2.4). For instane, op eq is dened as: publi stati Prog _op_eq(Var x, Var y) { Prog a = x.get(), b = y.get(); 149 if (a instaneof Comparable) return ((Comparable) a).eq(b); else return (a.equals(b)) ? Bool.TRUE : Bool.FALSE; } This primitive (whih is strit in both its arguments) rst tests of it an ompare its two arguments using the Comparable.eq method; if not then it uses the equals method whih all Java lasses implement, either through inheritane or by overriding. Note that Comparable.eq is preferred as it returns a general Aladin program rather than a boolean. This is preferred when partially evaluating programs as we might not be able to alulate all of the result (see Setion 6.5.1). So, the user an overload the == and ~= operators on any type they wish by implementing the Comparable type in the type's lass denition. The interfae Orderable is implemented by lasses whose instanes an be ordered (f. Haskell's Ord lass), while the Num interfae is implemented by lasses whose instanes an be used in arithmeti expressions (f. Haskell's Num lass and its sublasses). Sine we wish to use Aladin to implement solutions to problems in fuzzy logi, we also introdue the Logial interfae (f. the Logi lass introdued in Setion 3.1) whih is implemented by lasses whose instanes an be ombined in logial expressions. See Setion 5.4.5 for further details. 5.4.5 Fuzzy Primitives As mentioned in Setion 5.4.4 we overload the logial primitives so that they implement both boolean and fuzzy logi. For fuzzy logi, we have to implement the Logial interfae in the Real lass. This interfae denes the methods and, or and not whih are alled by our implementation of op and (&), op or (|) and op not 150 (~) respetively. Fuzzy onjuntion, using Zadeh's denition, is implemented using the following method: publi Prog and(Var v) { if (value == 0.0) return ZERO; else if (value == 1.0) return v; else return (value < v.evalReal()) ? (Prog) this : (Prog) v; } The instane eld value is a double ontaining the value of the Real. ZERO is a stati eld of Real ontaining the Real with the value 0. Note that this primitive is still lazy in its seond argument as in the seond ase, sine 0 and 1 are the annihilator and identity of fuzzy onjuntion and we test for these values rst. The methods op or and op not are implemented similarly. Unlike in Ginger and Haskell, we do not overload the logial primitives so that they also at as set operators sine, beause Aladin is untyped, this an lead to ompliations when we partially evaluate fuzzy Aladin programs. Instead we introdue the funtions union, inter, add and omp to perform operations of union, intersetion, addition and omplement of sets. These are dened using the s dash funtion (the S0 ombinator) and funtion omposition: s_dash :: l * l * l * l -> l s_dash op f g x = f x `op` g x; union :: l * l -> l union x y = s_dash (|) x y; 151 inter :: l * l -> l inter x y = s_dash (&) x y; add :: l * l -> l add x y = s_dash (+) x y; omp :: l -> l omp x = (~) . x; We also need to dene fuzzy set operations point-wise over tuples for use in fuzzy systems whih have more than one output variable. Again, beause of ompliations due to partial evaluation, this is done using separate funtions rather than overloading. The funtions applyUn and applyBin map a unary funtion over a tuple and ombine two tuples with a binary funtion respetively (f. the list funtions map and zipWith). These have the following behaviour: applyUn f (x1 , : : : ,xn ) = (f x1 , : : : ,f xn ) and applyBin (x1 , : : : ,xn ) (y1 , : : : ,yn ) = (x1 y1 , : : : ,xn yn ) The weighting operator (==> in Haskell and Ginger) beomes the funtion when in Aladin: when :: l * l * l -> l when w f x = if isZero w | isZero (f x) then 0 else w * f x endif; The funtion isZero returns whether a program has the value 0; it is espeially useful if we are partially evaluating (see Setion 6.6.6). All other fuzzy funtions have obvious translations from their Haskell ounterparts. 152 We an now implement our shower ontroller from Setion 3.4.1 in Aladin. Given the domain hangedom = [-0.2, -0.175 .. 0.2℄ over whih we defuzzify the hanges to the hot and old taps, and the fuzzy subsets of temperature, ow and hange, we an dene the fuzzy rule base funtion, hange valves as: hange_valves :: s * s -> l hange_valves temp flow = let defuz = entroid hangedom; hanges = rulebase (applyBin add) [ applyUn (when (old temp & weak flow)) (pm, z), applyUn (when (old temp & right flow)) (pm, z), applyUn (when (old temp & strong flow)) ( z, nb), applyUn (when (ok temp & weak flow)) (ps, ps), applyUn (when (ok temp & strong flow)) (ns, ns), applyUn (when (hot temp & weak flow)) ( z, pb), applyUn (when (hot temp & right flow)) (nm, z), applyUn (when (hot temp & strong flow)) (nb, z)℄; in (defuz (fst hanges), defuz (snd hanges)) endlet; While this may not be as elegant as the Haskell version it is very amenable to partial evaluation (see Setions 6.6.6 and 6.6.7) and, as we shall see in the next hapter, partially evaluating it leads to a residual program whih is an order of magnitude faster than the original. 153 5.4.6 Importing Primitives As we shall see in Setion 5.5, we shall ompile Aladin denitions into stati Java methods, so the import proedure for primitives dened in Aladin and Java is the same. The import delarations are onverted into method alls and plaed in the stati method initialise lass that is reated for eah ompiled Aladin sript. This method is alled when the lass is loaded, either by the lass's main method or by the import mehanism depending on whether the lass is the one being run to evaluate programs or has been imported respetively (see Setion 5.5.4). The rst setion of ode in initialise lass reates the funtion, setting its stritness signature and inserting it into the heap, but for the moment leaving its app rule (see Setion 5.4.1) null for the moment. As an example, we have the following from stdlib.java | the ode generated from stdlib.as where the arithmeti funtions, amongst others, are dened: publi stati void __initialise__lass__() throws Exeption { put("fp.aladin.lib", "Operators", "_op_plus", CONST_0); put("fp.aladin.lib", "Operators", "_op_minus", CONST_0); put("fp.aladin.lib", "Operators", "_op_times", CONST_0); put("fp.aladin.lib", "Operators", "_op_divide", CONST_0); // ... } The stati put method (inherited from the AAM lass whih is the superlass of all lasses reated by the ompiler) reates a new funtion from its arguments and inserts it into the heap. The rst three arguments represent the pakage, lass and name of the funtion to be reated. The last argument is the stritness signature, whih is reated only one and stored as a private eld of the generated lass (see 154 Setion 5.5.2). Here CONST 0 represents the stritness signature s s ! s. After setting the funtions up, we have to ll in their app rule eld. This is done by going through eah lass, inluding the lass that is doing its importing sine any funtions it denes itself are held in that lass, using the getDelaredMethods method from the java.lang.Class lass and lling in eah funtion with its appropriate Method objet. If we end up with any funtions whih we have a stritness signature for but no orresponding Method then an error has ourred. Eah primitive that is written in C and diretly imported will have had a orresponding Java method header reated in the generated lass le, hene these primitives will be imported by the same mehanism that imports Java and Aladin primitives. However, we also need to load in the libraries where the atual objet ode an be found. This is done by loading eah library using the System.loadLibrary method. Importing Ginger primitives is done similarly to importing Aladin and Java primitives, exept that as eah Ginger funtion is stored as a fp.ginger.Fun eld of the Ginger lass generated by the Ginger ompiler from eah Ginger sript (see Setion 4.5.2) we have to examine the elds of eah Ginger lass rather than the methods. 5.5 Compilation The aim of the ompiler is to take an Aladin sript and translate it into a Java program whih will then be ompiled by a Java ompiler into a Java lass le. Our ompiler has three jobs: 1. To provide a stati Java method for eah primitive dened in the sript. 2. To set the stritness of eah funtion dened in the sript and any imported funtion whose stritness is redened in the sript. 155 3. To import all the funtions speied in the sript. The latter two jobs will be aomplished by putting the ode that ahieves their objet in the method initialise lass of the reated lass, whih will be alled by Aladin when that lass is loaded. The whole import proedure is triggered when a lass is run using the Java interpreter by plaing a all to initialise lass in the main method of eah reated lass le. 5.5.1 Compiling Sripts To ompile a sript we need the following parameters: , the lass to reate (derived from the name of the sript); p, the pakage to plae the reated lass in, delared using pakage (if no suh delaration the p is set to the empty string); ss , the set of distint stritness signatures used in a sript; s , the set of distint onstants used in a sript; fs , the set of funtions dened in the sript with those imported into the sript who have their stritness signature set or over-ridden; ls , the lasses ontaining primitives dened in Java and Aladin diretly im- ported into a sript; gs , the lasses ontaining primitives dened in Ginger diretly imported into a sript; ls , the shared objet libraries ontaining primitives dened in C/C++ diretly imported into a sript; ds , the funtions dened in the sript (with their denitions) plus any primitives dened in C whih are diretly imported. 156 The ompilation sheme P reates a Java program using these parameters P p ss s fs ls gs ls ds = pakage p; import fp.aladin.*; publi final lass extends AAM f S ss s fs ls gs ls ds publi stati void main(String[℄ args) throws Exeption f initialise lass (); parseAndEval(args); g g If no pakage has been given then we omit the pakage delaration. The S sheme reates the ode used to initialise the lass and reates the method denitions for eah funtion dened in the sript. The main method will be alled if we run the reated lass using the java interpreter or some other way of exeuting Java lasses. It rst initialises the lass, by importing all the required lasses and funtions and setting any required stritness signatures. It then parses the ommand line arguments. Some of these are used to set options, for instane, whether to pretty print, whether to partially evaluate and how many deimal plaes to use when printing real numbers. The rest are assumed to form an expression to be evaluated. If there is no suh expression then Aladin attempts to evaluate the main CAF dened in the lass (if there is one). Note that Java allows us to overload the main method, so there is no onit between main(String[℄), the method alled by the Java interpeter when the lass is run, 157 and main(), the ompiled form of the main CAF. 5.5.2 Compiling Constants and Stritness Signatures We do not reate a separate objet representing all the onstants and stritness signatures in a lass. Rather, for eah distint onstant we reate just one instane and store it as a private eld of the lass we are reating and read this eld whenever we want an instane of the partiular onstant or stritness signature. S (s1; : : : ; sm) (1 ; : : : ; n) fs ls gs ls ds = 1 1 ::: n n s (n + 1) s1 ::: s (n + m) sm publi stati void initialise lass () throws Exeption f Dfs ls gs ls ds where 2 3 75 6 7! CONST i; : : : ; n 7! CONST n; =4 s1 7! CONST (n + 1); : : : ; sm 7! CONST (n + m) 1 The funtion ompiles a onstant and plaes it in a stati private eld of the lass being reated: i = private stati Var CONST i = new Var(); The Var lass has a onstrutors to onstrut variables whih point to the appropriate program for eah basi type. The funtion s is similar to but it ompiles 158 a stritness signature instead. s i (1 : : : n ! ) = private stati StritnessSig CONST i = new StritnessSig(as , r ); where as = new boolean[℄ ai r f a1 , : : : , an g = true; if i = s = false; otherwise = true; if = false; otherwise =s 5.5.3 Compiling Stritness Delarations The D sheme delares the stritness signature of eah funtion. The nal parameter is an environment detailing whih onstant or stritness signature orresponds to eah eld. For a onstant or stritness signature, its orresponding eld is (). D (f1; : : : ; fm ) ls gs ls ds = put(p1 , 1 , n1 , s1 ); ::: put(pm , m , nm , sm ); I ls gs ls ds where pi = pakage (fi ) i = lass (fi ) ni = name (fi ) si = (stritness (fi )) 159 5.5.4 Compiling Imports The I sheme deals with reating the ode needed to import the various lasses and libraries. I (1 ; : : : ; n ) (g1 ; : : : ; gm ) (l1 ; : : : ; lk ) ds = importClass("1 "); ::: importClass("n "); importGinger("g1 "); ::: importGinger("gm "); System.loadLibrary("l1 "); ::: System.loadLibrary("lk "); g H ds Note the terminating brae for the denition of initialise lass started by the S sheme. 5.5.5 Compiling and Optimising Loal Bloks Our Aladin ompiler ontains a faility to eliminate ommon subprograms in a denition. Consider the following denition of the funtion subseqs whih returns all the subsequenes of a list: subseqs :: s -> l subseqs xs = 160 if isEmpty xs then [[℄℄ else subseqs (tl xs) ++ map ((:) (hd xs)) (subseqs (tl xs)) endif; Evaluation of this funtion applied to an argument has to evaluate the subseqs (tl xs) and tl xs twie. We would expet the user to reognise this and lift the repeated appliations into a let. However there is no reason why we annot let the ompiler do it instead. For eah program denition, we examine its denition for any repeated nontrivial subprograms. For our purposes, a non-trivial subprogram is an appliation or a funtion, the latter beause the ode to onstrut a funtion is done using a hash table lookup and it is quiker if we only have to do this lookup one. So, for example, the above denition of subseqs is optimised to: subseqs xs = let v5 = _op_list_empty; v3 = (subseqs (tl xs)); v0 = _op_list_ons; in (((_op_if (isEmpty xs)) ((v0 v5) v5)) ((_op_list_at v3) ((map (v0 (hd xs))) v3))) endlet Sine both the funtions op list empty and op list ons our more than one in the denition of subseqs they are lifted into a loal denition, similarly with the appliation subseqs (tl xs). Note that by lifting the reursive all to subseqs into a loal denition we now only have one ourrene of tl xs and so this does not have to be lifted out into a separate denition. This is ensured by a omplimentary 161 optimisation: eliminating redundant lets, that is loal denitions whose variable ours only one in the entire denition. While these optimisations are not ritially important in normal Aladin programs, sine an experiened programmer should be able to spot when to introdue loal denitions, they are important when we partially evaluate Aladin programs. This is beause all knowledge of loal denitions is lost after ompilation and so the denition of any residual programs may ontain repeated subprograms. See the next hapter for further details. Before ompilation all loal variables are `oated' to the top level using the funtion. This funtion splits a program into a list of delarations and a program ontaining no lets: ((let v = D in B endlet) C ) = ((v; D0 ) : ds ++ es ; P ) where (ds ; P ) = (B C ) (C (let v = D in B (es ; D0 ) = D endlet)) = ((v; D 0 ) : ds ++ es ; P ) where (ds ; P ) = (C B ) (es ; D0 ) = D (BC ) = (ds ++ es ; B 0 C 0 ) where (ds ; B 0 ) = P (es ; C 0 ) = = (hi; P ) B C In a onventional ompiler we would have to be areful about how far outwards we oated loal variable delarations as we might end up unneessarily building the graph of programs that ould otherwise be avoided. For instane, in a ondi162 tional expression a onventional ompiler an exploit its knowledge of the onditional primitive to build only the graph related to the `true' branh of the onditional and ignoring all the rest. Sine Aladin knows very little about how its primitives work we an perform no suh optimisation, but this does mean we do not have to be as areful about where we plae our loal variable delarations as in a onventional ompiler. The H sheme ompiles all the denitions in a sript plus reates headers for any primitives written in C into the sript: H(f1; : : : ; fn) = F f1 ::: F fn The sheme F ompiles an individual funtion. If the funtion is written in C or C++ we just need to delare a native method: F f n = publi final native stati Prog f (Var x 1, : : : , Var x n); where x i are dummy parameter names and n is the arity of the funtion. Eah denition rst has all its variables renamed so that they are all distint and then ompiled using the F sheme: F (f x1 : : : xn = E) = publi final stati Prog f (Var x1 , Var v1 = C D1 vs ; ::: Var vm = return C Dm vs ; R E 0 vs ; g 163 : : : , Var xn ) f where (h(v1 ; D1 ); : : : (vm ; Dm )i; E 0 ) = E vs = fx1 ; : : : ; xn ; v1 ; : : : ; vm g 5.5.6 Compiling Simple Programs The R and C shemes ompile a simple program. They dier only in how they treat the outermost part of a program when that part is an appliation. If we have a onstant then we simply have to load the relevant eld: C vs = R = () If we have an identier then we either have a funtion or a variable. We an tell the dierene by seeing if the identier is in the set of variables passed to C : C id vs = if id id ; = get(id ); 2 vs otherwise If we have a variable then the ode to ompile is thus that variable, otherwise we presume it is a funtion name and look it up in the heap using the get funtion inherited from the AAM superlass. If we have an appliation, then the C sheme ompiles the funtor and the argument using the C sheme and then forms then into an App whih is pointed to by a Var (ahieved using the two-argument onstrutor of Var): C (f a) vs = new Var(C f vs , C a vs ) If we are ompiling the outermost appliation then we do not need to reate the Var. Hene R is dened as: R (f a) vs = new App(C f vs , C a vs ) 164 5.5.7 Compiling the Target Code and Using the Resultant Classes The ode reated by the ompiler is plaed in a .java le whih is ompiled into a lass le using the java ompiler. If the sript diretly imports any C/C++ funtions then javah is run over the generated lass to reate the header le dening the prototypes for the imported C/C++ funtions. For example, suppose we have the following denitions in the sript foo.as: import fp.aladin.stdlib; import fp.aladin.gstdlib; importg bar lists :: s -> l hdlists :: s -> l; main :: -> l main = hdlists 10; where lists and hdlists are as dened in Setion 5.4.3. Then this sript is ompiled into the Java le foo.java (the indenting has been added by hand): import fp.aladin.*; publi lass foo extends AAM { private stati fp.aladin.Var CONST_2 = new fp.aladin.Var(10); private stati fp.aladin.StritnessSig CONST_0 = new fp.aladin.StritnessSig(new boolean[℄ {true}, false); private stati fp.aladin.StritnessSig CONST_1 = new fp.aladin.StritnessSig(new boolean[℄ {}, false); 165 publi stati void __initialise__lass__() throws Exeption { importClass("fp.aladin.stdlib"); importClass("fp.aladin.gstdlib"); putGinger("", "bar", "lists", CONST_0); putGinger("", "bar", "hdlists", CONST_0); importGinger("bar"); System.loadLibrary("Aladin"); put("", "foo", "main", CONST_1); importClass("foo"); } publi stati void main(String[℄ args) throws Exeption { __initialise__lass__(); parseAndEval(args); } publi stati Prog main() { return new fp.aladin.App(get("bar.hdlists"), CONST_2); } } This is then ompiled into the lass foo.lass. The lass fp.aladin.stdlib ontains various standard funtions, while fp.aladin.gstdlib ontains import delarations and stritness signatures for Ginger primitives and funtions in its standard prelude. We an now evaluate programs. To evaluate main we simply pass foo as an argument to java with the -pp2 argument to speify that we want to pretty-print 166 lists using the square braket notation: gem:~/Aladin/progs> java foo -pp2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9℄ We an also apply hdlists to a dierent argument: gem:~/Aladin/progs> java foo -pp2 hdlists 20 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19℄ Sine we import stdlib into our sript, we have aess to standard funtions suh as arithmeti and omparison operators, and hene we an evaluate programs using these funtions via the foo lass: gem:~/Aladin/progs> java foo '(3 < 2) | (2 >= 8 + 5)' false As well as ommand-line evaluation, we provide a graphial environment in whih the user an interatively load, disard and evaluate programs. Figure 5.5 shows an example of the above three programs being evaluated in the graphial environment. Further examples of running Aladin programs an be found in the next hapter. 5.6 Summary The Aladin Abstrat Mahine is a useful tool for investigating aspets of funtional programming, sine its simpliity allows us to onentrate on the vital issues. Its ontrol over stritness allows the user to speify when and if parts of a program should be evaluated, useful when importing primitives from a variety of languages, eah with diering evaluation strategies. We have given the semantis of the Aladin Abstrat Mahine and an implementation of the mahine, written in Java and reating a Java lass. At present, our implementation is able to import primitives from 167 Figure 5.5: A GUI for Aladin programs written in Java, C/C++, Ginger and the simple Aladin sripting language that we have dened in this hapter, using the fat that there are ompilers available that target Java lass les for all three of these languages. Sine the semantis of Aladin onern only the evaluation of programs, the onstrution of data strutures and other suh entities an only be done by means of funtion appliations. However, sine funtions an be written in any language of the user's hoie, there is no xed template that the user must adhere to, and they have the freedom to implement the strutures that their programs use in any way they see t. The Aladin sripting language whih we developed in Setion 5.2, while outwardly resembling Ginger, is in fat a sugared way of onstruting Aladin programs (as graphs of funtions applied to arguments). Data strutures suh as lists and tuples are redued to funtion appliations and it is expeted that the user provide suitable implementations of these funtions. Ginger, on the other hand, has a xed set of data strutures whih are built into the language and whih the language's ompiler is aware of. This allows the 168 Ginger ompiler to generate ode whih will diretly onstrut data strutures in some ases (suh as lists | see Setion 4.5.2) and also to diretly all funtions. In eet, the ompiler is doing some evaluation of the program to be ompiled in order to ahieve these optimisations. Another area where Aladin diers from other Ginger and other funtional languages is in its use of stritness. In a onventional funtional language ompiler, whatever stritness information there is | obtained by stritness analysis, for example, or by dening a known set of primitives to be strit as in Ginger (see Setion 4.4) | is used by the ompiler to optimise ode and is disarded at run-time. Stritness information is an integral part of Aladin, however, and is used to ontrol every step of the evaluation proess at run-time and, as we shall see in the next hapter, to ontrol partial evaluation. In essene, Aladin oers simpliity and exibility at the ost of slower running times when ompared to Ginger and other funtional languages, a familiar trade-o in all branhes of programming. 169 Chapter 6 Partial Evaluation in Aladin In the previous hapter, we mentioned that Aladin ould be used for partial evaluation and designed some of the primitives so that they ould be more eetively used in partially-evaluated programs. In this hapter we shall make good on our earlier promises and show how partial evaluation an be done in Aladin. Partial Evaluation [29, 51℄ is a means by whih speialised programs an be obtained from more general ones by evaluating the general one with only some of its arguments instantiated. The speialised programs are typially onsiderably faster than the more general ones | an order of magnitude faster is not untypial. The simpliity of Aladin, and its ontrol over stritness, make this a suitable vehile for partial evaluation, as rst noted in [11℄. Simpliity is advantageous when partially evaluating as it keeps to a minimum the ases when partial evaluation is dierent from non-partial evaluation, and makes these dierenes easier to determine. The advantage of stritness ontrol when partially evaluating is that we know exatly what parts of a program it is safe to try to partially evaluate and whih are not. We shall also see that beause Aladin has no xed set of primitives, the user an implement the primitives that their program uses so that they exploit partial evaluation to the full. 170 6.1 The Denotational Semantis Partially evaluating a program involves evaluating it when some of its inputs are not known, the result being a residual program whih will yield the same result as the original program when the rest on the inputs are provided. In the ontext of an Aladin program, this means that some of the variables ourring in the program have no value in the heap the program is being evaluated with respet to. That is, the program is being evaluated in a heap x and ontains some variable x suh that = ?. We will refer to suh a variable as an unknown variable. In the literature, if a parameter to a funtion ontains unknown variables it is lassed as dynami, otherwise it is lassed as stati. Our denotational semantis require a few modiations to handle partial evaluation. First, if we have an unknown variable applied to a number of arguments (possibly none) then we just return the original heap, as in the ase when we have a data objet applied to a number of arguments: U [x0 7! p 2 D [ f?g; x1 7! x0 y1 ; : : : ; xn 7! xn 1 yn ℄ xn = (6.1) If we have a funtion applied to too few or too many arguments, then the rules are the same as in the non-partial evaluation ase and given in Rules 5.9 and 5.10 respetively. If we have a funtion applied to the exat number of arguments we rst evaluate any strit arguments | using A whih retains the same denition as that given in Rule 5.13 | and then see if it is possible to apply the funtion. This is the ase eah argument ontains no unknowns (in other words, is stati) or the funtion being applied is lazy in that argument (and hene the value of the argument may 171 not be needed). 2 x0 7! f :: 1 : : : m ! U 64 x1 7! x0 y1; : : : ; xm 7! xm where 00 = P 0 xm ; = 0; 0 = A if 1 ym 3 75 xm = 00 Vm (stati 0 y ) _ ( = l) i i i=1 (6.2) otherwise xm The funtion stati returns whether a program ontains no unknowns: stati x = stati = x f ^ stati if a; 6= ?; x =f a otherwise The P meta-funtion deals with the job of triggering the atual appliation of the program (using the meta-funtion): 2 x0 7! f :: 1 : : : m ! ; P 64 x1 7! x0 y1; : : : ; xm 7! xm 1 ym 3 75 xm = 000 where 000 = U 00 xm ; = 00 ; 00 = update 0 xm r; = 0; (r; 0 ) = if =l otherwise (6.3) if r 6= ? otherwise f ( ; y1 ; : : : ; ym ) The `answer' part of the result (r in the above denition) of an be ? if either an error ours or the value of an unknown variable is required. 6.2 The Operational Semantis The hanges in the operational semantis to handle partial evaluation mirror those made in the denotational semantis. In the rst ase, if we have an unknown as well 172 as a data objet at the top of the stak, then no further work an be done and we need to make no hanges. If the dump is non-empty we restore it (whih remains the same as desribed in Rule 5.23), else we terminate as no more rules apply. hi hEVALi hi hx0 ; x1 ; : : : ; xn i =) [x0 7! p 2 D [ f?g℄ (6.4) The rules for unwinding an appliation at the head of the stak and, when we have a funtion applied to too few or too many arguments at the head of the stak, remain the same as desribed in Rules 5.16, 5.17 and 5.18 respetively. If we have a funtion of arity m at the top of the stak and m other elements at the top of the stak then we rst evaluate the neessary arguments and then see if it is possible to trigger the appliation (as in equation 6.2): hEVALi hEVALARGS mi ++ C hx0 ; x1 ; : : : ; xm i 3 2 hx0 ; y1; : : : ; ymi ++ S = ) x ! 7 f :: : : : ! ; 0 1 m 75 64 xi 7! xi 1 yi where (C; S ) = (hAPPLYi; hxm i); = (hi; hi); if Vm ((stati i=1 yi ) (6.5) _ (i = l)) otherwise The rules for the EVALARGS and EVALITH stay the same as in the non-partial ase and dened in Rules 5.20 and 5.21 respetively. The APPLY instrution triggers the 173 primitive appliation as in equation 6.3: C hAPPLYi hroot i hx0 ; y1; : : : ; ym ; root i =) 00 [x0 7! f :: 1 : : : m ! ℄ where C hi; = hEVALi; 00 = update 0 root = 0; (r; 0 ) = 6.3 if = =s_r =? (6.6) otherwise r; if r 6= ? otherwise f ( ; y1 ; : : : ; ym ) Implementation To represent unknowns, we introdue the following lass to our Aladin implementation desribed in the previous hapter: publi final lass Unknown extends Prog { private String name; // ... } We store the identier used to refer to the unknown inside the Unknown objet sine identier names are otherwise lost at ompile time. To signify that an unknown value has been enountered where one was not expeted, we reate the UnknownValueExeption lass, instanes of whih will be thrown in suh ases. We now need to adapt the evaluation mehanism to deal with unknown values. First of all, we have the eval method of the Var lass. This heks to see if the 174 Var objet being evaluated is an unknown and if so throws an exeption, if not it ontinues as before. publi Prog eval() throws UnknownValueExeption { if (p instaneof App || p instaneof Funtion && ((Funtion) p).arity == 0) // only need to bother evaluating if this variable refers to an // App or a CAF. (new VarStak(this)).transform(); else if (p instaneof Unknown) // Can't evaluate this var further; use the exeption to notify // the aller that an Unknown was enountered. throw new UnknownValueExeption(); return get(); } The only required hange to the transform method, rst introdued in Setion 5.3.3, is a hek to make sure that after any strit arguments have been evaluated no strit argument ontains unknowns. This is done by the hekForStritUnknowns method: private void hekForStritUnknowns(Funtion f, Var[℄ args) { for (int i = 0; i < args.length; i++) if (f.stritIn(i + 1) && args[i℄.ontainsUnknowns()) throw new UnknownValueExeption(); } and transform now beomes: 175 publi void transform() throws EvaluationExeption { for (;;) { Var v = head(); Objet head = v.get(); if (head instaneof App) push(((App) head).funtor); else if (head instaneof Funtion) { Funtion f = (Funtion) head; int no_args = ount - 1; if (no_args == f.arity) { Var[℄ args = evalArgs(f); learAllButRoot(); // root is elements[0℄ if (AAM.partial) hekForStritUnknowns(f, args); Prog res = f.primApply(args); elements[0℄.update(res); // stop evaluation if f has a strit result if (f.hasStritResult()) return; } // as before 176 } // as before } } The evalArgs method must also be modied to deal with unknowns: private Var[℄ evalArgs(Funtion f) { // we need to preserve the ordering -- the stak is held reversed // for effiieny when pushing Var[℄ args = new Var[ount - 1℄; UnknownValueExeption unknown = null; for (int i = 0; i < ount - 1; i++) { // get the argument part args[i℄ = ((App) elements[ount - (i + 2)℄.get()).arg; if (f.stritIn(i + 1)) { try { args[i℄.eval(); } ath (UnknownValueExeption e) { // don't want to throw the exeption until we have // attempted to evaluate all arguments, so just make // a note of its existene for now unknown = e; } } 177 } if (unknown != null) throw unknown; return args; } Sine we need to evaluate every strit argument of the funtion being applied, we ath any UnknownValueExeptions and only throw one at the end of the method when we have attempted to evaluate all strit arguments. 6.4 Partially Evaluating Programs Reall that our Aladin implementation takes programs written in the Aladin sripting language and ompiles them to Java lass les. Suppose we have the following funtion dened in the le power.as: power :: l * s -> l power x n = if n == 0 then 1 else x * power x (n - 1) endif; Compiling this funtion yields a Java lass le, power.lass. We an then use this lass le along with the java interpreter to evaluate Aladin programs. For example: > java power 'power 12 3' 1728 We an also supply an unknown value as the rst argument of power and obtain a speialisation. For instane, we an speialise power to a ube funtion viz : > java power -p -pp -n ube 'power x 3' 178 ube :: l -> l ube x = x * (x * (x * 1)); The -p option denotes that we wish to do partial evaluation (any undened identiers are treated as unknowns rather than errors); -pp indiates that we wish to `pretty' print the result (in this ase, write the multipliation as an inx funtion using the * symbol, rather than as the alpha-numeri prex version as would be the normal ase); and the -n option supplies the name of the residual funtion. The result of the partial evaluation is an Aladin funtion, with all unknown values beoming arguments of that funtion, whih an be redireted to a le and ompiled. It is the fat that power is lazy in its rst argument whih allows us to speialise the funtion like this; if it were strit then no evaluation ould be done. This suggests a general rule of thumb when it omes to stritness and partial evaluation: if a parameter at a ontrol point, for example the anteedent part of an if expression, then that parameter is strit and annot have an unknown value; otherwise it is lazy and an have an unknown value. This orrespondene was noted by Launhbury [62℄, who showed that doing stritness analysis and binding-time analysis | a preevaluation proess whih determines whih expression in a program an be partially evaluated given the limited amount of data that will be present | at the same time ould prove useful. So far, we have only desribed how to use Aladin to partially evaluate programs at the ommand line. The residual funtion must then be put manually into a program le. This has several advantages, for instane, it makes testing easier and printing a program fores all of its omponents to be (partially) evaluated (for example, every element of a list will be evaluated and printed, rather than just leaving the list in its initial ons form). However, this restrits us to partially evaluating one funtion at a time and requires the intervention of a human user. 179 We therefore allow the user to speify that some partial evaluation be done at ompile time, by allowing the user to denote that the body of a funtion is the result of partially evaluating another. For instane, we an dene the ube funtion from above as: ube :: s -> l ube x => power x 3; Here we use the => in plae of = to indiate that we want to partially evaluate the RHS of the denition, regarding the parameters of the LHS as unknowns, before assigning it as the denition of ube. We ould legitimately partially evaluate the RHS of all funtion denitions | whih would give us suh ompiler enhanements as onstant folding (see Setion 14.7.1 of [86℄ for example), and funtion unfolding [17, 16℄. However, partially evaluating programs may not terminate or doing so may take so muh time that the ompiler beomes unfeasibly slow. Compile-time partial evaluation has other advantages. For example, in the above we have speied that ube is strit in its argument, whereas produing the funtion at the ommand line makes ube lazy in its argument by default. Sine our implementation an only evaluate ompiled funtions (ompiled by both Aladin and the Java ompiler) any funtions whih appear on the RHS of funtion denitions whih we wish to partially evaluate must have been pre-ompiled, in other words, they annot appear in the same le as the funtion being partially evaluated. This is only a minor inonveniene and does not aet our analysis. Partially evaluating a program may lead to a residual program that is less spae eÆient than the original one. This is beause the original funtion(s) may have been implemented to use spae-saving devies suh as aumulating parameters and tail reursion whih might be expanded in the denition of the residual funtion(s). It is hard to predit in advane when this might our to a detrimental eet sine 180 prediting the spae behaviour of funtional programs is notoriously diÆult [15, 86, 97℄, and it is up to the user whether the the speed-up due to partial evaluation is outweighed by any additional spae used. We are only onerned with the time behaviour of partially-evaluated programs in this hapter, and will not onsider spae behaviour further. However, we have not yet enountered any problems aused by spae ineÆieny during our investigations into partial evaluation. 6.5 Primitives and Partial Evaluation Some primitives an be rewritten to more omprehensively handle partial evaluation. For instane, onsider the omparison operations. For an unknown x, we know the following: x == x =) TRUE x ~= x =) FALSE x <= x =) TRUE x < x =) FALSE x >= x =) TRUE x > x =) FALSE The above equations an be inorporated into the ode for the primitives, at the ost of a little more programming eort and making the operators lazy in both their arguments. For example, we an rewrite the method op eq (the prex form of the equality operator ==) dened in Setion 5.4.4 as: publi final stati Prog _op_eq(Var v, Var w) { Prog a = v.eval_nu(); Prog b = w.eval_nu(); 181 if (a.ontainsUnknowns() || b.ontainsUnknowns()) { if (a instaneof Unknown && b instaneof Unknown && a.equals(b)) return Bool.TRUE; else // one or both of v or w ontains an unknown and they are not // the same unknown, hene we an't omplete the equality throw new UnknownValueExeption(); } else if (a instaneof Comparable) return ((Comparable) a).eq(b); else return (a.equals(b)) ? Bool.TRUE : Bool.FALSE; } The eval nu method is similar to the eval method exept that it athes and disards any UnknownValueExeptions. The ontainsUnknowns method returns whether the program it was invoked upon ontains any unknowns. The method rst of all evaluates its arguments (and does not have to worry about any UnknownValueExeptions) and then heks to see if either ontains any unknown values. If so, it heks to see if they are in fat the same single unknown. If so we know they have to be equal, despite not knowing their atual values, otherwise we annot omplete the inequality and throw the exeption to indiate this. As an example, onsider the funtion min whih returns the minimum of its two arguments (and stored in the sript power.as): min :: l * l -> l min x y = if x <= y then x else y endif; Partially evaluating this with two unknown, but equal, arguments gives: 182 > java power -p -pp -n min_xx 'min x x' min_xx :: l -> l min_xx x = x; Using suh an implementation of the omparison operators an have an eet on the termination properties of residual funtions, in that the residual funtion an terminate in more ases than the original funtion when restrited to the same arguments as the residual. For example, onsider the simple funtion: foo :: l * l -> l foo a b = if a == b then 1 else 0 endif; Partially evaluating this with two unknown, but equal, arguments gives: > java foo -p -pp -n foo1 'foo x x' foo1 :: l -> l foo1 x = 1; ? = 1, but the equivalent all to foo, foo ? ?, returns ?, sine ? = ?. This is a beneial side-eet of partial evaluation, analogous to Now foo1 ? == the fat that lazy funtional programs an terminate in more ases than their strit equivalents [15℄. Note that the reverse situation annot be true, sine the set of individial evaluations done during partial evaluating a program then evaluating the residual funtion is a subset of the set of individual evaluations done during full evaluation of the same program. If the result of a program is ? then the result of at least one of these individual evaluations must be ? and vie versa | hene if the ? when applied to some partiular arguments, then the original funtion must also return ? when applied to the equivalent arguments. residual funtion returns The omparison operators are not the only funtions whih an be re-oded to exploit partial evaluation. We an also re-implement the binary logial primitives 183 to take advantage of the following rules: x & true = true & x = x x & false = false & x = false x | true = true | x = true x | false = false | x = x Note this holds true in the fuzzy as well as the boolean ase. The standard implementation of these primitives have the stritness s l ! l. Logial onjuntion is performed by the op and method dened as: publi stati Prog _op_and(Var x, Var y) { Prog a = x.get(); if (a instaneof Logial) return ((Logial) a).and(y); else throw new EvaluationExeption("Can't take the logial " + "onjuntion of " + a + " and " + y); } } with logial disjuntion ( op or) being dened similarly. Rewriting the denitions of these primitives to take advantage of partial evaluation rst requires us to to make them lazy in both arguments as with the omparison operators. Also (exploiting ommutativity) if the rst argument ontains unknowns we swith the order of the arguments and see if we an obtain a result by treating the seond argument as the rst and vie versa : publi stati Prog _op_and(Var x, Var y) { 184 Prog a = x.eval_nu(); if (a instaneof Logial) return ((Logial) a).and(y); else { Prog b = y.eval_nu(); if (b instaneof Logial) return ((Logial) b).and(x); else if (a.ontainsUnknowns() || b.ontainsUnknowns()) throw new UnknownValueExeption(); else throw new EvaluationExeption("Can't take the logial " + "onjuntion of " + a + " and " + y); } } The denition of the and method an stay the same as in the non-partial ase in whatever lass implements it. For instane, in the Bool lass we have: publi Prog and(Var v) { return (value) ? (Prog) v : (Prog) Bool.FALSE; } where value is the boolean value of the Bool objet. As an example, we have: > java power -pp -p -n prog 'x <= 3 & (y < 2 | 13 > (5 + 6))' prog :: l * l -> l prog x y = x <= 3; 185 This strategy is not only useful for partially evaluating binary logial primitives: it an be used on any ommutative primitive whih an be made lazy in one of its arguments. It is espeially eetive if the operator has an annihilator sine it an be used to avoid evaluating one of the arguments altogether. For instane, from above we see that true is an annihilator of logial disjuntion and false one of onjuntion, but also 0 is an annihilator of multipliation and division. This is used to great eet in Setion 6.6 6.5.1 Partial Data Strutures Aladin treats data objets as indivisible entities, in partiular, as far as it is onerned the list [1, y, 3, x, 5℄ does not ontain any unknowns, sine by default data objets do not. This means that suh a list an be used as a strit argument of a funtion and still allow some useful partial evaluation to be done. For instane, given the following denition of reverse: reverse :: s -> l reverse xs = rev xs [℄; rev :: s * s -> l rev xs ys = if isEmpty xs then ys else rev (tl xs) (hd xs : ys) endif; partially evaluating reverse [1, y, 3, x, 5℄ yields: > java power -p -pp2 -n rev1 'reverse [1, y, 3, x, 5℄' rev1 :: l * l -> l rev1 y x = [5, x, 3, y, 1℄; 186 To be able to do more useful partial evaluation on lists and other data strutures it is useful if we an do (in)equality tests on them. For instane, we ould partially evaluate [x, y, z℄ == [1, 2, 3℄ to x == 1 & y == 2 & z == 3. This requires us to alter the implementation of the Comparable interfae (see Setion 5.4.4) in the SumProd lass so that it an handle unknown values. For the eq method we have: publi Prog eq(Prog p) { if (this.getClass().equals(p.getClass())) { SumProd sp = (SumProd) p; Prog result = Bool.TRUE; Var and = AAM.get("_op_and"); Var eq = AAM.get("_op_eq"); for (int i = 0; i < fields.length; i++) { Prog e = (new Var(eq, fields[i℄, sp.fields[i℄)).eval_nu(); if (e instaneof Bool) { // equality went to ompletion if (e.equals(Bool.FALSE)) // found fields that don't orrespond, so an return now return Bool.FALSE; } else // must have unknowns so add to list of onjuntions result = (result == Bool.TRUE) ? e : new App(and, e, result); } 187 return result; } // If we've got here then an't be equal return Bool.FALSE; } This method rst heks that the two objets to be ompared are of the same lass and then steps through eah of the elds, seeing if they are equal. If it nds two that do not math, then it immediately returns false; else it builds the result into a onjuntion (eliminating any redundant trues). We an dene ne similarly. So, for example, we have: > java power -p -pp -n eq3 '[x,y,z℄ == [1,2,3℄' eq3 :: l * l * l -> l eq3 x y z = (z == 3 & y == 2) & x == 1; and: > java power -p -pp -n neq2 "(x, y, 'a') ~= (false, 2, 'a')" neq2 :: l * l -> l neq2 x y = y ~= 2 | x ~= false; As a further example, onsider the funtion palindrome in power.as: palindrome :: s -> l palindrome xs = let n = #xs / 2; in 188 take n xs == take n (reverse xs) endlet; We an speialise this to a funtion whih heks if a list of length 5 is a palindrome: > java power -p -pp -n pal5 'palindrome [a,b,,d,e℄' pal5 :: l * l * l * l * l -> l pal5 a b d e = b == d & a == e; This partial evaluation leads to arity raising whih is normally onsidered a good thing sine we avoid having to onstrut and deonstrut data strutures [35, 95℄. However it may be preferable to have the residual funtion take a single list as its only argument. For this to work, we have to either have an alternative form of palindrome whih takes the length of the list as a parameter, or some other way of deonstruting the list into its (here 5) onstituent parts. We hoose the latter, using the funtion deons to deonstrut a list into a speied number of elements: deons :: s * l -> l deons n xs = if n == 0 then [℄ else hd xs : deons (n - 1) (tl xs) endif; Note that by making the seond (list) argument lazy, we an use an unknown in its plae. So, for instane: > java power -p -pp2 -n list5 deons 5 xs list5 :: l -> l list5 xs = [hd xs, hd (tl xs), hd (tl (tl xs)), hd (tl (tl (tl xs))), hd (tl (tl (tl (tl xs))))℄; 189 Note that the ommon subprograms here will be lifted out by the ompiler as desribed in Setion 5.5.5. So, the above funtions is optimised to: list5 xs = let v2 = hd; v1 = tl; v4 = (v1 xs); v5 = (v1 v4); v3 = (v1 v5); v0 = (:); in (v0 (v2 xs) (v0 (v2 v4) (v0 (v2 v5) (v0 (v2 v3) (v0 (v2 (v1 v3)) _op_list_empty))))) endlet; However, for purposes of readability, all future examples of residual funtions will be given in non-optimised form. We an now produe a speialisation of palindrome whih takes a single argument: > java power -p -pp2 -n pal5 'palindrome (deons 5 xs)' pal5 :: l -> l pal5 xs = hd (tl xs) == hd (tl (tl (tl xs))) & hd xs == hd (tl (tl (tl (tl xs)))) 190 The above data strutures are lazy in that the evaluation of their omponents is not done until required, whih might not be until the printing stage. This an prevent some useful partial evaluation being done at ompile time if the omponents of a data struture an be evaluated but evaluation has stopped beause Aladin does not evaluate the omponents of data strutures. For instane, [(1 + 2) .. 10℄ evaluates to 1 + 2 : [((1 + 2) + 1) .. 10℄. If we evaluated this program at the ommand line then we would not notie this, sine printing the list fores its evaluation, but if the list was evaluated at ompile time then it would only be evaluated to the rst ons. To alleviate this problem, we provide the primitive fore, as in Miranda and Ginger, whih fores evaluation of all parts of its argument in the exat same way as printing it would. Care must be taken not to use fore on innite data strutures. For instane, the speialisation of matrix multipliation to 2 2 matries used in Setion 6.6.1 is oded as: mult_2x2 :: l -> l mult_2x2 p => let m1 = dedeons 2 2 (fst p); m2 = dedeons 2 2 (snd p); in fore (mult m1 m2) endlet; Here dedeons n m deonstruts a list of lists into a list of n elements eah of whih is a list of m elements. 191 6.5.2 Partial Evaluation and C Primitives To be able to partially evaluate C/C++ primitives we need to be able to deal with UnknownValueExeptions. Also, being able to detet unknown values is also useful if we wish to do something other than abort evaluation when unknown values our (see below). First of all, the following maro will abort a funtion if any exeption ours: #define abortIfExeption(env) \ if ((*(env))->ExeptionOurred(env)) return 0 The result of the funtion in whih the exeption ourred will be null and the exeption is thrown bak up to the JVM. For example, onsider the following power funtion, whih will be imported into an Aladin sript update.as. This funtion is delared to have stritness l s ! s and its implementation is: JNIEXPORT jobjet JNICALL Java_update_power (JNIEnv *env, jlass l, jobjet x, jobjet y) { jint power = getInt(env, y); if (power == 0) return makeInt(env, 1); else { jint base = evalInt(env, x), result = base; // abort exeution if any exeptions have ourred abortIfExeption(env); for (; power > 1; power--) 192 result *= base; return makeInt(env, result); } } This gets the value of y and if it is zero immediately returns 1. Otherwise it evaluates x and extrats its integer result. At this point, if x ontains unknowns an UnknownValueExeption will have been thrown. This will be deteted by the abortIfExeption maro and evaluation will be immediately aborted. If things went well, we just ompute the power using an iterative method and return. An alternative way is to evaluate x and expliitly test to see if it ontains unknowns. In this ase, we an unfold the exponentiation into multiple appliation of the multipliation funtion, op times. We provide a ontainsUnknowns funtion analogous to its Java namesake and funtions and maros whih will reate and throw the appropriate exeptions. So, we an rewrite power as: JNIEXPORT jobjet JNICALL Java_update_power (JNIEnv *env, jlass l, jobjet x, jobjet y) { jint power = getInt(env, y); if (power == 0) return makeInt(env, 1); else { eval(env, x); if (ontainsUnknowns(env, x)) { 193 /* return multiple appliations of `*' */ jobjet timesx = makeApp(env, getFuntion(env, "_op_times"), x); jobjet mult = x; for (; power > 1; power--) mult = makeApp(env, timesx, mult); return mult; } else { /* an work out the full answer */ jint base = getInt(env, x); jint result = base; for (; power > 1; power--) result *= base; return makeInt(env, result); } } } 6.5.3 Partial Evaluation and Ginger Primitives As our Ginger ompiler produes Java byte-ode (Chapter 4), we do not have to worry about expliitly deteting and throwing exeptions sine this is handled by the JVM. We do, however, have to worry about Unknown objets sine there is no 194 equivalent in Ginger and we need to onvert all Aladin programs into Ginger ones before applying the Ginger primitive. To deal with this we introdue the GingerProg lass whih represents a `suspended' onversion: publi final lass GingerProg extends fp.ginger.App { proteted Var aladin; publi GingerProg(Var v) { aladin = v; args = empty; total_app = true; in_whnf = false; } publi Objet eval() { aladin.eval(); return aladin.toGinger(); } } All lazy arguments of a Ginger primitive will be onverted to the above lass; strit arguments annot ontain unknowns (sine otherwise we would not be applying the primitive) and so an be safely onverted. When (and only when) Ginger attempts to evaluate an objet, the Aladin program is evaluated and then onverted to its Ginger representation. This means that if a lazy argument of a Ginger primitive ontains unknowns, we an still exeute the Ginger primitive and only if the argument ontains unknowns after it has been evaluated (whih may not be required) will an 195 exeption be thrown, sine trying to onvert an Unknown to a Ginger representation will raise an UnknownValueExeption. This has the added benet that if a lazy argument is part of the result of a Ginger primitive and is unevaluated throughout then no onversion from Aladin to Ginger and vie versa has to be done: we an simply extrat the value of the aladin eld. 6.6 Further Examples and Results One question needs to be asked: does partially evaluating Aladin programs have any benet? In this setion we shall present ve further, more substantial examples of programs where some partial evaluation an be done and then ompare performanes. Note that we have given the results of partial evaluation at the ommand line sine this is the most readable form, but all partial evaluation desribed below is done at the ompile time for the purposes of obtaining results. Note that for display purposes we have split the answer Aladin returns into separate lines and indented appropriately, but no other manipulation of the answers has been done. 6.6.1 Matrix Multipliation Consider the following program to multiply two matries together, where a matrix is represented by a list of lists: row :: s * s -> l row n xss = xss ! n; ol :: s * s -> l ol n xss = map (flip (!) n) xss; no_rows :: s -> l 196 no_rows xss = # xss; no_ols :: s -> l no_ols xss = # (hd xss); mprod :: s * s * s * s -> l mprod xss yss r = if r == no_rows xss then [℄ elsif == no_ols yss then [℄ : mprod xss yss (r + 1) 0 else let m = mprod xss yss r ( + 1); v = sum (zipWith (*) (row r xss) (ol yss)); in (v : (hd m)) : (tl m) endlet endif; mult :: s * s -> l mult xss yss = mprod xss yss 0 0; Matrix multpliation has many uses. One suh use is the rotation of points about some origin in an n-dimensional spae. For example, the result of rotating a twodimensional point (x; y) lokwise about the origin by radians is given by the result of the matrix multipliation: 1 0 0 os sin C B B A x 1 C A y sin os This an be oded as the following Aladin funtion: 197 // oords as a pair, theta in radians rotate :: l * l -> l rotate theta oords = let x = fst oords; y = snd oords; roords = mult [[os theta, sin theta℄, [neg (sin theta), os theta℄℄ [[x℄, [y℄℄; in (hd (roords ! 0), hd (roords ! 1)) endlet; The appliation of mult used in the denition of rotate is xed to matries of a spei size, namely a 2 2 matrix multipled by a 2 1 matrix, and hene we have sope for partial evaluation: > java matrix -p -pp2 'rotate theta (x, y)' rotate :: l * l * l -> l rotate theta x y = (os theta * x + (sin theta * y + 0), neg (sin theta) * x + (os theta * y + 0)); All list operations, onditionals and reursive alls have disappeared and all we are left with are simple mathematial operations. More partial evaluation an be done by xing theta to be a speif angle, for example =2: > java matrix -p -pp2 -n quarter 'rotate (pi / 2) (x, y)' quarter :: l * l -> l quarter x y = (0 * x + (1 * y + 0), -1 * x + (0 * y + 0)); 198 These residual programs ontain some redundant operations, namely the addition of zero and multipliation by one and zero. We an eliminate this by providing alternative denitions of op plus and op times. Rather than re-implementing the funtions from srath, we an provide wrapper funtions to the original funtions to do the job. First of all we dene the funtions isZero and isOne of stritness l ! s whih tests if a value is denitely zero or denitely one. We an implement isZero viz : publi stati Prog isZero(Var x) { try { Prog p = x.eval(); if (p instaneof Int) return ((Int) p).value == 0 ? Bool.TRUE : Bool.FALSE; else if (p instaneof Real) { double d = ((Real) p).value; return (-1.0E-7 < d && d < 1.0E-7) ? Bool.TRUE : Bool.FALSE; } else return Bool.FALSE; } ath (UnknownValueExeption e) { return Bool.FALSE; } } 199 If x ontains unknowns then it annot denitely be zero, hene we return false. Note that if x refers to a real we return true if the value is in the range ( 110 7 ; 110 7 ) so as to ope with rounding errors. The funtion isOne is dened similarly. We an now dene our wrapper funtions, for example, op times viz : _op_times :: l * l -> l _op_times x y = if isZero x | isOne y then x elsif isZero y | isOne x then y else Operators._op_times x y endif; where the method Operators. op times is the original denition of multipliation dened in Aladin. Note that if either x or y ontain unknowns then we default to the standard denition and so do not drag the graph of the if statement around while evaluating the rest of the program. Our speialisation of rotation for an angle of =2 is now: > java matrix -p -pp2 -n quarter 'rotate (pi / 2) (x, y)' quarter :: l * l -> l quarter x y = (y, -1 * x); In the tests whih we obtain results for, we shall speialise rotate to angles of =2, and 3=2 and rotate 256 points by these angles. 6.6.2 Gaussian Elimination Gaussian Elimination (see [59℄, for example) is a systemati way of solving systems of linear equations. If we have a system: a11 x1 + : : : a1n xn = b1 200 .. . am1 x1 + : : : amn xn = bm Then we an form the augmented matrix: 0 B B B B B 1 C .. C C . C C A a11 ::: a1n b1 am1 ::: amn bm .. . .. . ... and by a ombination of forward elimination and bak substitution we an obtain a solution, if one exists. This is a problem ripe for partial evaluation, espeially if the aij are known and our matrix is sparse (that it, has a lot of zero entries). This was rst reognised in [31℄ though not desribed as partial evaluation, as suh. The Aladin program implementin Gaussian elimination is: // augment an m x n1 matrix with an m x n2 one augment :: -> s augment = zipWith sno; // add an element onto the end of a list sno :: s * l -> l sno xs x = xs ++ [x℄; // partition xs into a pair of lists, the first element of whih // is all those elements of xs satisfies the prediate p, the // seond one all those that don't splitWith :: s * s -> l splitWith p xs = if isEmpty xs then ([℄, [℄) else 201 let ps = splitWith p (tl xs); x = hd xs; in if p x then (x : fst ps, snd ps) else (fst ps, x : snd ps) endif endlet endif; // reorder xs, s.t. all those elements of xs whih satisfy p // ome before those that don't reorder :: s * s -> l reorder p xs = let ps = splitWith p xs; in fst ps ++ snd ps endlet; // returns whether the kth element of xs is not zero nonZeroKth :: s * s -> l nonZeroKth k xs = (xs ! k) ~= 0.0; // pivot/reorder the rows of the matrix xss s.t. all the rows of // xss with a non-zero kth element ome before those that don't pivot :: s * s -> l 202 pivot xss k = let p = nonZeroKth k; yss = reorder p xss; in if p (hd yss) then yss else error "Singular Matrix" endif endlet; // subtrat a onstant multiple of the first row of the given matrix // from all proeeding rows so that in the result matrix all bar the // first row have a zero element in the kth position (forward // elimation) the resultant matrix will be in triangular form forwardElim :: s * s -> l forwardElim k xss = if isEmpty xss then [℄ else let pss = pivot xss k; first = hd pss; rest = tl pss; fator = first ! k; // Non-zero rows = map (subtratRows first k) rest; in first : forwardElim (k + 1) rows endlet endif; 203 // auxiliary funtion for forwardElim: performs the atual // multipliation/subtration subtratRows :: s * s * s -> l subtratRows xs k ys = let m = (ys ! k) / (xs ! k); in zipWith (-) ys (map ((*) m) xs) endlet; // bak substitutes the values obtained for eah olumn of the // matrix (i.e., variable when the matrix is onsidered as a system // of equatiuons) starting from the last row, whih should have // only one non-zero olumn in the unaugmented part. bakSub :: s -> l bakSub rs = if isEmpty rs then [℄ else let xs = bakSub (map tl (tl rs)); r = hd rs; a = hd r; as = tl r; b = last r; in ((b - sum (zipWith (*) as xs)) / a) : xs endlet 204 endif; // redue a given augmented matrix to triangular form and use bak // subsitition to solve the underlying simultaneous equations solve :: -> s solve = bakSub . forwardElim 0; Now, suppose we have the following system of equations: 3x1 2x2 = 1 2x1 + 5x2 = 26 Then we an inlude the following matrix in our program: two_x_two :: ->l two_x_two = [[3.0, -2.0℄, [2.0, 5.0℄℄; The above system of equations an be solved by augmenting this matrix with the values of the RHSs of the above equations and using this augmented matrix as the argument to the solve funtion: > java gauss_npe -pp2 -p 'solve (augment two_x_two [1, 26℄)' [3.0, 4.0℄ That is, x1 = 3 and x2 = 4. Alternatively we an hoose to leave the RHSs of the system of equations unknown: 3x1 2x2 = b1 2x1 + 5x2 = b2 and augment the matrix with these unknown values. Partial evaluation an then give us values for x1 and x2 in terms of these unknowns: 205 > java gauss_pe -pp2 -p -dp 3 'solve (augment two_x_two [b1, b2℄)' solve :: l * l -> l solve b1 b2 = [(b1 - -2 * ((b2 - 0.667 * b1) / 6.333)) / 3, (b2 - 0.667 * b1) / 6.333℄; where the value of x1 is the rst element of the list on the RHS of the funtion denition, and x2 the seond element. In the tests we shall give times for, we have the following system of equations (from [31℄): 7x1 3x3 2x1 +8x2 = b1 = b2 = b3 = b4 = b5 +6x6 = b6 x5 +x3 3x1 +5x4 +4x5 x2 2x 4 whih we solve for fb1 ; : : : ; b6 g 2 permutations f1; : : : ; 6g. 6.6.3 Exponentiation We an use the denition of ex (where e = 2:718 : : :): 1 xn X ex = n=0 n! to obtain an exponential funtion in Aladin: // return the exponential of z, using n iterations exp :: s * l -> l exp n z = if n == 0 then 1.0 else exp1 n z 1.0 1.0 1.0 1 206 endif; // return the exponential of z, using n interations; e_z is the // urrent approximation; num the urrent numerator and denom the // urrent denominator of the term of the power series; and i the // urrent power of the power series exp1 :: s * l * l * l * s * s -> l exp1 n z e_z num denom i = if i == n then e_z else let new_num = z * num; new_denom = i * denom; new_e_z = e_z + new_num / new_denom; in exp1 n z new_e_z new_num new_denom (i + 1) endlet endif; This an be speialised for a spei n, for example: > java exp -p -pp2 -n exp6 'exp 6 z' exp6 :: l -> l exp6 z = ((((1.0 + z) + (z * z) / 2) + (z * (z * z)) / 6) + (z * (z * (z * z))) / 24) + (z * (z * (z * (z * z)))) / 120; In the tests we shall do, we shall speialise n to 20 and use it to work out ex for x 2 f0; =720; : : : ; g. 207 6.6.4 Polynomials Polynomials an be represented as lists of their oeÆients. For example, a0 + a1 x + : : : + an xn an be represented as the list [a0 ,a1 , : : : an ℄. Evaluation of a polynomial for a spei value of x an be oded in Aladin as: eval :: s * l -> l eval p x = if isEmpty p then 0 else hd p + x * eval (tl p) x endif; It is fairly simple to add two polynomials together: add :: s * s -> l add p q = if isEmpty p then q elsif isEmpty q then p else hd p + hd q : add (tl p) (tl q) endif; One we have dened addition of polynomails, we an dene multipliation: mult :: l * l -> l mult p q = foldr add [℄ (summands 0 p q); // return a list of the summands in the sum; eah summand is the // produt of the polynomial q with eah element of q; ord is the // urrent power summands :: s * s * l -> l summands ord p q = 208 if isEmpty p then [℄ else let s = rep ord 0 ++ map ((*) (hd p)) q; in s : summands (ord + 1) (tl p) q endlet endif; These funtions an be speialised for polynomials of a ertain degree, or even for polynomials for xed oeÆients. For example, we an obtain a speialised funtion whih multiplies two quadratis together: > java poly -pp2 -p -n mult2 'mult [a0,a1,a2℄ [b0,b1,b2℄' mult2 :: l * l * l * l * l * l -> l mult2 a0 a1 a2 b0 b1 b2 = [a0 * b0, a0 * b1 + a1 * b0, a0 * b2 + (a1 * b1 + a2 * b0), a1 * b2 + a2 * b1, a2 * b2℄; In the tests we shall do, we perform 720 quadrati multipliations. 6.6.5 Integration by Simpson's Rule Simpson's rule for integration of a funtion f over the range [a; b℄ over 2n sub intervals is given as: Zb h f (x) dx (f0 + 4f1 + 2f2 + 4f3 + : : : 2f2n 2 + 4f2n 1 + f2n ) 3 a where h = (b a)=2n and fi = f (a + hi). This an be enoded as the following Aladin program: 209 // integrate f over the range [a, b℄ whih is split into 2n intervals integrate :: s * l * l * l -> l integrate n a b f = let h = (b - a) / (2.0 * n); // fore real division in (h / 3) * (sumInt (2 * n - 1) f h (a + h) (f a + f b)) endlet; // returns the sum of integrating the f over m intervals, where // eah interval starts at x and is h wide; sub_total is the // umulative total sumInt :: s * l * l * l * l -> l sumInt m f h x sub_total = if m == 0 then sub_total else let = if m % 2 == 0 then 2 else 4 endif; in sumInt (m - 1) f h (x + h) (sub_total + * f x) endlet endif; There is muh sope for partial evaluation here. We an speialise integrate on a xed number of sub-intervals, 4 for example: > java integrate -p -pp2 -n integrate2 'integrate 2 a b f' integrate2 :: l * l * l -> l 210 integrate2 a b f = (((b - a) / 4.0) / 3) * ((((f a + f b) + 4 * f (a + (b - a) / 4.0)) + 2 * f ((a + (b - a) / 4.0) + (b - a) / 4.0)) + 4 * f (((a + (b - a) / 4.0) + (b - a) / 4.0) + (b - a) / 4.0); There is also the possibility of speialising for a xed interval too, [0; 1℄, say: > java integrate -p -pp2 -dp 4 -n int2_01 'integrate 2 0.0 1.0 f' int2_01 :: l -> l int2_01 f = 0.0833 * ((((f 0 + f 1) + 4 * f (0 + 0.25)) + 2 * f ((0 + 0.25) + 0.25)) + 4 * f (((0 + 0.25) + 0.25) + 0.25)); Note that the argument to the ourrenes of f in the sum have not been evaluated, even though they ould have been. This is beause sumInt is lazy in its third and fourth arguments (the width of the interval, h, and the urrent value x) to allow partial evaluation to be done when the range is not known. If the range is known, we an hange the stritness of sumInt to s l s s l ! l and do more partial evaluation: > java integrate -p -pp2 -dp 4 -n int2_01 'integrate 2 0.0 1.0 f' int2_01 :: l -> l int2_01 f = 0.0833 * ((((f 0 + f 1) + 4 * f 0.25) + 2 * f 0.5) + 4 * f 0.75); 211 In the tests we shall do, we shall use the above to alulate values for the standardised normal distribution, that is evaluate (z ) for z 2 f 4; 3:99; : : : ; 4g where: (z ) = p1 Zz 2 1 e x2 =2 dx This is enoded in Aladin as: e_minus_half_x2 :: l -> l e_minus_half_x2 x = MathPrimitives.exp ((x * x) / -2.0); phi :: s -> l phi z = let f = 1 / sqrt (2 * pi); in f * integrate 8 -6.0 z e_minus_half_x2 endlet; This uses a lower bound of 6 (the integral over ( 1; 6) is suÆiently small to be ignored) and 16 sub-intervals. We an partially evaluate this funtion simply by replaing phi z = with phi z => and letting the ompiler do the work. 6.6.6 Fuzzy Systems Reall from Setion 5.4.5 the denition in Aladin of the shower ontroller: hange_valves :: s * s -> l hange_valves temp flow = let defuz = entroid hangedom; hanges = rulebase (applyBin add) [ 212 applyUn (when (old temp & weak flow)) (pm, z), applyUn (when (old temp & right flow)) (pm, z), applyUn (when (old temp & strong flow)) ( z, nb), applyUn (when (ok temp & weak flow)) (ps, ps), applyUn (when (ok temp & strong flow)) (ns, ns), applyUn (when (hot temp & weak flow)) ( z, pb), applyUn (when (hot temp & right flow)) (nm, z), applyUn (when (hot temp & strong flow)) (nb, z)℄; in (defuz (fst hanges), defuz (snd hanges)) endlet; where hangedom is the list [-0.2, -0.175 .. 0.2℄. The funtion entroid is dened as: entroid :: s * l -> l entroid dom f = let fdom = map f dom; in sum (zipWith (*) dom fdom) / sum fdom endlet; Naturally, we would expet do be able to partially evaluate entroid by xing dom to be hangedom and indeed we an do so to beneial eet. We would not expet to be able to x either of the arguments of hange valves sine it is expeted that the funtion is to be used in a dynami situation where the parameters are onstantly hanging. However, there is a somewhat surprising sope for partial evaluation arising from the fat that our fuzzy subsets may overlap, but not to any 213 great extent. Consider the alulation needed to ompute the hange in the hot valve (the rst element of the pair in the rule base). We weight and sum the fuzzy subsets and pass this as the f parameter to entroid. This has then to work out the degree to whih eah element of hangedom is in this sum, amongst other things. Now, the way that fuzzy sets are normally arranged in a fuzzy system means that a value usually ours in at most two dierent fuzzy subsets to a non-zero degree. For instane, 0:2 is only in the fuzzy subset nb to a non-zero degree. This means that for eah element of hangedom only a limited amount of rules an possibly ontribute to the weighted sum, in the ase of 0:2 only the last rule ontributes. For eah element of hangedom and we an partially evaluate the weighted sum of eight fuzzy subsets down to the weighted sum of one or two. For the hanges to the hot valve (the hanges to the old valve partial are similar) over hangedom the weighted sum redues to: [(-0.2, up 36 75 temp & up 12 25 flow), (-0.175, (up 36 75 temp & up 12 25 flow) * 0.833), (-0.15, (up 36 75 temp & up 12 25 flow) * 0.667), (-0.125, (up 36 75 temp & up 12 25 flow) * 0.5), (-0.1, (up 36 75 temp & up 12 25 flow) * 0.333), (-0.075, (up 36 75 temp & atri 9 ((9 + 15) / 2) 15 flow) * 0.667 + (up 36 75 temp & up 12 25 flow) * 0.167), (-0.05, (up 36 75 temp & atri 9 ((9 + 15) / 2) 15 flow) * 0.667), (-0.025, atri 32 ((32 + 40) / 2) 40 temp & up 12 25 flow), (0, (down 15 36 temp & up 12 25 flow) + (up 36 75 temp & down 0 12 flow)), (0.025, atri 32 ((32 + 40) / 2) 40 temp & down 0 12 flow), 214 (0.050, (down 15 36 temp & down 0 12 flow) * 0.667 + (down 15 36 temp & atri 9 ((9 + 15) / 2) 15 flow) * 0.667), (0.075, (down 15 36 temp & down 0 12 flow) * 0.667 + (down 15 36 temp & atri 9 ((9 + 15) / 2) 15 flow) * 0.667), (0.100, 0), (0.125, 0), (0.150, 0), (0.175, 0)℄; We use the denitions of multipliation and addition in Setion 6.6.1 to eliminate any redundant arithmeti operations. Note that the denitions of hot, strong, et. have been evaluated down to their representations as standard fuzzy subsets and also that atri is lazy in its seond argument (the value at whih the membership funtion hits the value 1) and hene (9 + 15) / 2 and (32 + 40) / 2 remain unevaluated. We an now partially evaluate hange valves, though we have to make it lazy in both its arguments and use a fore to fore evaluation of the two elds of the resultant pair. In the tests we give results for, we alulate how long it takes the shower to ome to a satisfatory temperature and ow for hot and old valves set to the values 0:0; 0:2; : : : ; 1:0 (that is, 36 runs of the shower program). 6.6.7 Results Table 6.1 gives running times for evaluating Aladin programs with and without partial evaluation, using the same mahine as was used to obtain the results for Ginger in Setion 4.5.6. As with our Ginger ompiler, we also have the overhead of initialising the JVM (roughly 0.5{1.0 seonds). Partially evaluating programs an result in a signiant derease in running times, with a derease of an order of 215 Running times (s) Speed up Fator No Part. Eval. Part. Eval. due to Part. Eval. Matrix Multipliation 13:0 2:3 5:7 Gaussian Elimination 318:6 12:1 26:1 Exponentiation 12:0 5:1 2:4 Polynomials 24:0 8:8 2:7 Integration 17:3 8:6 2:0 Shower Controller 92:5 6:4 14:5 Table 6.1: Comparisons of running times of Aladin programs with and without partial evaluation magnitude in the ase of the Gaussian elimination and shower ontroller programs. Our results suggest two lasses of speed-ups that an be obtained from partially evaluating Aladin programs. The rst omes from simply unfolding the denition of a funtion so that ontrol strutures are replaed by ombining lots of simple funtion appliations in one single program. This was observed in the exponential, polynomial and integration programs and results in a speed-up fator of 2{3. The seond lass arises when, after or during unfolding, many of the simple funtion appliations an be eliminated by exploiting the algebrai rules of the funtions involved (implementing the funtions involved aordingly). This was seen in the matrix multipliation, Gaussian elimination and shower ontroller programs and the speed-up fator was typially an order of magnitude. The extremes of this an be seen with the Gaussian Elimination test | beause the matrix is sparse, many operations (and hene heap alloations) an be avoided entirely by exploiting the arithmeti rule 8x:x 0 = 0 x = 0 | and with the fuzzy shower ontroller whih xes the domain over whih our weighted sum is defuzzied and hene many fuzzy membership tests are eliminated. The results in Table 6.1 do not inlude the time taken to do any partial evaluation, just the time taken to evaluate the original or residual program. In an 216 Matrix Multipliation Gaussian Elimination Exponentiation Polynomials Integration Shower Controller Compile + Run times (s) No Part. Eval. Part. Eval. 5:8 + 13:0 = 18.8 6:1 + 2:3 = 8.4 5:4 + 318:6 = 324.0 8:8 +12:1 = 20.9 5:7 + 12:0 = 17.7 6:1 + 5:1 = 11.2 4:6 + 24:0 = 28.6 5:6 + 8:8 = 14.4 5:3 + 17:3 = 22.6 8:8 + 8:6 = 17.4 7:0 + 92:5 = 99.5 12:2 + 6:4 = 18.6 Table 6.2: Compile + Run times of Aladin programs with and without partial evaluation environment where the resiudal program is used many times, the ost of partially evaluating the original program an be amortized over eah evaluation of the residual program and beome negligible. However, in a situation where the residual program is only run one or two times, the ost of the partial evaluation beomes more signiant and, if too high, negates the argument for using partial evaluation in the rst plae. Sine all partial evaluation is done at ompile time, the ost of partial evaluation an be alulated by examining the ompile times of the programs with and without partial evaluation. Table 6.2 gives the times for ompiling and running a single time eah of our example programs. Compiling involves ompiling the Aladin ode into Java and then ompiling the Java ode into Java byte-ode, as well as partially evaluating when neessary. It turns out that ompiling and running a program whih does some partial evaluation during ompilation is faster than ompiling and running a program without partial evaluation, at least in these ases. This was noted by Jones et al [51℄ among others, and is analogous to the situation that ompiling and running a program is often faster than interpreting it. Sine Aladin uses the same syntax as Ginger, it is not too diÆult to use Ginger to evaluate the results of partially evaluating an Aladin program, giving us a way of 217 partially evaluating Ginger programs. (A summary of the dierenes between Aladin and Ginger an be found in Setion 5.6.) This involves partially evaluating the required Aladin funtions at the ommand line and manually utting-and-pasting the results into Ginger programs (the automation of this proess is disussed in Setion 7.1). For instane, in our test of the matrix multipliation program we use the following funtions: quarter, half, three_quarters :: -> s quarter = rotate (pi / 2); half = rotate pi; three_quarters = rotate (3 * pi / 2); rotations :: l * l -> l rotations x y = (quarter (x, y), half (x, y), three_quarters (x, y)); The funtion rotations is partially evaluated at the ommand line using Aladin: > java matrix -pp2 -dp 0 -p -og 'rotations x y' rotations :: l * l -> l rotations x y = let v2 = (*) -1; v1 = (v2 y); v0 = (v2 x); in mkTuple_3 (mkTuple_2 y v0) (mkTuple_2 v0 v1) (mkTuple_2 v1 x) endlet; Here the -og option tells Aladin to optimise the residual funtion for pasting into a Ginger program. This eliminates ommon subprograms and redundant loal deni218 Running times (s) Speed up Fator No Part. Eval. Part. Eval. due to Part. Eval. Matrix Multipliation 4:4 1:3 3:4 Gaussian Elimination 57:7 4:4 13:1 Exponentiation 3:6 1:6 2:3 Polynomials 5:7 2:8 2:0 Integration 4:4 3:6 1:2 Shower Controller 15:3 2:1 7:3 Table 6.3: Comparisons of Running times of Ginger programs with and without partial evaluation tions on the residual program but, unlike when optimising Aladin programs, funtions are not treated as individual subprograms sine Ginger funtions are ompiled to eld aesses and in some ases may be diretly applied (see Setions 4.5.2 and 4.6.1). This new denition of rotations an then be manually ut-and-pasted into the Ginger program and used instead of the old one. Denitions of mkTuple 2 and mkTuple 3 also have to be supplied. Table 6.3 shows the running times of evaluating these partially-evaluated programs. The time required by partial evaluation remains the same as that in Table 6.2. Again, we ahieve onsiderable speed-ups, but notie they aren't quite as big as those ahieved by Aladin. This is beause Aladin, or at least out partiular implementation of it, an benet more from partial evaluation than other languages sine nearly everything has to be done via heap alloation and, as we saw in Setion 4.7, Java is not partiularly fast at heap alloation when ompared to a dediated funtional engine like the Haskell interpreter Hugs [50℄. Hene, the more heap alloation we an avoid doing, the proportionally better performane beomes. 219 6.7 Summary We have shown that Aladin is a suitable mahine for partial evaluation, by giving the denotational and operational semantis for a version of the AAM with unknown values, and then produing an implementation based on these semantis. By using the fat that Aladin's primitives may be implemented in any language and none are built in to the language, we have produed a set of primitives and data strutures whih exploit partial evaluation to the full. We have shown that using stritness delarations not only allows us to make lazy arguments strit for eÆieny purposes, but strit arguments (or at least arguments that an be made strit without aeting the termination properties of the program) lazy for partial evaluation purposes. Another advantage of using Aladin to partially evaluate programs is the fat that it has no built-in primitives, letting the user supply their own. This means that the primitives an be adapted to exploit partial evaluation to the full. A good example of this is with the multipliation funtion: by using the arithmeti rule 8x:x 0 = 0 x = 0 and partially evaluating programs in whih this rule ould be exploited, we ould irumvent large amounts of evaluation and redue run times by an order of magnitude. In general, partially evaluating Aladin programs results in a signiant derease in running times, and in some ases these running times are shorter than those of the equivalent programs in Ginger. The results of partially evaluating programs an also be evaluated using Ginger, albeit with a little manual intervention on the user's part. 220 Chapter 7 Conlusion and Further Work Our stated aim of this thesis, bak in Chapter 1, was to takle the downsides of funtional programming, by expanding the range of appliations in whih funtional programming ould be used, inreasing the portability of funtional programs, enabling funtional languages to be interfaed with other languages, and using novel evaluation strategies to inrease the performane of funtional language implementations. Have we ahieved these aims? Our implementation of fuzzy logi in a number of funtional languages is elegant and onise. One the synonymity of funtions and fuzzy subsets is reognised, appliations involving fuzzy logi beome programs involving higher-order funtions, and suh programs are most easily expressed in a funtional language sine this is one of the raisons d'etre of funtional programming. While we only gave a ouple of small examples of the appliations of fuzzy logi in a funtional language, Jan Skibinski at Numeri Quest In. [100℄ has developed larger Haskell programs whih utilise our original work on fuzzy logi. We then takled the problem of portability of funtional programs by produing a ompiler for the funtional language Ginger whih ompiles programs that run on the Java Virtual Mahine (JVM). While this gave us the desired portability, 221 sine many mahines have an implementation of the JVM, and also a way of using Java methods in funtional programs, sine we represented funtions as stati Java methods, performane was disappointing with programs running an order of magnitude slower than the equivalent ones using the Haskell interpreter Hugs. This was due to the expense of heap alloation in both the Java implementations tried when ompared to the alloator used by Hugs (written in C). This level of performane was noted by David Wakeling who produed a Haskell ompiler whih targetted the JVM. Our experiene with Ginger led us to develop an implementation for Aladin whih was designed to integrate many languages, funtional and imperative, into a pure, lazy funtional mahine. This rst required us to develop a reasonably eÆient denotational and operational semantis for Aladin, whih was used as the basis for the implementation. The implementation allowed us to dene primitives in Aladin itself, Ginger, Java and C/C++, and ombine them into Aladin programs. The purity and simpliity of Aladin has other appliations, too, and one we examined was the use of Aladin to partially evaluate programs. This was greatly aided by the requirement that the user speify the stritness of all Aladin funtions. The implementation used these stritnesses to nd out whih parts of a program it ould attempt to safely evaluate. The ability of the user to dene all primitives used by Aladin programs was also beneial where partial evaluation was onerned, sine their denitions ould be ne-tuned by the user to exploit partial evaluation to the full. This led to signiant performane benets, in partiular with the sample fuzzy system we enoded in a funtional style in Chapter 3 (a shower ontroller) we were rewarded by a speed up of an order of magnitude when we onverted the ode into Aladin and partially evaluated it. In summary, we have found a new appliation area for funtional programming, developed a system for making funtional programs more portable, developed a 222 mahine that integrates many languages into a funtional ontext, and used this mahine to inrease the eÆieny of funtional programs by means of partial evaluation. We have thus aomplished what we set out to do | what possibilities are there for future development of our work? 7.1 Integrating Aladin and Ginger We have already seen in Setion 6.6.7 how we an use the results of partially evaluating an Aladin program in a Ginger program, albeit in a manner whih requires a manual ut-and-paste by the user, and obtain signiant speed ups in running time by doing so. It is thus reasonable to ask if and how we an automate this proess. Integrating Aladin into Ginger would allow us to integrate partial evaluation, stritness signatures and the inlusion of primitives written in a number of languages into Ginger. The AAM would be used as a seondary evaluation mehanism, used by Ginger to partially evaluate programs and to apply non-Ginger primitives, while Ginger's evaluation mehanism would be used in all other ases. Aladin's stritness signatures ould also be used by Ginger to ompile more eÆient ode, for instane, by being able to use the E sheme in more plaes. 7.2 Ginger and Type Systems One of the downsides of Ginger being weakly-typed is that all overloading must be resolved at runtime. This means that even simple operations suh as arithmeti, omparison and logial operations must be done via method alls even though the JVM provides instrutions to perform these operations diretly on the stak. This approah would be faster and allow us to avoid having to onstrut some objets in the heap. It would be fairly simple to enode fuzzy logi operations using a 223 ombination of stak operations. Making Ginger a strongly typed language also has the orretness benets desribed in Setion 2.4. This leaves the question of how we resolve the overloading. Haskell's type lasses are ertainly powerful and exible, but perhaps too large and omplex for a small and simple language like Ginger, and Miranda's type system ignores overloading all together. A similar approah to that of ML (see Setion 2.4) would thus seem to be best for Ginger, sine all overloading is resolved at ompile time. As well as allowing us to do simple operations on the stak in some ases, this method would allow us to remove run-time resolution of overloading in the funtion denitions. As an example, onsider the operator - (subtration) whih is used to subtrat integer and real arguments. In Ginger this is ahieved by means of a single method, minus (see Setion 4.4). If we had the appliation of - to two integer arguments then we ould adapt the E ompilation sheme to produe more eÆient ode than before: E (- i1 i2 ) fs v = new Long dup E i1 fs v invokevirtual Long .getLong()J E i2 fs v invokevirtual Long .getLong()J lsub invokespeial Long /<init>(J)V This sheme ompiles the two arguments and extrats their long values. The subtration is then performed and the result put into a Long objet. This method not 224 only avoids run-time type heking and type asting, but also avoids a method all. A similar strategy an be used to subtrat two real (double) arguments. This is just an outline of how suh a sheme would preede | Peyton Jones [86℄ desribes the B sheme whih ompiles suh expressions muh more eÆiently. We may not be able to diretly apply the overloaded primitive in ode, for instane in the expression map ((-) x) xs the subtration is not applied until the elements of the list are required and thus must be done by a generi funtion appliation. However, if we adopt ML's system of overloading resolution we must speify whether the x and xs are integers or reals and the ompiler an use this information to ompile the ourrene of - here to a all to the orret funtion: either one that subtrat integers or one that subtrats reals. So, instead of the single method denition as in Setion 4.4 we need two: publi lass StritPrimitives extends Node { publi final stati Class TYPE = StritPrimitives.lass; publi stati Objet _minus_int(Objet lhs, Objet rhs) { return new Long(((Long) eval(lhs)).longValue() ((Long) eval(rhs)).longValue()); } publi final stati Objet _minus_int = Funtion.make(TYPE, "_minus", 2); publi stati Objet _minus_real(Objet lhs, Objet rhs) { return new Double(((Number) lhs).doubleValue() ((Number) rhs).doubleValue()); } publi final stati Objet _minus_real = 225 Funtion.make(TYPE, "_minus", 2); // ... } The ompiler then has to deide whether to ompile - to ode whih loads the value of the eld minus int or minus real. 7.3 Parallel Aladin The stritness of funtion arguments in Aladin is used to determine whih arguments an be safely evaluated, and those that we wish to leave unevaluated. This was used to great eet when we partially evaluated Aladin programs; this information an also be used to evaluate the various parts of an Aladin program in parallel. Parallelism has long interested researhers into funtional programming [33, 91℄. The purity of a funtional language means that evaluation order doesn't matter and hene it is safe to evaluate the various parts of a funtional program in parallel. This an be done in a variety of ways. The user an reate individual proesses whih will evaluate dierent parts of a program onurrently, with ommuniation being done by passing messages. This approah is used by Erlang [6℄. Alternatively, an attempt an be made to evaluate the various parts of the graph representing the program in parallel. This approah is taken by GAML [69℄ and the original version of Ginger [52℄, and parallel abstrat mahines suh as GRIP [20℄ and the h; Gi mahine [9℄. The latter approah raises the question of how to disover whih parts of a program an be safely evaluated in parallel. The program an rely on user annotations, as in GAML or Ginger; use stritness analysis and other suh methods to determine whih parts of a program an be safely evaluated in parallel [55℄; or even speulatively evaluate parts of a program in the hope that the results an be used later on in the evaluation sequene [71℄. 226 As Aladin already has stritness delarations, we an use these to determine whih parts of the program to evaluate in parallel. With regards to our partiular implementation, this would involve modifying the evalArgs method from Setion 5.3.3 so that eah of the strit arguments of a funtion are evaluated onurrently in separate threads in parallel | in Java this would involve reating a new thread for eah strit argument and evaluating eah strit argument in that thread. We would also have to adapt the implementation so that it performed the various housekeeping tasks required by a parallel evaluator, for example, making sure that two threads don't try to evaluate or update the same variable, loking the heap so that two threads don't try to write to it at the same time, and managing the various threads of exeution. However, stritness information by itself may not be enough. Kaser et. al. [55℄ showed that even if we know the strit arguments of a funtion we may not be able to extrat and exploit enough information for maximal parallelism, that is, when all available proessors have work to do. This is beause most lazy funtional languages only evaluate arguments down to Weak Head Normal Form (see Setion 2.2) whereas more parallelism an be ahieved if we know whih arguments an be evaluated all the way down to Normal Form as well as those that an be evaluated to WHNF. For instane, in the ase of a list, it is in WHNF one we have evaluated down to the rst list onstrutor (the one at the head of the list), but is not in NF until all elements of the list have been evaluated. Kaser alled this three-level stritness ee-stritness and used a form of stritness analysis to determine the ee-stritness of funtions. Aladin is not restrited to evaluating expressions to WHNF or NF, it depends on the stritness and denitions of the funtions used, but a funtion annot evaluate a program down to WHNF in one ontext and down to NF in another without the denition or stritness of the funtion being hanged. However, we ould alter Aladin's onept of stritness to 227 enable the user to speify that an argument should be evaluated all the way down to normal form and provide a suitable implementation using [55℄ as a basis. Another method of introduing parallelism into Aladin programs ould be to supply parallel primitives. For instane, we ould dene par of stritness l l ! l whih would evaluate its two arguments in parallel and return the result of applying the rst to the seond. For instane, par ((+) x) y would return the result of x + y but evaluate x and y in parallel (presuming + is strit in its rst argument so that x is evaluated). We ould also dene other parallel primitives, suh as parallel versions of map, fold | whih would be useful in fuzzy systems, for instane, to evaluate the rules of a fuzzy system in parallel | and filter. 7.4 Other Aladin Enhanements Our implementation of Aladin was developed as muh to show the orretness and appliability of the Aladin semantis as to have a mahine to develop and work with. This means we have mirrored the semantis in the implementation as muh as possible. Any future implementation may wish to use a more obsure interpretation of the semantis in order to implement ompiler enhanements, suh as those for Ginger desribed in Chapter 4. We mentioned above the possible use of Aladin to partially evaluate Ginger programs. Suh a use for Aladin is not restrited to Ginger | Aladin ould be used to partially evaluate programs written in any pure funtional language, provided suitable implementations of the primitives and funtions of that language were provided. Another possible area for investigation is the language used to implement Aladin. Java has the advantages of portability, ease of use, being able to be run in applets, and an in-built garbage olletor, but it has one large disadvantage: urrent 228 implementations are slow. It may be advantageous to write any future implementation in C or C++ and take advantage of the existene of fast ompilers for these languages. This approah would also make the task of importing C/C++ primitives into Aladin (the present method is a little involved) though would ompliate the importing of Ginger and Java primitives. A C/C++ implementation would be more involved, for instane, we would have to enode our own memory management sheme. The development of Aladin programs is sometimes ompliated by the fat that it is very weakly typed, whih an make debugging programs hard. To alleviate this problem we ould investigate adding a type system, probably based on the Hindley-Milner system desribed in Setion 2.4. This leaves us with the problem what to do with the type of data objets. Aladin allows the user to introdue new types at will, and in no partiularly systemati way. Some way would have to be found to allow the user to delare new types, inluding ontainer types, and some way of allowing the overloading of funtions using these types, possibly based on Haskell's type lasses or subtyping [19, 101℄. Alternatively, we ould use a single type to represent all Aladin data objets, though this would redue the strength of the typing system. 229 Bibliography [1℄ Aptronix Ltd. Fousing system. http://www.aptronix.om/fuzzynet/ applnote/fousing.htm, 1992. [2℄ Aptronix Ltd. Washing mahine. http://www.aptronix.om/fuzzynet/ applnote/wash.htm, 1992. [3℄ Aptronix Ltd. Fuzzy java. http://www.aptronix.om/fuzzynet/applnote/java. htm, 1996. [4℄ J.L. Armstrong, S.R. Daker, B.O.and Virding, and M.C. Williams. Implementing a funtional language for highly parallel real-time appliations. In Software Engineering for Teleommuniation Swithing Systems, Florene, Marh/April 1992. [5℄ J.L. Armstrong and S.R. Virding. Erlang | an experimental telephony programming language. In XIII International Swithing Symposium, Stokholm, May/Jun 1990. [6℄ J.L. Armstrong, S.R. Virding, and M.C. Williams. Conurrent Programming in Erlang. Prentie Hall, 1993. [7℄ Ken Arnold and James Gosling. The Java Programming Language. AddisonWesley, 2nd edition, 1998. 230 [8℄ L. Augustsson. A ompiler for lazy ML. In Proeedings of the ACM Symposium on Lisp and Funtional Programming, Austin, pages 218{27, August 1984. [9℄ Lennart Augustsson and Thomas Johnsson. Parallel graph redution with the h; Gi-mahine. In Funtional Programming & Computer Arhiteture, pages 202{214. ACM, 89. [10℄ Tom Axford and Mike Joy. Aladin: An Abstrat Mahine for Integrating Funtional and Proedural Programming. Journal of Programming Languages, 4:63{76, 1996. [11℄ Tom Axford and Mike Joy. Lazy Partial Evaluation in Aladin. Unpublished paper, September 1997. [12℄ John Bakus. Can programming be liberated from the von Neumann style? a funtional style and its algebra of programs. Communiations of the ACM, 21(8):613{641, August 1978. [13℄ H.P. Barendregt. The Lambda Calulus | Its Syntax and Semantis. NorthHolland Publishing Company, 1981. [14℄ Rihard Bird. Introdution to Funtional Programming using Haskell. Prentie Hall, 1998. [15℄ Rihard Bird and Philip Wadler. An Introdution to Funtional Programming. Prentie Hall, 1988. [16℄ James M. Boyle. Program reusability through program transformation. IEEE Transations on Software Engineering, 10(5):574{588, September 1984. [17℄ R.M. Burstall and John Darlington. A transformation system for developing reursive programs. Journal of the ACM, 24(1):33{67, January 1977. 231 [18℄ R.M. Burstall, D.B. MaQueen, and D.T. Sanella. Hope: an experimental appliative language. Tehnial Report CSR-62-80, Department of Computer Siene, Edinburgh University, May 1980. [19℄ Guiseppe Castagna, Giorgio Ghelli, and Guiseppe Longo. A alulus for overloaded funtions with subtyping. In ACM Conferene on LISP and Funtional Programming, San Franiso, 1992. [20℄ Chris Clak, Simon L. Peyton Jones, and Jon Salkild. EÆient parallel graph redution on GRIP. Tehnial Report RN/88/29, Department of Computer Siene, University College, London, July 1988. [21℄ Earl Cox. The Fuzzy Systems Handbook. AP Professional, 1994. [22℄ Antony J.T. Davie and David J. MNally. CASE | a lazy version of an SECD mahine with a at environment. Tehnial Report CS/90/19, Department of Computer Siene, University of St. Andrews, 1990. [23℄ Patrik Eklund and Frank Kwalonn. Neural fuzzy logi programming. IEEE Transations on Neural Networks, 3(5):815{818, September 1992. [24℄ Errisson. CSLab | Erlang. http://www.erisson.se:800/slab/erlang/, 1999. [25℄ J. Fairbairn and S. Wray. TIM: A simple, lazy abstrat mahine to exeute superombinators. In Funtional Programming Langauges and Computer Arhiteture, number 274 in LNCS, pages 34{45. Springer-Verlag, 1987. [26℄ Anthony J. Field and Peter G. Harrison. Funtional Programming. AddisonWesley, 1988. [27℄ Janos Fodor and Mar Roubens. Fuzzy Preferene Modelling and Multiriteria Deision Support. Kluwer Aademi Press, 1994. 232 [28℄ Franz In. Home page. http://www.franz.om, 1999. [29℄ Carsten K. Gomard and Neil D. Jones. A Partial Evaluator for the Untyped Lambda Calulus. Journal of Funtional Programming, 1(1):21{69, January 1991. [30℄ Rob Gordon. Essential JNI: Java Native Interfae. Prentie Hall, 1998. [31℄ F. G. Gustavson, W. Liniger, and R. Willoughby. Symboli Generation of an Optimal Crout Algorithm for Sparse Systems of Linear Equations. Journal of the ACM, 17(1):87{109, January 1970. [32℄ Cordelia Hall, Kevin Hammond, Simon Peyton Jones, and Philip Wadler. Type lasses in Haskell. ACM Transations on Programming Languages and Systems, 18(2):109{138, 1996. [33℄ Kevin Hammond. Parallel funtional programming: An introdution (invited paper). In PASCO'94: First International Symposium on Parallel Symboli Computation. World Sienti Publishing Company, Sept 94. [34℄ Chris Hankin. Lambda Caluli | A Guide for Computer Sientists. Oxford University Press, 1994. [35℄ John Hannan and Patrik Hiks. Higher-Order Arity Raising. In Proeedings of the ACM SIGPLAN International Conferene on Funtional Programming (ICFP '98), pages 27{38, January 1999. [36℄ Harlequin. Lisp produts. http://www.harlequin.om/produts/ads/lisp/, 1999. [37℄ Harlequin. ML works. http://www.harlequin.om/produts/ads/ml/, 1999. [38℄ Pieter Hendrik Hartel and Henk Muller. Funtional C. International Computer Siene Series. Addison-Wesley, 1997. 233 [39℄ Report on the programming language Haskell 98. http://haskell.systemsz.s. yale.edu/denition/, February 1999. [40℄ Peter Henderson. Funtional Programming | Appliation and Implementation. Prentie Hall, 1990. [41℄ John L. Hennessy and David A. Patterson. Computer Arhiteture: A Quantative Approah. Morgan Kaufmann, 2nd edition, 1996. [42℄ J. Roger Hindley and Jonathan P. Seldin. Introdution to Combinators and Lambda-Calulus. Cambridge University Press, 1986. [43℄ J.R. Hindley. The prinipal type sheme of an objet in ombinatory logi. Transations of the Amerian Mathematial Soiety, 146:29{60, Deember 1969. [44℄ J.M. Horvath. A fuzzy set model of learning disability. In Tamas Zetenyi, editor, Fuzzy Sets in Pyshology, number 56 in Advanes in Pyshology, pages 345{382. North-Holland, 1988. [45℄ Ian Hoyler. Funtional Programming with Miranda. Pitman, 1991. [46℄ John Hughes. Why funtional programming matters. In D.A. Turner, editor, Researh Topis in Funtional Programming, pages 17{42. Addison-Wesley, 1990. [47℄ Persimmon IT. The Persimmon MLJ Compiler. http://researh.persimmon. o.uk/mlj/. [48℄ T Johnsson. EÆient Compilation of Lazy Evaluation. In Proeedings of the 11th ACM Symposium of Priniples of Programming Languages, pages 58{69, 1984. 234 [49℄ T Johnsson. Lambda-lifting | transforming programs to reursive equations. In Conferene on Funtional Programming and Computer Arhiteture, Nany, number 201 in LNCS, pages 190{203. Springer-Verlag, 1985. [50℄ Mark Jones. Hugs arhive. http://www.se.ogi.edu/mpj/hugsar/. [51℄ Neil D. Jones, Carsten L. Gomard, and Peter Sestoft. Partial Evaluation and Automati Program Generation. Prentie Hall, 1993. [52℄ M. Joy and T. H. Axford. A parallel graph redution mahine. In H. Kuhen and R. Loogen, editors, Proeedings of the 4th Int. Workshop on the Parallel Implementation of Funtional Languages. Fahgruppe Informatik, 1992. [53℄ Mike Joy. Ginger | A Simple Funtional Language. Tehnial Report CSRR-235, Department of Computer Siene, University of Warwik, Coventry, UK, 1992. [54℄ Mike Joy and Steve Matthews. Some experienes of teahing funtional programming. International Journal of Mathematial Eduation in Siene and Tehnology, 25(2):165{172, 1994. [55℄ Owen Kaser, C.R. Ramakrishnan, I.V. Ramakrishnan, and R.C. Sekar. EQUALS |- a fast parallel implementation of a lazy language. Journal of Funtional Programming, 7(2):183{217, Marh 1997. [56℄ Arnold Kaufmann. Introdution to the Theory of Fuzzy Subsets, volume 1. Aademi Press, 1975. [57℄ Brian Kernighan and Dennis Rithie. The C Programming Language. Prentie Hall, 2nd edition, 1988. [58℄ Bart Kosko. Fuzzy Thinking. Flamingo, 1994. 235 [59℄ Erwin Kreyszig. Advaned Engineering Mathematis. Wiley, 6th edition, 1988. [60℄ P.J. Landin. The Mehanial Evaluation of Expressions. BCS Computing Journal, 6(4):308{320, January 1964. [61℄ J. Launhbury. A natural semantis for lazy evaluation. In Priniples of Programming Languages, Charleston, 1993. [62℄ John Launhbury. Projetion Fatorisations in Partial Evaluation. Distinguished Dissertations in Computer Siene. Cambridge University Press, 1991. [63℄ Daan Leijen and Erik Meijer. HaskellSript. http://haskell.systemsz.s.yale. edu/haskellsript, 1999. [64℄ J.W. Lloyd. Pratial advantages of delarative programming. In Joint Conferene on Delarative Programming, GULP-PRODE '94, 1994. [65℄ Logi Programming Assoiates Ltd. FLINT toolkit. http://www.lpa.o.uk/ n.html, 1997. [66℄ Jan Lukasiewiz. On the notion of possibility/On three-valued logi. In Storrs MCall, editor, Polish Logi 1920{1939, pages 15{18. Oxford University Press, 1967. Appeared originally under the titles `O pojeiu mo_zliwosi' and `O logie trojwartosiowej' in Ruh Filozozny 5 (1920), pp 169{171. [67℄ Jan Lukasiewiz. Philosophial remarks on many-valued systems of propositional logi. In Storrs MCall, editor, Polish Logi 1920{1939, pages 40{65. Oxford University Press, 1967. Appeared originally under the title `Philosophishe Bemerkungen zu mehrwertigen Systemen des Aussagenkalkuls' in Comptes rendus des seanes de la Soiete des Sienes et des Lettres de Varsovie, Cl. iii, 23 (1930), pp 51{77. 236 [68℄ E.H. Mamdani and S. Assilian. An experiment in linguisti synthesis with a fuzzy logi ontroller. International Journal of Man-Mahine Studies, pages 1{13, 1975. [69℄ Lu Maranget. GAML: a parallel implementation of lazy ML. In J. Hughes, editor, Funtional Programming & Computer Arhiteture, pages 102{123. Springer-Verlag, August 1991. [70℄ T.P. Martin, J.F. Balwdin, and B.W. Pilsworth. The implementation of FProlog | a fuzzy Prolog interpreter. Fuzzy Sets and Systems, 23:119{129, 1987. [71℄ Jim S. Mattson Jr. Performane of parallel shedulers for distributed graph redution. In Nijmegen Workshop on the Implementation of Funtional Languages, 1993. [72℄ John MCarthy. Reursive funtions of symboli expressions and their omputation by mahine. Communiations of the ACM, 8(3):184{195, Marh 1960. [73℄ John MCarthy, Paul W. Abrahams, Daniel J. Edwards, Timothy P. Hart, and Mihael I. Levin. LISP 1.5 Programmer's Manual. MIT Press, 2nd edition, 1965. [74℄ Gary Meehan. Compiling funtional programs to Java byte-ode. Researh Report CS-RR-334, Department of Computer Siene, University of Warwik, Coventry, UK, September 1997. [75℄ Gary Meehan. Fuzzy funtional programming. Researh Report CS-RR-322, Department of Computer Siene, University of Warwik, Coventry, UK, April 1997. [76℄ Gary Meehan. The Aladin Abstrat Mahine. Researh Report CS-RR-355, 237 Department of Computer Siene, University of Warwik, Coventry, UK, Deember 1998. [77℄ Gary Meehan and Mike Joy. Animated Fuzzy Logi. Journal of Funtional Programming, 8(5):503{525, September 1998. [78℄ Gary Meehan and Mike Joy. Compiling Lazy Funtional Programs to Java Byte-ode. Software | Pratie and Experiene, 1999. [79℄ Jon Meyer and Troy Downing. Java Virtual Mahine. O'Reilly, 1997. [80℄ Robin Milner. A theory of type polymorphism in programming. Journal of Computer and Siene Systems, 17(3):348{375, 1979. [81℄ Robin Milner, Mads Tofte, Robert Harper, and David MaQueen. The Denition of Standard ML (Revised). MIT Press, 1997. [82℄ C.V. Negoita. Expert Systems and Fuzzy Systems. The Benjamin/Cummings Publishing Company, 1985. [83℄ NRC-CNC Institute for Information Tehnology. Fuzzy CLIPS. http://ai.iit. nr.a/fuzzy/fuzzy.html, 1996. [84℄ Martin Odersky and Philip Wadler. Pizza into Java: Translating theory into pratie. In Proeedings 24th ACM symposium on Priniples of Programming Languages, Paris, Frane, 1997. [85℄ The Haskell 1.4 report. http://haskell.org/report/, April 1997. [86℄ Simon L. Peyton Jones. The Implementation of Funtional Programming Languages. Prentie Hall, 1987. 238 [87℄ Simon L. Peyton Jones. Implementing lazy funtional languages on stok hardware: the Spineless Tagless G-mahine. Journal of Funtional Programming, 2(2):127{202, April 1992. [88℄ Simon L. Peyton Jones, Mark P. Jones, and Erik Meijer. Type lasses: exploring the design spae. In Proeedings of the Haskell Workshop, Amsterdam, June 1997. [89℄ Simon L. Peyton Jones and David Lester. Implementing Funtional Languages | A Tutorial. Prentie Hall, 1992. [90℄ Simon L. PeytonJones, Thomas Nordin, and Reid Alastair. Green ard: a foreign language interfae for Haskell. In Haskell Workshop, Amsterdam, June 1997. [91℄ Rinus Plasmeijer and Marko van Eekelen. Funtional Programming and Parallel Graph Rewriting. Addison-Wesley, 1993. [92℄ Rinus Plasmeijer and Marko van Eekelen. Conurrent Clean v1.1 language report. Tehnial report, University of Nijmegen, Marh 1996. [93℄ Chris Reade. Elements of Funtional Programming. Addison-Wesley, 1989. [94℄ Claus Reinke. Towards a Haskell/Java onnetion. In Implementation of Funtional Languages, the 10th International Workshop, IFL'98, London, UK, number 1595 in Leture Notes in Computer Siene, pages 200{215. SpringerVerlag, September 1998. [95℄ Sergei A. Romanenko. Arity Raiser and its Use in Program Speialization. In N. Jones, editor, Proeedings of the European Symposiom on Programming, Copenhagen, Denmark, number 432 in Leture Notes in Computer Siene, pages 351{360. Springer-Verlag, May 1990. 239 [96℄ Timothy J. Ross. Fuzzy Logi With Engineering Appliations. MGraw-Hill, 1995. [97℄ Colin Runiman and David Wakeling. Appliations of Funtional Programming. UCL Press, 1995. [98℄ Stuart Russel and Peter Norvig. Artiial Intelligene | A Modern Approah. Prentie Hall, 1995. [99℄ Peter Sestoft. Deriving a lazy abstrat mahine. Journal of Funtional Programming, 7(3):231{264, May 1997. [100℄ Jan Skibinski. Fuzzy osillator. http://www.numeri-quest.om/haskell/ osillator/Fuzzy osillator.html, November 1998. [101℄ Georey S. Smith. Prinipal type shemes for funtional programs with overloading and subtyping. Siene of Computer Programming, 23:197{226, 1994. [102℄ Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, 3rd edition, 1997. [103℄ Sun Mirosystems, In. Javap | The Java Class File Disassembler. http: //java.sun.om/produts/jdk/1.1/dos/tooldos/solaris/javap.html. [104℄ Kazuo Tanaka. An Introdution to Fuzzy Logi for Pratial Appliations. Springer-Verlag, 1997. First published in Japanese, 1991. [105℄ Transvirtual Tehnologies. What is Kae Open VM? http://www. transvirtual.om/kae.html. [106℄ Simon Thompson. Miranda: The Craft of Funtional Programming. AddisonWesley, 1995. 240 [107℄ Simon Thompson. Haskell: The Craft of Funtional Programming. AddisonWesley, 1996. [108℄ Thomas W. Torgerson. Visual Basi Professional 3.0 Programming. Wiley, 1994. [109℄ D.A. Turner. A new implementation tehnique for appliative languages. Software | Pratie and Experiene, 9:31{49, 1979. [110℄ D.A. Turner. Miranda | a non-strit funtional language with polymorphi types. In Jouannaud, editor, Conferene on Funtional Programming Languages and Computer Arhiteture, Nany, number 201 in LNCS, pages 1{16. Springer-Verlag, 1985. [111℄ D.A. Turner. An overview of Miranda. In D.A. Turner, editor, Researh Topis in Funtional Programming, pages 1{16. Addison-Wesley, 1990. [112℄ Jerey D. Ulman. Elements of ML Programming. Prentie Hall, 1994. [113℄ Philip Wadler. Introdution to Orwell. Tehnial report, Programming Researh Group, University of Oxford, 1985. [114℄ Philip Wadler. Comprehending monads. In ACM Conferene on Lisp and Funtional Programming, pages 61{78. ACM, ACM Conferenes, June 1990. [115℄ Philip Wadler. The essene of funtional programming. In ACM Symposium of Programming Languages, January 1992. [116℄ Philip Wadler. An angry half-dozen. ACM SIGPLAN Noties, 33(2):25{30, February 1998. [117℄ Philip Wadler. Why no one uses funtional languages. ACM SIGPLAN Noties, 33(8):23{27, August 1998. 241 [118℄ David Wakeling. Mobile Haskell: Compiling lazy funtional languages for the Java Virtual Mahine. Unpublished paper. [119℄ David Wakeling. VSD: A Haskell to Java Virtual Mahine ode ompiler. In Proeedings of the 9th International Workshop on the Implementation of Funtional Languages, 1997. [120℄ Li-Win Wang. Adaptive Fuzzy Systems and Control | Design and Stability Analysis. Prentie Hall, 1994. [121℄ Robert Wilensky. Common LISPraft. W. W. Norton & Company, 1986. [122℄ Barry Wilkinson. Computer Arhiteture: Design and Performane. Prentie Hall, 1991. [123℄ Jun Yan, Mihael Ryan, and James Power. Using Fuzzy Logi. Prentie Hall, 1994. [124℄ L. A. Zadeh. Fuzzy sets. Information and Control, 8:338{353, 1965. [125℄ L. A. Zadeh. Outline of a new approah to the analysis of omplex systems and deision proesses. IEEE Transations on Systems, Men and Cybernetis, 3:28{44, 1973. [126℄ H.-J Zimmermann. Fuzzy Set Theory | and its Appliations. Kluwer Aademi Publishers, 1991. 242