Anonymous State Variables And How They Work
When debugging code, I will often add a counter variable to a loop so I can keep track of what's going on, or so that I can process a fraction of my data set while I'm iterating on a piece of code:
my $event-no = 0;
for get_events() -> $event {
$event-no++;
process-event($event);
last if $event-no >= 5;
}
If you're just debugging, or if you're trying to save space in a one-liner,
Perl 6 actually has a notion of anonymous state variables, indicated
by a $
without a name
you can also use the kv method on many iterable
objects to accomplish something similar, but anonymous $
is more generic
you can also use the kv method on many iterable
objects to accomplish something similar, but anonymous $
is more generic
:
for get_events() -> $event {
process-event($event);
last if ++$ >= 5;
}
However, be warned; usages like this will not work:
for get_events() -> $event {
process-event($event);
$++;
last if $ >= 5;
}
Now, why is that?
Use the Source
Well, let's have a look at the Rakudo source, shall we?
As you can imagine, looking for how $
is parsed in the Perl 6 Grammar would
be a difficult task. So let's have the compiler itself help us! We'll take a
small example:
for ^10 { $++ }
and have Rakudo spit out the AST it generates, looking specifically for variables:
$ perl6 --target=ast -e 'for ^10 { $++ }' | grep Var
- QAST::Var(attribute $!do)
- QAST::Var(attribute $!do)
- QAST::Var(local __args__ :decl(param))
- QAST::Var(lexical $¢ :decl(contvar))
- QAST::Var(lexical $! :decl(contvar))
- QAST::Var(lexical $/ :decl(contvar))
- QAST::Var(lexical $_ :decl(contvar))
- QAST::Var(lexical GLOBALish :decl(static))
- QAST::Var(lexical EXPORT :decl(static))
- QAST::Var(lexical $?PACKAGE :decl(static))
- QAST::Var(lexical ::?PACKAGE :decl(static))
- QAST::Var(lexical $=finish :decl(static))
- QAST::Var(lexical $ANON_VAR__1 :decl(statevar))
- QAST::Var(lexical $_ :decl(param))
- QAST::Var(lexical $ANON_VAR__1) :BY<EXPR/POSTFIX W> :nosink<?> :WANTED $
- QAST::Var(lexical $ANON_VAR__1) :BY<EXPR/POSTFIX W> :nosink<?> :WANTED $
- QAST::Var(lexical $=pod :decl(static))
- QAST::Var(lexical !UNIT_MARKER :decl(static))
- QAST::Var(local ctxsave :decl(var))
- QAST::Var(contextual $*CTXSAVE)
- QAST::Var(local ctxsave)
- QAST::Var(local ctxsave)
- QAST::Var(local ctxsave)
You might not noticed it right away, but in there is a suspicious declaration:
$ANON_VAR__1
. Now that we have a search string that's going to yield more
relevant results, a simple search using a tool like ack
leads us to src/Perl6/Actions.nqp
. Let's dive in!
# taken from rakudo@85d20f3
sub declare_variable($/, $past, $sigil, $twigil, $desigilname, $trait_list, $shape?, :@post) {
...
elsif $desigilname eq '' {
if $twigil {
$/.CURSOR.panic("Cannot have an anonymous variable with a twigil");
}
$name := QAST::Node.unique($sigil ~ 'ANON_VAR_');
$varname := $sigil;
}
...
}
So what this chunk of code (the only result when searching for ANON_VAR
) tells is that when we
declare a variable and it has no name after its sigil, we should generate a unique name.
How Did We Get Here?
That's fine, but how do we get here from the grammar? One dirty little trick I do in this situation is throw an exception and see where the backtrace leads me:
sub declare_variable($/, $past, $sigil, $twigil, $desigilname, $trait_list, $shape?, :@post) {
...
elsif $desigilname eq '' {
if $twigil {
$/.CURSOR.panic("Cannot have an anonymous variable with a twigil");
}
+ if nqp::atkey(nqp::getenvhash(), 'ROB_DEBUG') {
+ $/.CURSOR.panic("here I am!");
+ }
$name := QAST::Node.unique($sigil ~ 'ANON_VAR_');
$varname := $sigil;
}
...
}
After re-compiling, we run with my ROB_DEBUG
environment variable turned on, and with --ll-exception
,
to make sure the internals are included in the stack trace:
$ ROB_DEBUG=1 perl6 --ll-exception -e 'for ^10 { $++ }'
I won't include the stack trace for brevity's sake, but you can generate it yourself if you'd like to follow
along. By looking for the first stack trace entry that mentions Grammar.nqp
that occurs after the entry
that mentions Actions.nqp:3160
(where I inserted the exception), that leads us to token variable
in
Grammar.nqp
:
# also taken from rakudo@85d20f3
token variable {
:my $*IN_META := '';
[
| :dba('infix noun') '&[' ~ ']' <infixish('[]')>
| <sigil> <twigil>? <desigilname>
[ <?{ !$*IN_DECL && $*VARIABLE && $*VARIABLE eq $<sigil> ~ $<twigil> ~ $<desigilname> }>
{ self.typed_panic: 'X::Syntax::Variable::Initializer', name => $*VARIABLE } ]?
| <special_variable>
| <sigil> $<index>=[\d+] [<?{ $*IN_DECL }> <.typed_panic: "X::Syntax::Variable::Numeric">]?
| <sigil> <?[<]> <postcircumfix> [<?{ $*IN_DECL }> <.typed_panic('X::Syntax::Variable::Match')>]?
| <?before <sigil> <?[ ( [ { ]>> <!RESTRICTED> <?{ !$*IN_DECL }> <contextualizer>
| $<sigil>=['$'] $<desigilname>=[<[/_!¢]>]
| {} <sigil> <!{ $*QSIGIL }> <?MARKER('baresigil')> # try last, to allow sublanguages to redefine sigils (like & in regex)
]
[ <?{ $<twigil> && $<twigil> eq '.' }>
[ <.unsp> | '\\' | <?> ] <?[(]> <!RESTRICTED> <arglist=.postcircumfix>
]?
{ $*LEFTSIGIL := nqp::substr(self.orig(), self.from, 1) unless $*LEFTSIGIL }
}
That chunk of code probably doesn't make a lot of sense to you if you're just starting out with Perl 6, let alone the Rakudo source. The important branch is consider is this one:
| {} <sigil> <!{ $*QSIGIL }> <?MARKER('baresigil')> # try last, to allow sublanguages to redefine sigils (like & in regex)
This branch accepts variables consisting solely of a sigil. So what ends up happening is that token variable
matches each
instance of bare $
in the source code, and each occurrence ends up calling Actions::declare_variable
, resulting in
distinct variables, demonstrated by this snippet:
for ^3 {
say ++$;
say ++$;
}
=output
1
1
2
2
3
3
So you are only allowed to do very simple operations with anonymous state variables. Keep in mind you can also do things with anonymous array or hash variables:
for ^10 {
say((@).push($_));
}
...but their cumbersomeness discourages usage in serious code.
Published on 2016-01-27