Chapter 11. 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.

11.1. Environment Changes

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.

11.2. The define Special Form

Now 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.

11.3. Persistant Environments

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.

11.4. Tests

Listing 21. 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 at
http://billhails.net/Book/releases/PScm-0.0.8.tgz
Last updated Sun Mar 14 10:43:08 2010 UST