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