Lci Manual: 1 Syntax
Lci Manual: 1 Syntax
Kostas Chatzikokolakis
12 March 2006
This manual describes the use of lci, which is an advanced interpreter for
the -calculus. This program was first developed by Kostas Chatzikokolakis
as an assignment for the Theory of Programming Languages course in the
Department of Informatics of University of Athens. Later it became an open
source project licenced under GPL, in order to be used and improved by the
open source community. Its main purpose is to compute the normal form of pure
-calculus terms. Moreover it supports various extensions which are, however,
implemented through the pure calculus.
Syntax
lci supports the syntax of -calculus enriched with integers, identifiers and
operators. The supported language is described by the following grammar:
Term
Oper
|
|
|
|
var
( Term Oper Term )
( var . Term )
num
id
op
Basic function
lci is an interactive program. When executed it displays the lci> prompt and
waits for user input. The most simple usage is to enter a -term and press return.
The program performs all and reductions and generates the terms normal
form. The result is printed in a readable way, that is only the necessary
parentheses are displayed, church numerals are displayed as integers and lists
using the standard Prolog notation. However the way terms are displayed can
be modified, for example the following command
Set showpar on
causes all parentheses to be displayed.
Terms are reduced using the normal order evaluation strategy, that is the
leftmost or reduction is performed first. This strategy guarantees that terms
normal form will be computed in finite time, if it exists. However if a term has no
normal form then execution will not terminate. After the execution the program
displays the number of reductions that were performed and the CPU usage time.
Integers
lci supports integers by encoding them as church numerals during parsing. Integer n will be converted to
cn f.x.f n (x)
So, actually, it is just a syntactic sugar. All operations are implemented in
pure -calculus and can be used through identifiers and operators. Although the
use of calculus for ordinary operations is sleek, it has has a serious performace
drawback. The complexity of an operation is far from constant, for example the
power-of operator (**) requires an exponential number of reductions. Moreover,
due to stack limitaions, the greatest suported integer is 9999.
2
Identifiers
Identifiers are used to represent big -terms by defining aliases. For example term
x.x can be assigned to alias I so that the term I y is equivalent to (x.x) y.
Aliases must be defined in a file that is read by the program. The syntax of this
file is described by the following grammar.
CmdList
Cmd
Cmd ; CmdList
id = Term
? Term
(eg. /usr/local/share/lci/.lcirc)
in that order. All files found are executed, if none is found then a warning is
printed. This file contains definitions for many basic functions and operators
(integer operations, for expample) and is similar to Haskells prelude.hs.
Identifiers are replaced by the corresponding terms during evaluation and
not during parsing. Thus the order of the definitions is not significant. If an
alias is not defined an error message is displayed during evaluation. If no alias
contains itself (directly or indirectly) then aliases are just a syntactic sugar, for
if we replace all of them we get valid -terms. However lci supports curcular
references of aliases as a way to implement recursion. This idea is described in
the following section.
Recursion
5.1
(1)
(2)
If the first case M may contain an abstraction and in the second any redex.
If we replace an alias only when necessary, we can finish the evaluation without
performing all the replacements. For example the abstraction x.y does not use
its argument, so the following reduction
(x.y) M y
eliminates M without computing it.
lci handles identifiers using this techique. That is it replaces an identifier
with its corresponding term only when necessary and only once at a time. So
even if a term is recursive, it is possible to find a normal form if recursion is interrupted by some condition. .lcirc contains many recursive definitions, mainly
concerning list manipulation functions.
This technique is not compatible with the pure calculus, as it uses invalid terms. However the following must be noted: suppose that in term M = x.M x
we need to replace M only twice until we reach its normal form. This means
that in term x.(x.(x.M y) y) y no replacement will be performed. M . So we
can substitute M with an arbitary valid -term N and we get
M = x.(x.(x.N y) y) y
M behaves exactly like M but it is a valid term. Of course in a different situation
more replacements could be needed, producing a different M . So M could be
considered as a term generator that produces an appropriate M each time.
4
5.2
Operators
Left-associative operator
Right-associative operator
Non-associative operator
Evaluation strategies
An evaluation strategy determines the choice of a redex when there are more
than one in a term. lci uses the normal order strategy, which selects terms
1
lci behaves similarly to Prolog, that is lower precedence operators are applied first.
leftmost redex. The main advantage of this strategy is that it always leads to
terms normal form, if it exists. However it has a serious drawback which is the
multiple computation of terms. For example in the following series of reductions
(f.f (f y))((x.x)(x.x))
(x.x)(x.x)((x.x)(x.x)y)
(x.x)((x.x)(x.x)y)
(x.x)(x.x)y
(x.x)y
y
the term (x.x)(x.x) was computed twice. An alternative strategy is call-byvalue, in which all arguments are computed before applied to a function. This
method can avoid multiple computation.
(f.f (f y))((x.x)(x.x))
(f.f (f y))(x.x)
(x.x)((x.x)y)
(x.x)y
y
This strategy, however, does not guarantee that normal form will be found. There
are also some other strategies like call-by-need that is used in some functional
languages like Haskell.
lci does not implement any such technique, but there has been an effort
to overcome this problem using a special operator . This operator does not
behave like ordinary operators. The expression M N denotes the application
of M to N which, however, uses call-by-value. So, if M N is the leftmost
redex then all reductions of N are performed before the application. Thus the
term (f.f (f y)) ((x.x)(x.x)) will be reduced according to the second of
the previous ways. Operator has the same precedence and associativity as the
application operator, so it can be easily combined with it.
This operator, however, should be used with caution since the normal form of
(x.y) ((x.x x)(x.x x)) will never be found, yet it exists. In file queens.lci
there is an implementation of the well-known n-queens problem, using experimentally this operator. Without the use of the operator the program is impossible
to terminate, even for 3 queens where the combinations that must be examined
are very few. This is due to the fact that terms are extremely complex and cause
a lot of recomputation. Using the operator and testing in an Athlon 1800, all
solutions for the 3 queens where found in 0.3 seconds, for 4 queens in 4.4 and for 5
in 190. For 6 queens after many hours of testing the program did not terminate.
This is not strange, though, since Haskell (with the same implementation and
using lazy-evaluation and constant time arithmetic) needs 1799705 reductions for
the 8 queens and extremely much time for n > 12.
7
Tracing
lci supports evaluation tracing. This function is enabled using the following
command
Set trace on
or pressing Ctrl-C during the evaluation of a term. When tracing is enabled,
the current term is displayed after each reduction and the program waits for
user input. Available commands are step, continue and abort. The first one
performs the next reduction, the second continues the reductions without tracing
and the last one stops the evaluation. An alternative function is to display all
intermediate terms without interrupting the evaluation. This can be enabled
using the following command
Set showexec on
System commands
Function
Removes circular references from aliases
using a fixed point combinator Y
DefOp op prec ass Declares an operator with the given precedence and associativity.
ShowAlias [name]
Displays the definition of the given alias,
or a lists of all aliases.
Print term
Displays a term. Useful to check parsing.
Consult file
Reads and processes the given file.
Set option on/off Changes one of the following parameters.
trace
Evaluation tracing
showexec
Display all intermediate terms
showpar
Display all terms parentheses
greeklambda Display instead of .
readable
Readable display of integers and lists
Help
Displays a help message.
Quit
Terminates the program.
Table 1: System commands
10
Examples