The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Perl6::Controls - Add Perl 6 control structures as Perl 5 keywords

VERSION

This document describes Perl6::Controls version 0.000006

SYNOPSIS

    use Perl6::Controls;

    try {
        CATCH { warn 'No more animals :-(' }

        loop {
            my @animals;

            repeat while (!@animals) {
                say 'Enter animals: ';
                @animals = grep {exists $group_of{$_} }
                           split /\s+/,
                           scalar readline // die;
            }

            for (%group_of{@animals}) -> $animal, $group {
                FIRST { say "\n(" }
                say " '$animal' => '$group',";
                LAST  { say ")\n" }
            }
        }
    }

DESCRIPTION

This module steals some of the most useful control structures provided by Perl 6 and retrofits them to Perl 5, via the extensible keyword mechanism.

INTERFACE

loop {...}

The loop loop is simply an infinite loop:

    loop {
        say 'Are we there yet?';
        sleep 60;
    }

Note that this module does not implement the extended Perl 6 loop (INIT; COND; INCR) {...} syntax, as that is already covered by Perl 5's builtin for.

for (LIST) -> $VAR1, $VAR2, $ETC {...}

The module adds an additional syntax for for loops, which allows one or more (lexical) iterator variables to be specified in the Perl 6 way: after the list.

That is, instead of:

    for my $pet ('cat', 'dog', 'fish') {
        say "Can I have a $pet?";
    }

you can write:

    for ('cat', 'dog', 'fish') -> $pet {
        say "Can I have a $pet?";
    }

The real advantage of the Perl 6 syntax is that you can specify two or more iterator variables for the same loop, in which case the loop will iterate its list N-at-a-time. For example:

    for (%pet_prices) -> $pet, $price {
        say "A $pet costs $price";
    }

Note that, unlike in Perl 6, the list to be iterated still requires parentheses around it.

Under Perl v5.22 and later, you can specify the variables after the arrow with a leading reference, in which case the corresponding values are aliased to that variable (see "Assigning to References" in perlref).

Note that the relevant experimental features must be activated to use this.

For example:

    use experimental qw< refaliasing declared_refs>;

    for (%hash_of_arrays) -> $key, \@array {
        # Print the contents of each hash entry...
        say "$key has $_" foreach @array;

        # Append an element to each nested array...
        push @array, 42;s
    }

while (COND) -> $VAR {...}

The module also adds a similar new syntax for while loops, allowing them to test their condition and assign it to a lexically scoped variable using the "arrow" syntax.

That is, instead of:

    while (my $input = readline) {
        process( $input );
    }

You can write:

    while (readline) -> $input {
        process( $input );
    }

Note that, unlike the modified for syntax, this modified while only allows a single variable after the arrow.

repeat while (COND) {...}

repeat until (COND) {...}

repeat {...} while COND

repeat {...} until COND

The Perl 5 do...while and do...until constructs are not proper loops (they're quantified do statements). This means, for example, that you can't use next, last, or redo to control them.

The Perl 6 repeat constructs are genuine "iterate-then-test" loops, and also allow the condition to be specified either after or before the block.

Therefore, instead of:

    do {
        print 'Next value: ';
        $value = get_data() // last;  # Oops!
    }
    while ($value !~ /^\d+$/);

or equivalently:

    do {
        print 'Next value: ';
        $value = get_data() // last;  # Oops!
    }
    until ($value =~ /^\d+$/);

you can write any of the following:

    repeat {
        print 'Next value: ';
        $value = get_data() // last;  # Works as expected
    }
    while ($value !~ /^\d+$/);

    repeat {
        print 'Next value: ';
        $value = get_data() // last;  # Works as expected
    }
    until ($value =~ /^\d+$/);

    repeat while ($value !~ /^\d+$/) {
        print 'Next value: ';
        $value = get_data() // last;  # Works as expected
    }

    repeat until ($value =~ /^\d+$/) {
        print 'Next value: ';
        $value = get_data() // last;  # Works as expected
    }

FIRST {...}

NEXT {...}

LAST {...}

These special blocks can only be placed in a loop block, and will execute at different points in each execution of the surrounding loop.

The FIRST block is executed only on the first iteration of the surrounding loop. The NEXT block iterates at the end of every iteration of the surrounding loop. The LAST block executes only after the final iteration of the surrounding loop.

For example, instead of:

    if (@list) {
        print '(';
        for my $elem (@list) {
            print $elem;
            print ',';
        }
        print ')';
    }

you could write:

    for my $elem (@list) {
        FIRST { print '('; }
        print $elem;
        NEXT  { print ','; }
        LAST  { print ')'; }
    }

or (because the order and position in which the special blocks are declared does not affect when they are called):

    for my $elem (@list) {
        FIRST { print '('; }
        NEXT  { print ','; }
        LAST  { print ')'; }

        print $elem;
    }

try { ... CATCH {...} ... }

The try block is more or less equivalent to a Perl 5 eval, except that it is a proper block, so it doesn't return a value, but it also doesn't require a semicolon after it. The try intercepts and neutralizes any exception called within its block.

The CATCH block specifies an alternative response when the surrounding try intercepts an exception. It may be specified with a parameter, to which the intercepted exception is then assigned. The CATCH block also acts like a given, so the exception is always aliased to $_, and the block may contain when and default statements as well.

For example:

    try {
        $data = acquire_data();  # May throw exception

        CATCH ($err) {
            when (/bad data/) { warn 'Ignoring bad data' }
            when (/no data/)  { exit -1;  }
            default           { die $err; } # Rethrow unknown exception
        }
    }

Note that the CATCH block is always optional within a try, and may be placed anywhere within the try block.

DIAGNOSTICS

Can't specify CATCH block outside a try

The syntax for Perl 6 try blocks requires the CATCH block to be placed inside the try block (so that it has access to any lexical variables declared in the try).

You declared a CATCH block outside of any try, which means that block could never be invoked. Either add a try block around the source of potential errors that you are trying to catch, or remove the CATCH.

Can't specify two CATCH blocks inside a single try

try blocks have a single slot for their CATCH callback, and it's already full, because you already specified another CATCH block earlier in the same try block.

Consolidate the code in the two blocks into a single block. (Remember, it doesn't matter where in the try you place the CATCH.)

CONFIGURATION AND ENVIRONMENT

Perl6::Controls requires no configuration files or environment variables.

DEPENDENCIES

Requires Perl 5.14 and the Keyword::Declare module.

INCOMPATIBILITIES

Due to a problem with regex parsing in Perl v5.20, code using this module with compile absurdly slowly under that version of the interpreter. There is no such problem with any other version of Perl from v5.14 onwards.

BUGS AND LIMITATIONS

Because the blocks of FIRST, LAST, NEXT, and CATCH are converted to subroutines, any call to return within one of these blocks will terminate the block, but not the surrounding subroutine.

Unlike in Perl 6, the FIRST phaser does not execute before the first iteration, but rather only during the first iteration. This means that if you want it to execute at the start of the first iteration, you need to place it at the start of the iterated block.

The underlying keyword rewriting mechanism (or perhaps the Keyword::Simple API) seems to have a bug when it comes to keywords that are also postfix quantifiers. This means that code using this module cannot also use postfix for or while. It can, however, still use postfix foreach or until.

No bugs have been reported.

Please report any bugs or feature requests to bug-perl6-controls@rt.cpan.org, or through the web interface at http://rt.cpan.org.

AUTHOR

Damian Conway <DCONWAY@CPAN.org>

LICENCE AND COPYRIGHT

Copyright (c) 2017, Damian Conway <DCONWAY@CPAN.org>. All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.