RPN Calculator in Perl 6

| 1 Comment | No TrackBacks

<!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } H3 { margin-bottom: 0.21cm } TD P { margin-bottom: 0cm } -->

As the wikipedia says, RPN (Revered Polish Notation), is a kind of calculator that accepts an input which is a bit different than some people is used to. But it is a great way of dealing with long calculations without needing parens grouping.


This notation is very common in finance calculators. Besides the use in math, the problem of implementing a RPN calculator is one of the “Hello World” problems implemented in a lot of programming languages. And, as we are talking about Perl 6, let's get our hands dirty.


Daniel Carrera implemented a version using grammars, but I wanted to make a different version using multi-subs, which is one of the most important features of Perl 6. I'll just throw the code, that you can save as rpn.pl

multi infix:<rpn> (@stack, $num where /^ \d+[\.\d+]? $/) {
  [ @stack, $num ];
}
multi infix:<rpn> (@stack, $op where /^ '+' | '-' | '*' | '/' $/) {
  my $lhs = @stack.pop;
  my $rhs = @stack.pop;
  my $res = do given $op {
    when '+' { $rhs + $lhs }
    when '-' { $rhs - $lhs }
    when '*' { $rhs * $lhs }
    when '/' { $rhs / $lhs }
  }
  [ @stack, $res ]
}
multi infix:<rpn> ($any, $unknown) {
  die "Unkown expression near $any $unknown";
}
say [rpn] [], (~@*ARGS).words;

That being done, considering you already has rakudo installed, you can just run

perl6 rpn.pl "5 4 + 3 / 5 3 - *"

And by that you should have the result “6”. Don't worry if you didn't understand all the details of that code, it uses some new features of Perl 6, some of them don't exist in any other language. If you have experience in some other languages, you might find weird that this code doesn't have any loop, as you would need to iterate all the items of the expression. In fact, this code shows, to all its extent, the functional heritage of Perl 6, and all happens by the “reduce” operator and the signature of each of the candidates of the multi-sub.

infix what?

One of the most unexpected things in this code is the “infix” in the name of the routines. The use of that term indicates that this is not a regular routine, but it is an operator, and that it can be used as any other operator.

What that means is that in Perl 6 all operators are defined in high-level. They are not language primitives, which means you can do:

multi infix:<+> ($a where 2, $b where 2) {
  return 5;
}
say 2 + 2;

And the result will be “5”, because the signature of this candidate is narrower than any other candidate for infix:<+> (Rakudo currently has a bug that prevents this code from working).

But after all, what is “infix”? Well, it defines the kind of the operator, follows a table explaining the types of operators you can create:

Type

How it appears

Examples

How to declare

Infix

$a OP $b

1 + 1, 4 * 4, 'a' x 4

multi infix:<+> {...}

Postfix

$aOP

$a++, 4i

multi postfix:<i> {...}

Prefix

OP$a

++$a, ^$a

multi prefix:<^> {...}

Postcircumfix

$aOP $b OP

$a{$b}, $a[$b]

multi postcircumfix:<{ }> {...}

Circumfix

OP $b OP

( $a )

multi circumifx:<( )> {...}

What that means is that, in fact, that multisubs are declaring a new operator, the “rpn” operator. And, after that declarations, you can simply write:

2 rpn 3

And it would take one of the candidates to execute (in this case it would execute the “die” one). But we could succesfully do the following using the operator:

(([] rpn 2) rpn 3) rpn '+'

Then it would return 5. But fortunally, to our sanity, there is a meta-operator in Perl 6 that does that in a much nicer way, which is “reduce”.

[rpn] [], 2, 3, '+'

The reduce meta-operator will call the infix rpn operator using the same kind of grouping of the previous example, making it all simpler.

The last line

This is a very concise line that says a lot, let's go step by step:

~@*ARGS

The @*ARGS variable contains the parameters sent to the program, and the the prefix ~ operator is used to “stringify” a give value, which in the case of the array, means that it will join all elements with a space. What that menas is that it doesn't matter if you send the entire expression as a single parameter, or each part of the expression as a different parameter, it will work either way..

(~@*ARGS).words

The “word” method in strings, will split this string into a list of words. It's more or less the same thing as using split with space, but as this is so common, there's a specific method to do that.

[], (~@*ARGS).words

That line is composing a new list which will contain an empty array as the first element.

[rpn] [], (~@*ARGS).words

That line will call the reduce meta-operator with the rpn operator, using the list as the argument, and that will reduce the input list to the rpn result, which in the end is sent to the user with “say”.

No TrackBacks

TrackBack URL: http://daniel.ruoso.com/cgi-bin/mt/mt-tb.cgi/136

1 Comment

Do you mind if I add this to the perl6-examples code?

Leave a comment

About this Entry

This page contains a single entry by Daniel Ruoso published on June 16, 2009 11:59 AM.

A plan for module loading in mildew is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.