Sign In/My Account | View Cart  
advertisement


Listen Print

Exegesis 4
by Damian Conway | Pages: 1, 2, 3, 4, 5, 6

Editor's note: this document is out of date and remains here for historic interest. See Synopsis 4 for the current design information.

Read or Die

Having prompted for the next expression that the calculator will evaluate:


    print "$i> ";

we read in the expression and check for an EOF (which will cause the <> operator to return undef, in which case we escape the infinite loop):


    my $expr = <> err last;

Err...err???

In Apocalypse 3, Larry introduced the // operator, which is like a || that tests its left operand for definedness rather than truth.

What he didn't mention (but which you probably guessed) was that there is also the low-precedence version of //. Its name is err:


          Operation         High Precedence       Low Precedence
          
         INCLUSIVE OR             ||                     or
         EXCLUSIVE OR             ~~                    xor
          DEFINED OR              //                    err

But why call it err?

Well, the // operator looks like a skewed version of ||, so the low-precedence version should probably be a skewed version of or. We can't skew it visually (even Larry thought that using italics would be going a bit far), so we skew it phonetically instead: or -> err.

err also has the two handy mnemonic connotations:

  • That we're handling an error marker (which a returned undef usually is)

  • That we're voicing a surprised double-take after something unexpected (which a returned undef often is).

Besides all that, it just seems to work well. That is, something like this:


    my $value = compute_value(@args)
        err die "Was expecting a defined value";

reads quite naturally in English (whether you think of err as an abbreviation of "on error...", or as a synonym for "oops...").

Note that err is a binary operator, just like or, and xor, so there's no particular need to start it on a new line:


    my $value = compute_value(@args) err die "Was expecting a defined value";

In our example program, the undef returned by the <> operator at end-of-file is our signal to jump out of the main loop. To accomplish that we simply append err last to the input statement:


    my $expr = <> err last;

Note that an or last wouldn't work here, as both the empty string and the string "0" are valid (i.e. non-terminating) inputs to the calculator.

Just Do It

Then it's just a matter of calling Calc::calc, passing it the iteration number and the expression:


    Calc::calc(i=>$i, expr=>$expr)

Note that we used named arguments, so passing them in the wrong order didn't matter.

We then interpolate the result back into the output string using the $(...) scalar interpolator:


    print "$i> $( Calc::calc(i=>$i, expr=>$expr) )\n";

We could even simplify that a little further, by taking advatage of the fact that subroutine calls interpolate directly into strings in Perl 6, provided we use the & prefix:


    print "$i> &Calc::calc(i=>$i, expr=>$expr)\n";

Either way, that's it: we're done.

Summing Up

In terms of control structures, Perl 6:

  • provides far more support for exceptions and exception handling,
  • cleans up and extends the for loop syntax in several ways,
  • unifies the notions of blocks and closures and makes them interchangeable,
  • provides hooks for attaching various kinds of automatic handlers to a block/closure,
  • re-factors the concept of a switch statement into two far more general ideas: marking a value/variable as the current topic, and then doing "smart matching" against that topic.

These extensions and cleanups offer us far more power and control, and -- amazingly -- in most cases require far less syntax. For example, here's (almost) the same program, written in Perl 5:


    package Err::BadData; 
    use base 'Exception';   # which you'd have to write yourself
    
    package NoData;         # not lexical
    use base 'Exception';
    sub warn { die @_ }
    
    package Calc;
    
    my %var;
    
    sub get_data  {
        my $data = shift;
        if ($data =~ /^\d+$/)       { return $var{""} = $data }
        elsif ($data eq 'previous') { return defined $var{""}
                                                 ? $var{""}
                                                 : die NoData->new() 
                                    }
        elsif ($var{$data})         { return $var{""} = $var{$data} }
        else                        { die Err::BadData->new(
                                             {msg=>"Don't understand $data"}
                                          )
                                     }
    }
    
    sub calc {
        my %data = @_;
        my ($i, $expr) = @data{'i', 'expr'};
        my %operator = (
            '*'  => sub { $_[0] * $_[1] },
            '/'  => sub { $_[0] / $_[1] },
            '~'  => sub { ($_[0] + $_[1]) / 2 },
        );
        
        my @stack;
        my $toknum = 1;
        LOOP: for my $token (split /\s+/, $expr) {
            defined eval {
                TRY: if ($operator{$token}) {
                    my @args = splice @stack, -2;
                    push @stack, $operator{$token}->(@args);
                    last TRY;
                }
                last LOOP if $token eq '.' || $token eq ';' || $token eq '=';

                push @stack, get_data($token);
            } || do {
                if ($@->isa(Err::Reportable))     { warn $@; }
                if ($@->isa(Err::BadData))        { $@->{at} = $i; die $@ }
                elsif ($@->isa(NoData))           { push @stack, 0     }
                elsif ($@ =~ /division by zero/)  { push @stack, ~0 }
            }
        }
        continue { $toknum++ }
        die Err::BadData->new(msg=>"Too many operands") if @stack > 1;
        $var{'$'.$i} = $stack[-1] . ' but true';
        return 0+pop(@stack);
    }
    
    package main;
    
    for (my $i=1; 1; $i++) {
        print "$i> ";
        defined( my $expr = <> ) or last;
        print "$i> ${\Calc::calc(i=>$i, expr=>$expr)}\n";
    }

Hmmmmmmm. I know which version I'd rather maintain.