Document 13806938

advertisement
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
Download