Epsilon extends Delta by adding user-defined functions and procedures.
Epsilon programmers create functions and procedures using expression and command abstractions, respectively:
<SpecialForm> ::= <ExpAbstraction> | <CmmdAbstraction> | etc.
More specifically, a function is created from an expression (called the body or implementation) and a list of parameters by using the fun operator. A procedure is created from a command (also called the body or implementation) and a list of parameters by using the proc operator:
<ExpAbstraction> ::= (fun
(<Symbol>*) <Expression>)
<CmmdAbstraction> ::= (proc (<Symbol>*) <Command>)
A procedure call is a new type of command:
<Command> ::= <ProcCall> | etc.
In Epsilon a function call is simply a list of one or more expressions surrounded by parenthesis, while a procedure call is a list of one or more expressions surrounded by curly braces:
<ProcCall> ::= {<Expression>+}
<FunCall> ::= (<Expression>+)
The first expression in the list is the operator (this is usually just a symbol). The subsequent expressions are the operands.
Here's how an Epsilon programmer defines a variable, a function, and a procedure:
-> [define w (var 10)]
done
-> [define square (fun (x) (* x x))]
done
-> [define incW (proc (y) {assign w (+ (val w) y)})]
done
-> w
var<10>
-> square
<function>
-> incW
<procedure>
Now lets call square and incW:
-> (square (val w))
100
-> {incW 2}
ok
-> w
var<12>
Of course calls can be nested:
-> {incW (square (+ 2 3))}
ok
-> w
var<37>
Epsilon permits anonymous function and procedure calls:
-> ((fun (x) (* 2 x)) 3)
6
Functions (and procedures) can be the return values of other functions:
-> [define incX (fun (x) (fun (y) (+ x y)))]
done
-> [define inc3 (incX 3)]
done
-> inc3
<function>
-> (inc3 7)
10
Epsilon employs the static scope rule. This means that the values of non-locals (i.e., symbols appearing in the body that are neither parameters nor symbols defined in a nested block) are determined by the function's defining environment, not its calling environment. Consider the following example:
-> [define a 100]
done
-> [deine addA
(let [[define a 50] [define b 30]]
(fun (c) (+ a b c)))]
done
-> (let [[define b 500]] (addA 20))
100
The body of the addA function contains four symbols: +, a, b, and c. In the environment in which addA is defined a = 50, b = 30, + = the primitive add function, and c is a parameter. However, in the environment in which addA is called a = 100 and b = 500. Nevertheless, the value returned is (+ 50 30 20) = 100.
Functions and Procedures are Abstracts. An abstract (also called a closure) contains an abstract apply method, a protected list of parameters, and a protected reference to its defining environment:
Here's a sketch of the Abstract class:
abstract class Abstract {
protected Environment defEnv;
protected List<Symbol> params;
abstract Value apply(List<Value>
args, Environment env) throw AppError;
}
Notice that I've made a few changes from Alpha. Abstract is now an abstract class rather than an interface. This is because I added the two fields defEnv and params. Of course you will need to provide constructors to initialize these fields.
Here's a sketch of how the Function class might be implemented:
class Function extends Abstract {
Expression body; // = Command in
Procedure
Value apply(List<Value> args, Environment
env) throw AppError {
1. tempEnv = new Environment with ce
= env and de = defEnv
2. put paramsi = argsi
into tempEnv
3. return body.eval(tempEnv)
}
}
Here's a sketch of how ExpAbstraction might be implemented:
class ExpAbstraction extends SpecialForm
{
List<Symbol> params;
Expression body;
static boolean
isNext(List<String> tokens) { ... }
static Phrase parse(List<String>
tokens) throws AppError {
1. ExpAbstration result = new ExpAbstraction();
2. iteratively
call Symbol.parse to init result.params
3. call
Expression.parse() to init result.body
4. return result
}
Value eval(Environment
env) throws AppError {
return new Function(params, env,
body);
}
}
Notice that an instance of ExpAbstraction looks just like an instance of Function. Both contain a parameter list and a body. The similarity is analogous to the similarity between NumberLiteral and NumberValue.