define
And so to define
. define
is another type of
side effect. It differs from set!
in that it is not an error
if the symbol does not exist, and it differs also in that the
binding is always installed in the current environment.
Therefore executing define
at the top level prompt
will install the binding in the global environment.
Code that manipulates environments is best defined in the environment
package PScm::Env, and that's what we do. The additional
method in PScm::Env is called, unsurprisingly,
Define()
and it's very simple.
131 sub Define { 132 my ($self, $symbol, $value) = @_; 133 134 $self->{bindings}{ $symbol->value } = $value; 135 return $symbol; 136 }
It takes a symbol and a value (already evaluated) as arguments. On Line 134 it directly adds the binding from the symbol to the value, reguardless of any previous value, and on Line 135 it returns the symbol being defined, to give the print system something sensible to print.
define
Special FormNow we need to follow the usual procedure to add another special
form to the language: we subclass PScm::SpecialForm and
give our new class an Apply()
method. In this case the new class
is called PScm::SpecialForm::Define.
167 package PScm::SpecialForm::Define; 168 169 use base qw(PScm::SpecialForm); 170 171 sub Apply { 172 my ($self, $form, $env) = @_; 173 my ($symbol, $expr) = $form->value; 174 $env->Define($symbol, $expr->Eval($env)); 175 } 176 177 1;
All it does is on
Line 173
it extracts the symbol and the
expression from the argument $form
then on
Line 174
it calls the
Define()
environment method described above with the
symbol and evaluated expression (value) as argument.
There is one more change to make. In order for define
to be effective from one expression to another, it no longer makes
sense to create a fresh environment for each expression to be evaluated
in, as ReadEvalPrint()
has done so far, because that would
eradicate the effect of any define
performed by a prior
expression. The solution is of course trivial, we create the initial
environment outside of the read-eval-print loop itself, and pass
it to each top-level Eval()
:
31 sub ReadEvalPrint { 32 my ($infh, $outfh) = @_; 33 34 $outfh ||= new FileHandle(">-"); 35 my $reader = new PScm::Read($infh); 36 my $initial_env = new PScm::Env( 37 let => new PScm::SpecialForm::Let(), 38 '*' => new PScm::Primitive::Multiply(), 39 '-' => new PScm::Primitive::Subtract(), 40 if => new PScm::SpecialForm::If(), 41 lambda => new PScm::SpecialForm::Lambda(), 42 list => new PScm::Primitive::List(), 43 car => new PScm::Primitive::Car(), 44 cdr => new PScm::Primitive::Cdr(), 45 cons => new PScm::Primitive::Cons(), 46 letrec => new PScm::SpecialForm::LetRec(), 47 'let*' => new PScm::SpecialForm::LetStar(), 48 eval => new PScm::SpecialForm::Eval(), 49 macro => new PScm::SpecialForm::Macro(), 50 quote => new PScm::SpecialForm::Quote(), 51 'set!' => new PScm::SpecialForm::Set(), 52 begin => new PScm::SpecialForm::Begin(), 53 define => new PScm::SpecialForm::Define(), 54 ); 55 56 while (defined(my $expr = $reader->Read)) { 57 my $result = $expr->Eval($initial_env); 58 $result->Print($outfh); 59 } 60 }
Now we can actually write some of the earliest examples from this book in the language at hand.
> (define factorial > (lambda (x) > (if x > (* x (factorial (- x 1))) > 1))) factorial > (factorial 4) 24
The factorial
function was chosen to demonstrate that closures
created by define
can call themselves recursively. After all,
the environment they capture must, by virtue of how define
operates, contain a binding for the closure itself.
define
can be used for other things too. Because of the
simple semantics of the PScheme language, define
is perfectly
suited for creating aliases to existing functions. For instance if a programmer doesn't
like the rather obscure names for the functions car
and
cdr
, they can provide aliases:
> (define first car) first > (define rest cdr) rest > (first (list 1 2 3 4)) 1 > (rest (list 1 2 3 4)) (2 3 4)
These are completely equivalent to the original functions except in name.
The primitive definitions are bound to the new symbols first
and rest
in the top level environment
exactly as they are still bound to their original symbols.
t/PScm_Define.t
1 use strict; 2 use warnings; 3 use Test::More; 4 use lib 't/lib'; 5 use PScm::Test tests => 3; 6 7 BEGIN { use_ok('PScm') } 8 9 eval_ok(<<EOF, <<EOX, 'global definition'); 10 (define square 11 (lambda (x) (* x x))) 12 (square 4) 13 EOF 14 square 15 16 16 EOX 17 18 eval_ok(<<EOF, <<EOX, 'local definition'); 19 (define times2 20 (lambda (x) 21 (begin 22 (define h 23 (lambda (k) 24 (- k (- k)))) 25 (h x)))) 26 (times2 5) 27 EOF 28 times2 29 10 30 EOX 31 32 # vim: ft=perl
Tests for define
can be seen in
Listing 21.
The first test demonstrates global definition, and also that closures
bound by define
are naturally capable of recursion since
define
assigns them in the current environment.
The second test shows that define
can be used in any
environmental context, even in the body of a closure, to create
new bindings. This second test is quite interesting because it
demonstrates a function that creates a “helper” function
(h
) that is only visible to the containing
times2
closure.
Full source code for this version of the interpreter is available athttp://billhails.net/Book/releases/PScm-0.0.8.tgz