Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

masak (6289)

masak
  (email not shown publicly)
http://masak.org/carl

Been programming Perl since 2001. Found Perl 6 somewhere around 2004, and fell in love. Now developing November (a Perl 6 wiki), Druid (a Perl 6 board game), pls (a Perl 6 project installer), GGE (a regex engine), and Yapsi (a Perl 6 implementation). Heavy user of and irregular committer to Rakudo.

Journal of masak (6289)

Saturday December 20, 2008
01:34 PM

Fun with code blocks in Perl 6

[ #38123 ]

Here's a little pattern I've discovered while hacking away at a board game implementation in Perl 6.

I had a subroutine called input_valid_move, whose job it was to read a move from $*IN, and return the move if it was valid according to the rules of the game. Easy enough.

repeat {
    print "\n", $player, ': ';
} until my $move = input_valid_move(...);

Now, there are several ways a move can be illegal, and I found myself printing and returning a lot from the sub:

unless $row_diff == 2 && $column_diff == 0
    || $row_diff == 0 && $column_diff == 2 {

    say 'Must be exactly two cells apart';
    return;
}

unless @heights[$row_1][$column_1]
    == @heights[$row_2][$column_2] {

    say 'Must be supported at both ends';
    return;
}

Notice the repetition? There were many (7) such tests for move correctness, and all of them made a boolean test, printed something and then returned from the sub:

if ( ... ) { # or 'unless'; depends
    say '...';
    return;
}

Repetition is a sign that there there is an abstraction just waiting to be created. I wanted to make an abstraction flunk_move that closed over the say '...'; return part of the above pattern, parametrizing the message printed. That way, I could just write this instead:

    flunk_move 'Must be exactly two cells apart'
        unless $row_diff == 2 && $column_diff == 0
            || $row_diff == 0 && $column_diff == 2;

    flunk_move 'Must be supported at both ends'
        unless @heights[$row_1][$column_1]
            == @heights[$row_2][$column_2];

Each move correctness test now became a single statement, instead of an if/unless statement containing two statements. As an added bonus, the most important part of the statement (the disqualification of the move) is now leftmost in the statement, something Damian Conway talks about in his book "Perl Best Practices".

But a new subroutine would not do as a repetition-reducing abstraction. The return statement in such a new sub, having moved from its original environment would be a no-op. I wanted to eat the cake and have it, too.

S06 states that the return function throws a control exception that is caught by the current lexically enclosing Routine, and this fact turned out to be just what I needed. To decipher the Perl 6 designese, the return in a sub returns from that sub, but the return in a bare block returns from the sub (or whatever) it was called from.

  # not what I want -- the return does nothing
  sub flunk_move($reason) { say $reason; return };

  # what I want, using pointy block
  -> $reason { say $reason; return };

  # what I want, using placeholder variables
  { say $^reason; return };

Think of it in biological terms: a sub is like a eukaryote: a little more complex, handles advanced things like return when necessary. A bare block doesn't have all that advanced piping, and has to delegate its return calls to its surrounding host cell. In other words, a bare block is a bit like an endosymbiont prokaryote, a simple organism that in the course of evolutionary history ended up in a symbiotic relationship inside a larger eukaryotic cell.

Biological analogies aside, what it meant to me was that I could do this in my sub input_valid_move:

my &flunk_move = { say $^reason; return };

(There's the endosymbiont, right there! It can't return from itself, because it's just a humble code block, so it returns from its surrounding subroutine instead, which happens to be input_valid_move.)

After that, I could use &flunk_move just as I wanted, as if it were a return statement with side effects. (Same code as above.)

    flunk_move 'Must be exactly two cells apart'
        unless $row_diff == 2 && $column_diff == 0
            || $row_diff == 0 && $column_diff == 2;

    flunk_move 'Must be supported at both ends'
        unless @heights[$row_1][$column_1]
            == @heights[$row_2][$column_2];

Some Smalltalk people extol the power in being able to define things like the if statement from within the language, without any magical trickery to make it work. The pattern I discovered above uses the same kind of strengths, the ability to define my own slightly fancy return statement, and have it look like a built-in in subsequent code.

That kind of power is what makes Perl 6 a joy to use.

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.
  • Hi. Where you talk about Damian's recommendation, I think you've got it backwards. On p 93 of his book he says 'avoid using the postfix form of if'. But that's exactly what you're doing!
    Cheers

    • Page 94 says "Reserve postfix if for flow-of-control statements." It's his only exception to the previous guideline.

      • Right, and this is a fancy return, hence control flow. I forgot to clarify that.

  • While clever, I think you've harmed readability rather than helped it by hiding the return in the block. As a reader, I want the flow control statements to be as explicit and obvious as possible so I don't miss them. That's the reason why PBP makes an exception for them in end-of-line conditionals and loops.

    • I can certainly understand that objection. Readability is important, and inventing new control flow "builtins" is a risky business.

      However, it's also possible to reason the other way: I expect any conscientious reader to familiarize themselves with all the local variables in my sub before trying to understand what it does. &flunk_move is one of those local variables; and it happens to be a code block controlling the program flow. Making use of PBP's rule for putting control flow on the left, serves to m

    • This technique is open to abuse, but consider a function with several potential exit points, each of which need to release acquired resources. This technique offers a cleaner approach than a local-goto or cut-and-past resource release blocks.

      • Agreed, to a degree. Carl is trading clarity of control flow for a consistent level of abstraction. As long as the technique is used sparingly and locally, I think it can be a net win.

        But I'll wager that someone will write a Perl6::Critic policy about it in the future, maybe to say that pointy blocks with far-reaching control statements should be simple or not exported beyond the lexical scope.

    • That seems like an odd attitude coming from someone programming in Perl. I know Python programmers who think statement modifiers are a terrible idea in any and all cases on principle. Where did we lose the idea of giving people enough rope to hang themselves if they so desire? Just because this particular abstraction facility is new to you does not make it dangerous. All abstraction facilities have abuse potential – practice shows which ones are very dangerous and which ones can offer enough potential

      • You're preaching to the choir. I'm just on the other end of the style continuum from you, I guess. I work with enough less-experienced programmers to prefer simple solutions, all else being (roughly) equal.

        I'm not saying the practice should be *banned* from the language. I just don't want to see it in any code I must maintain. Action at a distance is the problem -- if the distance is small enough, then it's probably fine. On first read, I personally didn't think the solution was beneficial enough to jus