Stories
Slash Boxes
Comments

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

pmichaud (6013)

pmichaud
  (email not shown publicly)
http://www.pmichaud.com/

Patrick Michaud is the pumpking for the Rakudo Perl 6 compiler. He holds a Ph.D. in Computer Science and was formerly a Professor of Computer Science at Texas A&M University-Corpus Christi. He is currently a software developer and consultant focused on open source development and applications, including Perl, PmWiki, and Linux.

Journal of pmichaud (6013)

Monday March 02, 2009
03:41 PM

a(nother) Reverse Polish Notation Calculator

[ #38580 ]

In a Reverse Polish Notation Calculator, fREW Schmidt takes the RPN calculator example from Higher Order Perl and converts it over to Perl 6.

Very cool.

But as usual, I visually look at the Perl 6 version compared to the Perl 5 version and think "Can't we do better?" In this case, we can. Here's my version of the same code:

    my %op_dispatch_table = {
        '+'    => { .push(.pop + .pop) },
        '-'    => { my $s = .pop; .push(.pop - $s) },
        '*'    => { .push(.pop * .pop) },
        '/'    => { my $s = .pop; .push(.pop / $s) },
        'sqrt' => { .push(.pop.sqrt) },
    };
 
    sub evaluate (%odt, $expr) {
        my @stack;
        my @tokens = $expr.split(/\s+/);
        for @tokens {
            when /\d+/     { @stack.push($_); }
            when ?%odt{$_} { %odt{$_}(@stack); }
            default        { die "Unrecognized token '$_'; aborting"; }
        }
        @stack.pop;
    }
 
    say "Result: { evaluate(%op_dispatch_table, @*ARGS[0]) }";

The result:

$ ./perl6 calc.pl '5 6 +'
Result: 11

The major changes I made to fREW's code:

1. Convert the explicit subs into simple closures, assuming that the stack is the implicit topic.
2. Use 'when' statements instead of if/elsif/else in the body of the 'evaluate' sub.

And yes, as frew points out -- when we get the 'R' metaoperator in place we can get rid of the 'my $s = ...' parts of subtraction and division. Maybe I'll implement meta-R now... :-)

Thanks fREW for the very interesting example!

Pm

Update: I went ahead and implemented the R metaoperator. For those who aren't familiar with meta-R, it reverses the order of the operands to an operator. So, (5 R- 4) is the same as (4 - 5), and (2 R/ 10) is the same as (10 / 2). With this change, the RPN calculator becomes:

    my %op_dispatch_table = {
        '+'    => { .push(.pop + .pop)  },
        '-'    => { .push(.pop R- .pop) },
        '*'    => { .push(.pop * .pop)  },
        '/'    => { .push(.pop R/ .pop) },
        'sqrt' => { .push(.pop.sqrt)    },
    };
 
    sub evaluate (%odt, $expr) {
        my @stack;
        my @tokens = $expr.split(/\s+/);
        for @tokens {
            when /\d+/     { @stack.push($_); }
            when ?%odt{$_} { %odt{$_}(@stack); }
            default        { die "Unrecognized token '$_'; aborting"; }
        }
        @stack.pop;
    }
 
    say "Result: { evaluate(%op_dispatch_table, @*ARGS[0]) }";

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • That's much more compact! I'll try to absorb these changes so that next time I make a post about perl6 it will be closer to The Essence of Perl 6. :-)
    --
    --fREW
    http://blog.afoolishmanifesto.com
  • I don't know how to do this, but I am sure Patrick or Jonathan can do this quickly... In the code above, there is still repetition: all entries in %op_dispatch_table are nearly identical, differing only depending on the arity. We could replace that with something generic which is passed a sub, it pops as many elements as the arity of the passed sub and pushes back the value of applying the sub to the reverse of this list. I was unable to do this myself because I could not get from the name of the sub to t
    • Yes, it could potentially be made slightly shorter... but I like the straightforward clarity of the above. An advantage of the above approach is that it would work for almost any stack pattern -- including things that don't fit the ".push( fn(.pop) )" model. For example, to add a 'say' operator that displays the top value of the stack (without removing it):

          'say' => { .[*-1].say }

      I can also imagine functions that rotate or swap the top N operands, or the like. Trying to wedge those into

      • I have one version here: http://use.perl.org/~amahabal/journal/38583 [perl.org] Thanks to your suggestion, I added 'say' and 'rotate3'. I will try my original idea next, for which I need to go from a name (such as 'double') to a callable object (&double). Not sure yet what I'd do with multis and such, but I will see how far I get.