[Melbourne-pm] Closures and scope warnings

Damian Conway damian at conway.org
Tue Jul 27 21:01:09 PDT 2010


Jacinta observed:

> Would you *really* want every subroutine which creates variables which
> shadow top-level variables to warn about such?  I don't think Perl
> distinguishes between your case and mine.

It doesn't even distinguish this case:

    my $name = "Jacinta";

    {
        my $name = 'shift';

        print "$name\n";
    }

The whole point of lexical scope is that variables declared inside a
block hide identically named variables declared outside the block. It
doesn't matter if the block is "raw" (as above) or is the body of a
named subroutine (like Jacinta's example), or the body of an unnamed sub
(like Toby's example).

Of course, it is annoying if you're using an upscope variable within a
closure and you accidentally shadow it with a lexical declaration. I use
closures all the time, so I have a couple of techniques that make that
mistake very unlikely.

If the upscope variable is being used only as a value, I generally
capitalize its name. For example:

    sub generate_prompter {
        my ($PROMPT) = @_;

        return sub {
            print $PROMPT;
            return readline;
        }
    }

The capitals help me remember to treat it as a constant, and stop
it from shadowing any in-scope lexicals (because they'll have
lowercase names, like variables normally do).

If the upscope variable is being modified downscope, I generally
name it $outer_whatever or $shared_whatever. For example:

    my $shared_verbose;

    sub set_verbose {
        my $verbose = shift // 1;

        $shared_verbose = $verbose;
    }

    sub get_verbose {
         return $shared_verbose;
    }

The longer name helps prevent name collisions with in-scope lexicals,
which generally get shorter names.

Thus, I'd write Toby's example something like:

    sub do_something {
        my $schema; #isa DBIx::Class::Schema
        my $user = $schema->resultset('Users')->find(1);
        my $NEW_ROLE = "something";
        my $outer_old_role;

        $schema->txn_do(sub {
            $user->name("flooble");
            $outer_old_role = $user->role;    # <-- accessing data outside
            $user->add_role($NEW_ROLE);       # <-- and again
            $user->update;
        });
    }

It's not fool-proof, but this approach does reduce the chance of name
collisions significantly. Of course, there's no reason to adopt my
particular naming scheme, but having *some* consistent naming scheme
for these situations really is a good idea.

Damian


More information about the Melbourne-pm mailing list