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

NAME

Acme::Pythonic - Python whitespace conventions for Perl

SYNOPSIS

 use Acme::Pythonic; # this semicolon yet needed

 sub delete_edges:
     my $G = shift
     while my ($u, $v) = splice(@_, 0, 2):
         if defined $v:
             $G->delete_edge($u, $v)
         else:
             my @e = $G->edges($u)
             while ($u, $v) = splice(@e, 0, 2):
                 $G->delete_edge($u, $v)

DESCRIPTION

Acme::Pythonic brings Python whitespace conventions to Perl. Just use it and Pythonic code will become valid on the fly. No file is generated, no file is modified.

This module is thought for those who embrace contradictions. A humble contribution for walkers of the Whitespace Matters Way in their pursuit of highest realization, only attained with SuperPython.

OVERVIEW

Acme::Pythonic provides grosso modo these conventions:

  • Blocks are marked by indentation and an opening colon instead of braces.

  • Simple statements are separated by newlines instead of semicolons.

  • EXPRs in control flow structures do not need parentheses around.

Additionally, the filter understands the keywords pass and in.

    for my $n in 1..100:
        while $n != 1:
            if $n % 2:
                $n = 3*$n + 1
            else:
                $n /= 2

DETAILS

Labels

The syntax this module provides introduces an ambiguity: Given

    if $flag:
        do_this()
    else:
        do_that()

there's no way to know whether that is meant to be

    if ($flag) {
        do_this();
    } else {
        do_that();
    }

or rather

    if ($flag) {
        do_this();
    }
    else: {
        do_that();
    }

The former is a regular if/else, whereas the latter consists of an if and a labeled block, so do_that() is unconditionally executed.

To solve this labels in Pythonic code have to be in upper case.

In addition, to be able to write BEGIN blocks and friends this way:

    BEGIN:
        $foo = 3

BEGIN, CHECK, INIT, END cannot be used as labels.

Let's see some examples. This is the Pythonic version of the snippet in perlsyn:

    OUTER: for my $wid in @ary1:
        INNER: for my $jet in @ary2:
            next OUTER if $wid > $jet
            $wid += $jet

And here we have a labeled block:

    my $k = 7
    FOO:
        --$k
        last FOO if $k < 0
        redo FOO

Note that if we put a label in the line before in a control structure indentation matters, because that's what marks blocks. For instance, this would be a non-equivalent reformat of the example above:

    OUTER:
        for my $wid in @ary1:               # NOT WHAT WE WANT
            INNER:
            for my $jet in @ary2:           # GOOD, ALIGNED
                next OUTER if $wid > $jet
                $wid += $jet

Since the first for is indented with respect to the label OUTER: we get a labeled block containing a for loop, instead of a labeled for. This is the interpretation in regular Perl:

    OUTER: {
        for my $wid (@ary1) {               # NOT WHAT WE WANT
            INNER:
            for my $jet (@ary2) {           # GOOD, ALIGNED
                next OUTER if $wid > $jet;
                $wid += $jet;
            }
        }
    }

The consequence is that next OUTER goes outside the outer for loop and thus it is restarted, instead of continued.

do/while-like constructs

Acme::Pythonic tries to detect statement modifiers after a do BLOCK. Thus

    do:
        do_something()
        do_something_else()
    while $condition

is seen as a do/while, whereas

    do:
        do_something()
        do_something_else()
    while $condition:
        handle_some_stuff()

is not.

New Keywords

pass

pass is a NO-OP, it is meant to explicit empty blocks:

    sub abstract_method:
        pass

in

This works:

    foreach my $foo @array:
        do_something_with $foo

Nevertheless, in can be inserted there as in Python in case you find the following more readable:

    foreach my $foo in @array:
        do_something_with $foo

This keyword can be used if there's no variable to its left too, which means we are dealing with $_ as usual:

    foreach in @array:
        s/foo/bar/

but can't be used when the loop acts as a modifier:

    print foreach in @array # ERROR

This keyword is not supported as sequence membership operator.

&-Prototyped subroutines

&-prototyped subroutines can be used like this:

    sub mygrep (&@):
        my $code = shift
        my @result
        foreach @_:
            push @result, $_ if &$code
        return @result

    @array = mygrep:
        my $aux = $_
        $aux *= 3
        $aux += 1
        $aux % 2
    reverse 0..5

If the prototype is exactly &, however, Acme::Pythonic needs to know it in advance. Thus, if any module defines such a subroutine use() it before Acme::Pythonic:

    use Thread 'async';
    use Acme::Pythonic; # now Acme::Pythonic knows async() has prototype "&"

    async:
        do_this()
        do_that()

If such a subroutine is defined in the very code being filtered we need to declare it before Acme::Pythonic is use()d:

    sub twice (&);      # declaration
    use Acme::Pythonic; # now Acme::Pythonic knows twice() has prototype "&"

    # the definition itself can be Pythonic
    sub twice (&):
         my $code = shift
         $code->() for 1..2

    twice:
         do_this_twice()

Nevertheless, the module is not smart enough to handle optional arguments as in a subroutine with prototype &;$.

Line joining

As in Python, you can break a logical line in several physical lines using a backslash at the end:

    my $total = total_products() + \
                total_delivery() + \
                total_taxes()

and in that case the indentation of those additional lines is irrelevant.

Unlike Python, backslashes in a line with a comment are allowed

    my $foo = 1 + \  # comment, no problem
        2

If a line ends in a comma or arrow (=>) it is conceptually joined with the following as well:

    my %authors = (Perl   => "Larry Wall",
                   Python => "Guido van Rossum")

As in Python, comments can be intermixed there:

    my %hello = (Catalan => 'Hola',   # my mother tongue
                 English => 'Hello',)

Acme::Pythonic munges a source that has already been processed by Filter::Simple. In particular, Filter::Simple blanks out quotelikes whose content is not even seen by Acme::Pythonic so backslashes in qw// and friends won't be removed:

    # Do not put backslashes here because qw// is bypassed
    my @colors = qw(Red
                    Blue
                    Green)

CAVEATS

Although this module makes possible some Python-like syntax in Perl, there are some remarkable limitations in the current implementation:

  • Compound statement bodies are not recognized in header lines. This would be valid according to Python syntax:

        if $n % 2: $n = 3*$n + 1
        else: $n /= 2

    but it does not work in Acme::Pythonic. The reason for this is that it would be hard to identify the colon that closes the expression without parsing Perl, consider for instance:

        if keys %foo::bar ? keys %main:: : keys %foo::: print "foo\n"
  • In Python statements may span lines if they're enclosed in (), {}, or [] pairs. Acme::Pythonic does not support this rule, however, though it understands the common case where you break the line in a comma in list literals, subroutine calls, etc.

Remember that source filters do not work if they are called at runtime, for instance via require or eval EXPR. The source code was already consumed in the compilation phase by then.

DEBUG

Filter::ExtractSource can be used to inspect the source code generated by Acme::Pythonic:

    perl -c -MFilter::ExtractSource pythonic_script.pl

Acme::Pythonic itself has a debug flag though:

    use Acme::Pythonic debug => 1;

In debug mode the module prints to standard output the code it has generated, and passes just a dummy 1; to Filter::Simple.

This happens before Filter::Simple undoes the blanking out of PODs, strings, and regexps. Those parts are marked with the label BLANKED_OUT for easy identification.

Acme::Pythonic generates human readable Perl following perlstyle, and tries meticulously to be respectful with the original source code. Blank lines and comments are preserved.

BUGS

This module uses a regexp approach and the superb help of Filter::Simple. The regexp part of this means it is broken from the start, though I've tried hard to make it as robust as I could. Bug reports will be very welcome, just drop me a line!

THANKS

Damian Conway gave his full blessing if I wanted to write a module like this based on his unpublished Language::Pythonesque. The code that handles indentation is inspired by his.

Also, Dr. Conway is the author of Filter::Simple, which aids a lot blanking out PODs, strings, etc. so you can munge the source with certain confidence. Without Filter::Simple this module would be infinitely more broken.

Esteve Fernandez helped testing the module under 5.6.1 and contributed a Sieve of Eratosthenes for t/algorithms.t. Thank you dude!

SEE ALSO

perlfilter, Filter::Simple, Filter::ExtractSource, SuperPython, Acme::Dot.

AUTHOR

Xavier Noria (FXN), <fxn@cpan.org>

COPYRIGHT AND LICENSE

Copyright (C) 2004-2012 by Xavier Noria

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.2 or, at your option, any later version of Perl 5 you may have available.