The tilt programming language
by Jim Davies, Jennifer Mankoff
and Terry Shikano
TABLE OF CONTENTS
Introduction
- what a genetic algorithm is
- what genetic programming is
- crossover and mutation
- how crossover and mutation are normally done
- how tilt can improve on this
- design goals
- language requirements
Comparisons to Other Languages
User's Manual
- the BNF
- the crossover! command
- the replace! command
- the choose command; using probabilities
- random generation of code
- defining pools
Conclusions
Reference Manual
Default Scheme BNF
References
INTRODUCTION
What a genetic algorithm is.
A genetic algorithm is a process that adapts a reproducing population
according to some notion of fitness. Evolution of any sort requires the
following:
* a population of entities that reproduce
* diversity of traits
* an environment that selects certain members of the
population to reproduce over others because of difference in traits.
What genetic programming is.
Genetic programming is the use of genetic algorithms to create
programs with a certain function. This example demonstrates the approach:
A user wants to make a program that can sort a list quickly. He
randomly generates 500 programs, and tests them all according to how
well they sort (based on accuracy and speed). He tests each one of
them 10 times and gives the average score to that program. Each
program receives a score. The programs are ranked according to score,
and the bottom 250 are deleted. This is selection. The remaining 250
are copied, resulting in a population of 500 once again. This is
reproduction. The new 250 are not copied perfectly, but are changed
slightly. Some have pieces from more than one of the originals. Some
are changed slightly in random ways. Then, the entire operation is
done to the new population. This pattern repeats. Over the course of
the process, the average sorting quality has slowly increaces.
In the above example, the 500 programs are the population. Different
sized populations can be used, but 500 is sufficient for many simple
problems (Koza 1993). A generation is the time between any two
selections. In the example, the top scoring half are selected, but
one could select the top two, only those that reach achieve a certain
score, or even an amount of reproduction in proportion to the fitness.
What deterimes the quality of how well the program sorts is called the
fitness function. When a program is copied, the copy is called the
child and the copied is called the parent. Crossover is when a child
has parts from more than one parent. Crossover is analogous to sexual
reproduction in nature. Mutation is when a program is changed slightly
in a random way. The algorithm can run for a specified amount of time
or it can terminate when the average fitness reaches a certain point.
Crossover and mutation.
In general, most adaptation comes as a result of crossover. Mutation
plays a smaller role, but can be important for removing the population
from local maxima (Back 1996). The tilt programming language focuses on
the mutation and crossover aspect of genetic programming: it provides
the programmer an easy way to describe how mutations and crossovers
can happen.
How crossover and mutation are normally done.
Each Lisp S expression is a tree.
(list (+ 1 2) (- (+ 2 3) 1))
list
/ \
+ -
/\ | \
1 2 + 1
/\
2 3
and each node gets assigned a number
1 2 3 4 5 6 7 8 9
(list (+ 1 2) (- (+ 2 3) 1))
One of the nodes is selected to be the cutoff point.
Say 5.
Then we have program2:
1 2 3 4 5
(first (rest (cons 1 2)))
and we get a cutoff point there, like 4. Now we clip the first at 5
and replace program 2 with it at 4:
(first (rest (cons (- (+ 2 3) 1) 2)))
This is basically how crossover is done with programs.
By default tilt specifies that there is a 90% chance that interior
nodes will be chosen, and a 10% chance that leaves will be chosen, as
swapping leaves is more like normal mutation, which is has not been
found to be helpful.
How tilt can improve on this.
Tilt's replace command can act as a kind of halfway between crossover
and mutation: it retrieves tree segments of a certain type and can
determine what type gets put in. In this way the programmer can
choose how drastically she wants the programs to be changed.
Tilt also allows multiple crossover and mutation kinds, and can assign
probabilities to them. So the tilt code can specify that mutations
occur rarely and that crossover happens more often.
Design goals.
The original design goals were uniformity, redundancy, orthogonality,
and locality/linearity. The original tilt concept was broader in
scope; as the project evolved its purpose became more specific and
defined. At first tilt was meant to be a language with which one would
write programs that could generate output with a certain syntax,
defined at multiple levels. The target problem was fiction
generation. The idea was that a tilt program could know the basic
elements of a story at a high level-- the word "tilt" referred to the
main conflict in a story.
Other applications of tilt that were planned were the generation of
test cases for software and mutation for genetic programming. We
found the fiction writing aspect too ambitious, and ended up focusing
on the mutation and crossover of scheme programs for genetic
algorithms as an interesting problem of appropriate scope.
LANGUAGE REQUIREMENTS
Tilt programs modify scheme code, so it is very handy to be able to
use the scheme commands in the tilt program. This allows tilt
programmers to do complex, sophisticated genetic programming.
Every expression in tilt should return some value. This is tilt's
functional aspect. If it does not return a value or a list of values,
it returns #t on success and nil or an error on error. This is
important because when the program has to terminate, it needs to spit
out a valid program. By requiring each expression, tilt can keep
track of what the latest successful change was.
Tilt should be of use to those doing genetic programming, and allow
all of the common functions normally used in genetic programming. The
exception is the fitness evaluation.
Tilt allows someone without probability training to make use of
randomness in the tilt program. A programmer may want to specify the
probability distribution of items in a pool, if not specified or
specified in a novice-like manner, tilt normlizes the distribution and
generate more easily-managable distribution..
COMPARISONS TO OTHER LANGUAGES
TXL (ftp://ftp.qucis.queensu.ca/pub/txl/) is another tree
transformation lanuage which performs source to source transformations
using a set of transformation rules. A TXL program defines its own
context free grammar according to which the input can be broken into
parts, and rules are constrained to preserve grammatical structure.
The basic unit in the grammar is the define statement. Each define
statement gives all of the alternative forms for one nonterminal type
corresponding roughly to the set of productions for a signal
nonterminal in a BNF grammar.
e.g.
define expression
[number]
| [expression] + [number]
| [expresison] - [number]
end define
Once a TXL program has parsed the input, it transformins the input
into a parse tree for the desired output using functions and rules. A
function has at least a pattern and a replacement:
e.g.
function name
replace [type]
pattern
by
replacement
end function
where type is the nonterminal type of parse tree to be transformed,
and pattern is a pattern which the functions's argument tree must
match in order for the function to transform it. It only transforms
the match it has found first.
For example, the following function changes 5 to 6
function fiveToSix
replace [number]
5
by 6
end function
Rules are similar to functions except that the former searches the
scope tree for matches to its pattern and replaces every such match.
e.g.
rule everyFiveToSix
replace [number]
5
by 6
end rule
fiveToSix transforms the input values [1 5 7 9 2 5 4] into [1 6 7 9 2 5 4]
while everyFiveToSix generates [1 6 7 9 2 6 4].
Both TXL and NewYacc can modify input program in a systematic manner,
whereas tilt modifies input program in more random fashion. Thus
neither TXL nor NewYacc are ideal for genetic programming, where
random diversity is key. Tilt was created to provide an easy way to do
this.
USER’S MANUAL
Tilt is used to specify how crossover and mutation occurs in
evolutionary programming in scheme. To use a tilt program, a higher
level program (the meta-program that's running the genetic algorithm)
calls the tilt program and passes it a file. The meta-program decides
which members of the population to send to this file, and there can be
any number of them. The names given to the functions therein need to
correspond to the names used in the tilt program. For the rest of
this paper, PROGRAM1 and PROGRAM2 will refer to two hypothetical
functions in the input file for a tilt program. The tilt program can
refer to any functions defined in this input file. When the tilt
program is run, it actually changes the functions in the input file
(not a copy of them). The meta-program can then reintroduce them into
the population.
The BNF.
Tilt can parse scheme code and make replacements and changes
of many kinds. In order to do this parsing, a version of the BNF for
scheme is included in the tilt interpreter. If the programmer wants to
use a different BNF, the file can be specified with the load bnf
command. If it is used, it should usually be the first command in the
tilt program.
(load bnf filename)
The crossover! command.
Now let's look at a simple program.
;; example code 1
(crossover! (PROGRAM1)
(PROGRAM2))
In this program, there is a random crossover from PROGRAM1 to
PROGRAM2. Some tree section in PROGRAM1 clobbers a tree in
PROGRAM2. The crossover occurs as described in the
introduction. crossover! has an exclamation mark after it to remind
the programmer that the command actually is destructive to a file.
The replace! command.
Note that this is all that is required for the tilt program, and this
much is sufficient for genetic programming to take place. Now let's
look at the replace! command. Here's another short program:
;; example code 2
(replace! (choose 1 of (get <procedure-call> PROGRAM1))
with
(get <procedure-call> PROGRAM2))
We will break down this command into sections. Note the piece that
says
(get <procedure-call> PROGRAM1)
This returns a pool of all instances of procedure calls in PROGRAM1.
A pool is a set of elements, each of which has a probability
associated with it. Tilt knows what a <procedure-call> is based on
the scheme BNF. So when we do
(choose 1 of (get <procedure-call> PROGRAM1))
it randomly returns one thing from the pool. Specifically, it returns
a node of that one thing chosen. A node is a data structure that
stores where a pool element came from. Essentially, it's a pointer. If
more than one thing is chosen, as in
(choose 10 of <pool>)
then choose returns a pool.
So far, nothing in example 2 has changed anything in the target file
containing PROGRAM1 and PROGRAM2. The replace! command is where the
file changing happens. The node returned by
(choose 1 of (get <procedure-call> PROGRAM1))
gets clobbered by what gets returned by the expression following the
with keyword, which in example 2 is
(get <procedure-call> PROGRAM2))
which is a pool. replace! randomly chooses one element of the pool.
So what example 2 does is it takes a procedure call from PROGRAM1
and replaces it with a procedure call from PROGRAM2.
The choose command; using probabilities.
Let's say that you wanted to replace! half the time and crossover the
other half of the time. You can use the choose command to choose a
procedure as well. See example 3.
;; example code 3
(choose 1 of
( (begin
(crossover! (PROGRAM1) (PROGRAM2))
(crossover! (PROGRAM1) (PROGRAM2))
(crossover! (PROGRAM1) (PROGRAM2))))
( (replace! (choose 1 of (get <procedure-call> PROGRAM1))
with
(get <procedure-call> PROGRAM2)))
)
The begin command is not specific to tilt, but a scheme command that
tilt inherits. It runs in sequence the expressions that follow. So
in this case there are three seperate crossovers occuring, one after
the other. They are descructive, so there is a chance that the first
crossover will be undone by the second! The seperate crossover! and
replace! commands should be familiar. The `choose 1 of' command will
randomly take one of the following expressions, either
( (begin
(crossover! (PROGRAM1) (PROGRAM2))
(crossover! (PROGRAM1) (PROGRAM2))
(crossover! (PROGRAM1) (PROGRAM2))))
or
( (replace! (choose 1 of (get <procedure-call> PROGRAM1))
with
(get <procedure-call> PROGRAM2)))
Choose assumes that the probability of all expressions are equal
unless otherwise specified. But it may be that the programmer wants
crossover to happen much more often than the replace. In this case
the programmer is free to assign probabilities to the choices. For
example:
;; example code 4
(choose 1 of ( .6 (crossover! (PROGRAM1) (PROGRAM2)) )
( .4 (replace! (choose 1 of (get <procedure-call> PROGRAM1))
with
(get <procedure-call> PROGRAM2)) ))
The probabilities (.6 and .4) are optional, but if they are there
choose will do the first 60% of the time and the replace 40% of the
time.
The programmer does not need to make sure that the numbers add
up to one, however. Any numbers can be put into the probability
slots, and tilt calculates the appropriate chances. This is how it
works: If none of the choices have associated probabilities, then all
have an equal chance of being chosen. If one or more have
probabilities, then those choices without probabilities are given the
average probability of the choices that do have assigned
probabilities. When all the choices have numbers, the list of numbers
is normalized to one, making each number a true probability.
Example code 5 is like the last example 4, but now the probabilities
attatched add up to 200. The two weights are normalized by tilt to
.25 and .75 respectively.
;; example code 5
(choose 1 of
( 50 ((crossover! (PROGRAM1) (PROGRAM2))
( 150 ((replace! (choose 1 of (get <procedure-call> PROGRAM1))
with
(get <procedure-call> PROGRAM2))))
)
Random generation of code.
Let's imagine that you want to replace a derived expression in
PROGRAM1 with a random derived expression. Tilt can do
this because it has the BNF. The command generate is used to do this:
(generate 1 <derived expression>))
What tilt does is it makes pools for all of the nonterminals in the
scheme BNF, based on all of the examples of the nonterminals appearing
in PROGRAM1, PROGRAM2, etc. So when you ask tilt to generate 1
derived experssion, it will return an expression from the pool. The
pool is not affected by this. If you try to generate 10 things from a
pool containing only one element, then you will get 1 element from the
pool, and the remaining 9 will be randomly generated from the
BNF. Note that this is the crucial difference between generate and
choose: choose does not guarantee unique returns. If you ask choose to
get 10 elements from a 1 element pool, it will return the same element
10 times. If there is nothing in the pool at all, choose returns nil.
One call to generate will return unique elements. If generate uses up
everything in the pool then tilt uses the BNF to actually randomly
generate an example of that nonterminal. Let's say that tilt gets the
following command, when there are no digits in the <digit> pool.
(generate 1 <digit>)
This command would go to the scheme BNF, look up digit, and randomly take a member of the set described. Some nonterminal definitions
specify a undetermined length, such as <string>:
<string> --> " <string element>* "
In this case the string could potentially be infinitely long. To avoid
this problem, generate assumes a depth of 10. This means that any
repeat operator (*, +, etc.) can be used a maximum of 10 times by
default. This can be overridden with
(generate 1 <string> to depth 500)
Defining pools.
In addition to the pools automatically created by tilt based on the
nonterminals, the programmer can specify further pools using the
define command. Things are added to the pool with the add command.
;; example code 6
(define foo '())
(add .4 (generate 1 <derived expression>) foo)
(add .4 (generate 1 <cond clause>) foo)
The first line in example code 6 creates a new pool called foo that is
empty. The second line adds something to foo. What it adds is a
derived expression from the derived expression pool. It is added with
an associated .4 probability. The third line adds a cond clause to
foo. Now, when the foo pool is called it will return that derived
expression (the same one every time) or the cond clause (the same
one every time.) New things can be added to foo, with or without
probabilities.
If a probability is not specified, then all the probabilities in the
pool are recalculated as in the choose command described above. Let's
extend example code 6.
(replace! (choose 1 of (get <derived expression> PROGRAM1))
with foo)
This replace command notes a derived expression in PROGRAM1 and
replaces it with something from foo. Tilt knows to take something
from foo when it follows a with command. More code:
(define clo (get <derived expression> PROGRAM1))
(define bar '())
(add .1 (generate 1 <derived expression>) bar)
(add .2 (choose 1 of (get <derived expression> PROGRAM1)) bar)
(add .3 clo bar)
(replace! (choose 1 of (get <cond clause> PROGRAM2))
with
bar)
clo is a pool of derived expressions from PROGRAM1. Note that it is a
pool, not an individual element, because we used the get command
rather than the generate command. When you ask for a bar, there is a
weight .1 that you will get some derived expression from the pool.
There is a .2 weight that you will get a derived expression
specifically from PROGRAM1 (of course, all of PROGRAM1's derived
expressions are already in the pool). The weight is .3 that it will
return some <derived expression> from the clo pool. When you ask for
something from a pool x and it refers you to another pool y, tilt asks
it for one element of that.
CONCLUSIONS
1. Uniformity
To promote ease of learning and reading the syntax
should be consistent.
Tilt inherits the uniform syntax of scheme: The first atom in every
expression is a function, for example. Some of the commands in tilt
are rather complicated, and in those cases we attempted to make them
as uniform as possible.
Probability weights can be assigned to elements in pools, and can be
assigned to different expressions in a choose command. In both
instances the probability preceeds the element, but in the choose
expression, expression probabilities are enclosed in parentheses.
(add .1 (generate 1 <derived expression>) foo)
(choose 1 of
(50 (begin
(crossover! (PROGRAM1) (PROGRAM2))
(crossover! (PROGRAM1) (PROGRAM2))
(crossover! (PROGRAM1) (PROGRAM2))))
(150 (begin (replace (choose 1 of (get <procedure-call> PROGRAM1))
with
(get <procedure-call> PROGRAM2))))
)
2. Redundancy
To promote ease of reading and learning, there
will be multiple ways to do the same thing.
Redundancy is most helpful in languages with many functions-- it can
be difficult to remember how to do something, so having many ways to
do it makes programming easier. Tilt turned out to be a small
language, thus reducing the need for our original goal of
redundancy.
One example of redundancy is in the way that the probabilities are
specified. The numbers do not have to add up to 1; since the numbers
are normalized only the relative size of the numbers makes a difference.
3. Orthogonality
To the extent possible we will try to make
commands work together.
Certain commands are overloaded to facilitate orthogonality. The
choose command can be used to pick a procedure or to pick a member out
of a pool. The replace with command can take a pool or a node.
4. locality/linearity
It should be easy to read and tell what kinds
of things the program made will generate.
Although the nature of crossover and mutation is random generation,
there is a level at which the tilt code reader can see what will
happen in the program. The nonterminals are used directly from the
BNF of scheme, and extensions made by the programmer can be named
meaningfully, so that the code reader can see what kinds of things
replace others.
REFERENCE MANUAL
Tilt is a procedural language with a functional-language flavor. It
runs on top of a functional language, scheme. Thus, it requires the
scheme run-time environment. It can be run either as an interactive
process or as an executable. The control regime of tilt is a von
Neuman, fetch-execute style.
The second method is useful when a programmer needs to deal with the
entire genetic programming process. That is, a top-level program runs
tilt to modify the input programs and another fitness function to
evluate the tilt output for the next iteration. For example, a C
program with multiple threads can run several tilt programs at a time
to handle one cycle of evolution and other threads evaluating tilt
output. The C program returns 1) when the fitness function finds the
optimal program or 2) when the maximum number of life-cycles is
reached.
Tilt tries to keep the input program in a valid state. Thus, when a
run-time error occurs, it outputs the state of the last successful
change (commit point) and aborts the execution returning an error.
Formal syntax and semantics.
The lexical issues are the same as that of scheme, and are answered in
the R5RS scheme documentation.
Tilt is very similar to Scheme, and we have extended a subset of the
Scheme BNF (included in this document) in our defenition of Tilt. In the
following grammar, nonterminals in lowercase refer to nonterminals
from the Scheme BNF. Nonterminals in uppercase are specific to tilt.
keywords:
add
combine
crossover!
generate
choose of
node
node internal leaf
load bnf
populate from
get
replace! with
replace! using
pool
function
A valid tilt program is described by an extension to the schme
BNF. Specifically, the nonterminal <expression> is extended to include
one additional rule:
<expression> --> <variable>
| <literal>
| <procedure call>
| <lambda expression>
| <conditional>
| <assignment>
| <derived expression>
| <macro use>
| <macro block>
| <EXPRESSION>
When nonterminals occur more than once in the same rule, they are
assumed to map to potentially different code (ie, <N> won't
necessarily be the same number when it appears repeatedly in the same
rule).
<NODE> --> <token>
<CLOSURE> --> <token>
<POOL> --> <token>
/* here the outer "<" and ">" are literals */
<NONTERMINAL> --> <<token>>
<N> --> <ureal>
<M> --> <ureal>
<PROBABILITY> --> <ureal>
<VALUE> --> <datum>
| pool <NAME>
| function <CLOSURE>
<EXPRESSION> --> <POOL>
| (add [<PROBABILITY>] <NODE> <POOL>)
| (add [<PROBABILITY>] function <CLOSURE> <POOL>)
| (add [<PROBABILITY>] pool <POOL> <POOL>)
| (combine <POOL>+)
| (crossover! (<datum> [:<NODE>])
(<datum> [:<NODE>]))
| (generate <N> <NONTERMINAL> [to depth <M>])
| (choose <N> of <POOL>)
| (choose <N> of ([<PROBABILITY>] <expression>)*)
| (node [internal <N> leaf <M>] <expression>)
| (node [<NONTERMINAL> <N>]+ <expression>)
| (load bnf <string>)
| (populate from <datum>)
| (get <NONTERMINAL> <expression>)
| (replace! <NODE> with <POOL>)
| (replace! <NODE> using <NONTERMINAL>)
| (replace! <NODE> with <NODE>)
ONT>
semantics:
--------------------
<NONTERMINAL> Any nonterminal in the scheme BNF
(see scheme BNF).
--------------------
<NODE> Refers to a tilt datatype (essentially, it's a scheme pointer)
--------------------
<CLOSURE> Refers to a tilt closure which can be run. This must
return a valid scheme <datum> (*note* scheme, not tilt)
--------------------
<POOL> Refers to a tilt datatype which contains <PROBABILITY>,
<VALUE> pairs.
'() is considered to be an empty pool.
<PROBABILITY> is the probability that a given <VALUE> will be
chosen from a pool (eg by the choose keyword). If only some of the
values have assigned probabilities, the un-assigned values will be
given a probability equal to the average of the assigned
probabilities. Then all probabilities are normalized to one.
<VALUE> is either a scheme <datum>, another pool, or a tilt function.
Every <POOL> data structure keeps track of where values of type
<datum> came from (ie, what points at them), if they are part of some
other structure. For example, if a tilt program put the value (or 3
#f) in a pool, and that value came from a list of the form (if (or 3
#f) 3 5), then tilt would need to store (cdr (if (or 3 #f) 3 5))
somewhere.
--------------------
Unless specified, the functions describe below return #t on success, otherwise
an error.
(load bnf <string>)
<string> should be a file name
This loads in a BNF from a file. At that time, an empty pool is created for
each non-terminal in the BNF (these are populated by calls to get). A default
BNF is provided at the beginning of a tile program. This can be changed using
load bnf, although it should be changed to be subset of scheme (e.g. new rules
should describe valid scheme programs). load bnf returns an error if there is
a parsing error, and #t otherwise.
--------------------
(crossover! (<datum> [:<NODE>])
(<datum> [:<NODE>]))
will pick the two nodes randomly if not specified (with a 90% chance of
picking internal nodes, 10% leaves). It will then do the standard genetic
algorithm crossover described in the introduction. crossover! returns nil
if there is an error, #t otherwise. It changes both <datum> permanently.
--------------------
(generate <N> <NONTERMINAL> [to depth <M>])
uses other nonterminal's pools and the BNF to randomly generate <N>
values for the <NONTERMINAL> pool (all of type function, since they
have no <NODE>s, as in add). In the case of recursive rules, it will
max out a depth 10, or <M> if to depth <M> is specified. This will
copy <VALUE>s from other pools if possible. It returns the generated pool, or
if <N> is 1, the generated <VALUE> (a scheme <datum>). Upon error, it returns
nil.
--------------------
(choose <N> of <POOL>)
will pick <N> values from the <POOL>, with either equal probability,
or the probability specified by the elements of the <POOL>.
Probabilities in the pool are automatically normalized to values
between 0 and 1 by choose. choose returns a <POOL> of the chosen
<VALUE>s, with no probabilities assigned, or a <VALUE>'s <NODE> if <N>
is 1. If the <VALUE> has no node, the <VALUE> itself is returned. If
a chosen <VALUE> is a function <CLOSURE> the result of calling
<CLOSURE> is used as a <VALUE> (of type <datum>). If a <VALUE> is a
pool <NAME>, the result of calling (choose 1 of <NAME>) is used. It returns
nil if there is an error.
(choose <N> of ([<PROBABILITY>] <expression>)*)
will run <N> of the <expression>s, chosen randomly or according to the
specified <PROBABILITY>s as described above.
--------------------
(node [internal <N> leaf <M>] <expression>)
(node [<NONTERMINAL> <N>]+ <expression>)
returns a <NODE> (pointer into) the expression chosen either randomly, or with
probability according to whether it's internal, a leaf, or matches the given
set of <NONTERMINALS> as specified. It returns an error if there is an error.
--------------------
(populate from <datum>)
<datum> is some scheme program (not a tilt program) which will be
parsed, and the pools of all non-terminals matched during parsing will
be populated with the associated code. It returns a list of all the populated
pools on success, otherwise nil.
--------------------
(get <NONTERMINAL> <expression>)
returns a <POOL> of all the occurrences of <NONTERMINAL> in
<expression>. Each time get is called, it automatically add
anything new if finds to the pool associated with <NONTERMINAL> (see
load bnf)
--------------------
(replace! <NODE> with <POOL>)
(replace! <NODE> with <NODE>)
The <NODE> pointer is changed to point at the result of a copy of the
value associated with (choose 1 <POOL>) or <NODE>. This returns nil on
error, #t otherwise.
(replace! <NODE> using <NONTERMINAL>)
Replace will choose a <VALUE> (and associated <NODE>) from the
<NONTERMINAL> pool, and then it will look up the nonterminals in the
rule for <NONTERMINAL> (e.g. <formals> and <body> for <lambda
expression>). If the <expression> pointed at by <NODE> has any such
nonterminals, they will be used in order, until they are used up, to
replace the corresponding terminals in the chosen <VALUE>. The result
will be copied into the <expression> pointed at by <NODE>.
e.g.
(replace! (or #t 1 2) using <derived expression>)
The <derived expression> pool contains
(and (foo) bar) <- randomly chosen
(or (baz) quux)
since we have (and <test>*) as our rule, and #t is a <test>, we get
(and #t 1).
--------------------
(add [<PROBABILITY>] <NODE> <POOL>)
adds <NODE> and it's associated <VALUE> to <POOL> with probability
<PROBABILITY> if specified.
(add [<PROBABILITY>] function <CLOSURE> <POOL>)
(add [<PROBABILITY>] pool <POOL> <POOL>)
adds the appropriate function or pool to <POOL> with probability
<PROBABILITY> if specified.
(add [<PROBABILITY>] <datum> <POOL>)
calls (add function (lambda () <datum>) <POOL>) with probability
<PROBABILITY> if specified.
--------------------
(combine <POOL>+)
basically appends two <POOL>s together and returns the result.
SCHEME BNF
/**
This BNF is provided by default to the tilt programmer.
All spaces in the grammar are for legibility. Case is insignificant;
for example, `#x1A' and `#X1a' are equivalent. <empty> stands for the
empty string.
The following extensions to BNF are used to make the description
more concise: <thing>* means zero or more occurrences of <thing>;
and <thing>+ means at least one <thing>.
<Intertoken space> may occur on either side of any token, but not
within a token.
Tokens which require implicit termination (identifiers, numbers,
characters, and dot) may be terminated by any <delimiter>, but not
necessarily by anything else.
**/
<token> --> <identifier> | <boolean> | <number>
| <character> | <string>
| ( | ) | #( | ' | ` | , | ,@ | .
<delimiter> --> <whitespace> | ( | ) | " | ;
<whitespace> --> <space or newline>
<comment> --> ; <all subsequent characters up to a
line break>
<atmosphere> --> <whitespace> | <comment>
<intertoken space> --> <atmosphere>*
<identifier> --> <initial> <subsequent>*
| <peculiar identifier>
<initial> --> <letter> | <special initial>
<letter> --> a | b | c | ... | z
<special initial> --> ! | $ | % | & | * | / | : | < | =
| > | ? | ^ | _ | ~
<subsequent> --> <initial> | <digit>
| <special subsequent>
<digit> --> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<special subsequent> --> + | - | . | @
<peculiar identifier> --> + | - | ...
<syntactic keyword> --> <expression keyword>
| else | => | define
<expression keyword> --> quote | lambda | if
| set! | begin | cond | and | or | case
| let | let* | letrec | do
<boolean> --> #t | #f
<character> --> #\ <any character>
| #\ <character name>
<character name> --> space | newline
<string> --> " <string element>* "
<string element> --> <any character other than " or \>
| \" | \\
<number> --> <prefix> <complex>
<complex> --> <real> | <real> @ <real>
| <real> + <ureal> i | <real> - <ureal> i
| <real> + i | <real> - i
| + <ureal> i | - <ureal> i | + i | - i
<real> --> <sign> <ureal>
<ureal> --> <uinteger>
| <uinteger> / <uinteger>
| <decimal>
<decimal> --> <uinteger> <suffix>
| . <digit>+ #* <suffix>
| <digit>+ . <digit>* #* <suffix>
| <digit>+ #+ . #* <suffix>
<uinteger> --> <digit>+ #*
<prefix> --> <radix> <exactness>
| <exactness> <radix>
<suffix> --> <empty>
| <exponent marker> <sign> <digit>+
<exponent marker> --> e | s | f | d | l
<sign> --> <empty> | + | -
<exactness> --> <empty> | #i | #e
<radix> --> <empty> | #d
/**
<datum> is what the read procedure (section see section Input)
successfully parses. Note that any string that parses as an
<expression> will also parse as a <datum>.
**/
<datum> --> <simple datum> | <compound datum>
<simple datum> --> <boolean> | <number>
| <character> | <string> | <symbol>
<symbol> --> <identifier>
<compound datum> --> <list> | <vector>
<list> --> (<datum>*) | (<datum>+ . <datum>)
| <abbreviation>
<abbreviation> --> <abbrev prefix> <datum>
<abbrev prefix> --> ' | ` | , | ,@
<vector> --> #(<datum>*)
<expression> --> <variable>
| <literal>
| <procedure call>
| <lambda expression>
| <conditional>
| <assignment>
| <derived expression>
| <macro use>
| <macro block>
<literal> --> <quotation> | <self-evaluating>
<self-evaluating> --> <boolean> | <number>
| <character> | <string>
<quotation> --> '<datum> | (quote <datum>)
<procedure call> --> (<operator> <operand>*)
<operator> --> <expression>
<operand> --> <expression>
<lambda expression> --> (lambda <formals> <body>)
<formals> --> (<variable>*) | <variable>
| (<variable>+ . <variable>)
<body> --> <definition>* <sequence>
<sequence> --> <command>* <expression>
<command> --> <expression>
<conditional> --> (if <test> <consequent> <alternate>)
<test> --> <expression>
<consequent> --> <expression>
<alternate> --> <expression> | <empty>
<assignment> --> (set! <variable> <expression>)
<derived expression> --> <cond> | <case> | <boolfunc> | <let> |
<begin> | <loop>
<cond> --> (cond <cond clause>+) |
(cond <cond clause>* (else <sequence>))
<case> --> (case <expression> <case clause>+) |
(case <expression>
<case clause>*
(else <sequence>))
<boolfunc> --> <and> | <or>
<and> --> (and <test>*)
<or> --> (or <test>*)
<let> --> (let (<binding spec>*) <body>)
| (let <variable> (<binding spec>*) <body>)
| (let* (<binding spec>*) <body>)
| (letrec (<binding spec>*) <body>)
<begin> --> (begin <sequence>)
<loop> --> (do (<iteration spec>*)
(<test> <do result>)
<command>*)
<cond clause> --> (<test> <sequence>)
| (<test>)
| (<test> => <recipient>)
<recipient> --> <expression>
<case clause> --> ((<datum>*) <sequence>)
<binding spec> --> (<variable> <expression>)
<iteration spec> --> (<variable> <init> <step>)
| (<variable> <init>)
<init> --> <expression>
<step> --> <expression>
<do result> --> <sequence> | <empty>
/**
Programs and definitions
**/
<program> --> <command or definition>*
<command or definition> --> <command>
| <definition>
| (begin <command or definition>+)
<definition> --> (define <variable> <expression>)
| (define (<variable> <def formals>) <body>)
| (begin <definition>*)
<def formals> --> <variable>*
| <variable>* . <variable>
REFERENCES
Back, T. (1996). Evolutionary Algorithms in Theory and Practice. New
York, Oxford University Press.
Koza, J. R. (1993). Genetic Programming. Cambridge, MA. MIT
Press/Bradford Books.
Purtilo, J. J. & Callahan, J. R. (1989). Parse-tree annotations.
Communications of the ACM. 32(12) 1467-77.