Synopsis 4: Blocks and Statements
Larry Wall <larry@wall.org>
Maintainer: Larry Wall <larry@wall.org> Date: 19 Aug 2004 Last Modified: 2 Apr 2008 Number: 4 Version: 65
This document summarizes Apocalypse 4, which covers the block and statement syntax of Perl.
From t/var/var.t lines 19–22 (2 √, 0 ×): (skip)
| # L<S04/The Relationship of Blocks and Declarations> |
√ | ok eval('my $x; my $x; 1'), 'it is legal to declare $x twice in the same scope.'; |
√ | ok eval('state $x; state $x; 1'), 'it is legal to declare $x twice in the same scope.'; |
| |
From t/data_types/anon_block.t lines 13–18 (no results): (skip)
| L<S04/"The Relationship of Blocks and Declarations"> |
| |
| =cut |
| |
| plan 32; |
| |
Every block is a closure. (That is, in the abstract, they're all anonymous subroutines that take a snapshot of their lexical scope.) How a block is invoked and how its results are used are matters of context, but closures all work the same on the inside.
Blocks are delimited by curlies, or by the beginning and end of the current compilation unit (either the current file or the current eval string). Unlike in Perl 5, there are (by policy) no implicit blocks around standard control structures. (You could write a macro that violates this, but resist the urge.) Variables that mediate between an outer statement and an inner block (such as loop variables) should generally be declared as formal parameters to that block. There are three ways to declare formal parameters to a closure.
From t/statements/no_implicit_block.t lines 7–64 (12 √, 0 ×): (skip)
| # L<S04/The Relationship of Blocks and Declarations/"no implicit blocks" around |
| # "standard control structures"> |
| { |
| my $y; |
| if (my $x = 2) == 2 { |
| $y = $x + 3; |
| } |
√ | is $x, 2, '$x assigned in while\'s condition'; |
√ | is $y, 5, '$y assigned in while\'s body'; |
| } |
| |
| { |
| my $y; |
| unless (my $x = 2) != 2 { |
| $y = $x + 3; |
| } |
√ | is $x, 2, '$x assigned in while\'s condition'; |
√ | is $y, 5, '$y assigned in while\'s body'; |
| } |
| |
| { |
| my $y; |
| given my $x = 2 { |
| when 2 { $y = $x + 3; } |
| } |
√ | is $x, 2, '$x assigned in while\'s condition'; |
√ | is $y, 5, '$y assigned in while\'s body'; |
| } |
| |
| { |
| my $y; |
| while my $x = 2 { |
| $y = $x + 3; |
| last; |
| } |
√ | is $x, 2, '$x assigned in while\'s condition'; |
√ | is $y, 5, '$y assigned in while\'s body'; |
| } |
| |
| { |
| my $y; |
| for my @a = 1..3 { |
| $y = @a[1] + 3; |
| last; |
| } |
√ | is ~@a, '1 2 3', '@a assigned in while\'s condition'; |
√ | is $y, 5, '$y assigned in while\'s body'; |
| } |
| |
| { |
| my $y; |
| loop (my $x = 2; $x < 10; $x++) { |
| $y = $x + 3; |
| last; |
| } |
√ | is $x, 2, '$x assigned in while\'s condition'; |
√ | is $y, 5, '$y assigned in while\'s body'; |
| } |
$func = sub ($a, $b) { .print if $a eq $b }; # standard sub declaration
$func = -> $a, $b { .print if $a eq $b }; # a "pointy" block
$func = { .print if $^a eq $^b } # placeholder arguments
A bare closure without placeholder arguments that uses $_ (either explicitly or implicitly) is treated as though $_ were a formal parameter:
$func = { .print if $_ }; # Same as: $func = -> $_ { .print if $_ };
$func("printme");
In any case, all formal parameters are the equivalent of my variables within the block. See S06 for more on function parameters.
Except for such formal parameter declarations, all lexically scoped declarations are visible from the point of declaration to the end of the enclosing block. Period. Lexicals may not "leak" from a block to any other external scope (at least, not without some explicit aliasing action on the part of the block, such as exportation of a symbol from a module). The "point of declaration" is the moment the compiler sees "my $foo", not the end of the statement as in Perl 5, so
my $x = $x;
will no longer see the value of the outer $x; you'll need to say either
my $x = $OUTER::x;
or
my $x = OUTER::<$x>;
instead.
If you declare a lexical twice in the same scope, it is the same lexical:
my $x;
my $x;
By default the second declaration will get a compiler warning. You may suppress this by modifying the first declaration with proto:
my proto $x;
...
while my $x = @x.shift {...} # no warning
while my $x = @x.shift {...} # no warning
If you've referred to $x prior to the first declaration, and the compiler tentatively bound it to $OUTER::x, then it's an error to declare it, and the compiler is required to complain at that point. If such use can't be detected because it is hidden in an eval, then it is erroneous, since the eval() compiler might bind to either $OUTER::x or the subsequently declared "my $x".
From t/builtins/my.t lines 29–115 (22 √, 1 ×): (skip)
| # L<S04/The Relationship of Blocks and Declarations/prior to the first declaration> |
| # "If you've referred to $x prior to the first declaration, and the |
| # compiler tentatively bound it to $OUTER::x, then it's an error to |
| # declare it, and the compiler is allowed to complain at that point." |
| # A fully conformant compiler will fail this test. At best, |
| # is($d, 1, '$d is still the outer $d'); |
| # passes "tentatively", and the subsequent my is an uncomplained error. |
| |
| # shadowing a lexical with a new lexical of the same name |
| # and that lexical does not leak out into the outer scope |
| |
| my $d = 1; |
| { # create a new lexical scope |
√ | is($d, 1, '$d is still the outer $d'); |
| { # create another new lexical scope |
| my $d = 2; |
√ | is($d, 2, '$d is now the lexical (inner) $d'); |
| } |
| } |
√ | is(eval('$d'), 1, 'eval(\'$d\') has not changed'); |
| |
× | is( eval(' |
| my $d = 1; |
| { |
| my $d = 3 |
| } |
| $d; |
| '), 1, '$d is available, and the outer value has not changed' ); |
| |
| # check closures with functions |
| |
| my $func; |
| my $func2; |
| if (1) { # create a new lexical scope |
| my $e = 0; |
| $func = sub { $e++ }; # one to inc |
| $func2 = sub { $e }; # one to access it |
| } |
| |
√ | ok(!(eval '$e'), '$e is the not available in this scope'); |
√ | is($func2(), 0, '$func2() just returns the $e lexical which is held by the closure'); |
| $func(); |
√ | is($func2(), 1, '$func() increments the $e lexical which is held by the closure'); |
| $func(); |
√ | is($func2(), 2, '... and one more time just to be sure'); |
| |
| # check my as simultaneous lvalue and rvalue |
| |
√ | is(eval('my $e1 = my $e2 = 42'), 42, 'can parse squinting my value'); |
√ | is(eval('my $e1 = my $e2 = 42; $e1'), 42, 'can capture squinting my value'); |
√ | is(eval('my $e1 = my $e2 = 42; $e2'), 42, 'can set squinting my variable'); |
| |
√ | is(eval('my $x = 1, my $y = 2; $y'), 2, 'precedence of my wrt = and ,'); |
| |
| # check proper scoping of my in while condition |
| |
| my $result; |
| my $x = 0; |
√ | is(eval('while my $x = 1 { $result = $x; last } $result'), 1, 'my in while cond seen from body'); |
√ | is(eval('while my $x = 1 { last } $x'), 1, 'my in while cond seen after'); |
| |
| # check proper scoping of my in if condition |
| |
√ | is(eval('if my $x = 1 { $x } else { 0 }'), 1, 'my in if cond seen from then'); |
√ | is(eval('if not my $x = 1 { 0 } else { $x }'), 1, 'my in if cond seen from else'); |
√ | is(eval('if my $x = 1 { 0 } else { 0 } $x'), 1, 'my in if cond seen after'); |
| |
| # check proper scoping of my in loop initializer |
| |
√ | is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { $result = $x; last } $result'), 1, '1st my in loop cond seen from body'); |
√ | is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { $result = $y; last } $result'), 2, '2nd my in loop cond seen from body'); |
√ | is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { last } $x'), 1, '1st my in loop cond seen after'); |
√ | is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { last } $y'), 2, '2nd my in loop cond seen after'); |
| |
| # check that can declaring lexical twice is noop |
| { |
| my $f; |
| $f = 5; |
| my $f; |
√ | is($f, 5, "two lexicals declared in scope is noop"); |
| } |
| |
| my $x = 42; |
| { |
| my $x = $x; |
√ | is( $x, undef, 'my $x = $x; can not see the value of the outer $x'); |
| } |
As in Perl 5, "our $foo" introduces a lexically scoped alias for a variable in the current package.
The new constant declarator introduces a lexically scoped name for a compile-time constant, either a variable or a 0-ary sub, which may be initialized with a pseudo-assignment:
constant Num $pi = 3;
constant Num π = atan(2,2) * 4;
The initializing expression is evaluated at BEGIN time.
From t/var/constant.t lines 253–266 (0 √, 1 ×): (skip)
| # L<S04/The Relationship of Blocks and Declarations/The initializing |
| # expression is evaluated at BEGIN time.> |
| { |
| my $ok; |
| |
| eval ' |
| my $foo = 42; |
| BEGIN { $foo = 23 } |
| my constant timecheck = $foo; |
| $ok++ if timecheck == 23; |
| '; |
| |
× | ok $ok, "the initializing values for constants are evaluated at compile-time", :todo<feature>; |
| } |
There is a new state declarator that introduces a lexically scoped variable like my does, but with a lifetime that persists for the life of the closure, so that it keeps its value from the end of one call to the beginning of the next. Separate clones of the closure get separate state variables.
From t/var/state.t lines 7–27 (3 √, 0 ×): (skip)
| # L<S04/The Relationship of Blocks and Declarations/There is a new state declarator that introduces> |
| |
| # state() inside subs |
| { |
| sub inc () { |
| state $svar; |
| $svar++; |
| return $svar; |
| }; |
| |
√ | is(inc(), 1, "state() works inside subs (#1)"); |
√ | is(inc(), 2, "state() works inside subs (#2)"); |
√ | is(inc(), 3, "state() works inside subs (#3)"); |
| } |
| |
| # state() inside coderefs |
| { |
| my $gen = { |
| # Note: The following line is only executed once, because it's equivalent |
| # to |
| # state $svar will first { 42 }; |
Perl 5's "local" function has been renamed to temp to better reflect what it does. There is also a let function that sets a hypothetical value. It works exactly like temp, except that the value will be restored only if the current block exits unsuccessfully. (See Definition of Success below for more.) temp and let temporize or hypotheticalize the value or the variable depending on whether you do assignment or binding. One other difference from Perl 5 is that the default is not to undefine a variable. So
From t/var/let.t lines 7–62 (3 √, 8 ×): (skip)
| # L<S04/The Relationship of Blocks and Declarations/There is also a let function> |
| # L<S04/Definition of Success> |
| # let() should not restore the variable if the block exited successfully |
| # (returned a true value). |
| { |
| my $a = 42; |
| { |
× | is(eval('let $a = 23; $a'), 23, "let() changed the variable (1)"); |
| 1; |
| } |
× | is $a, 23, "let() should not restore the variable, as our block exited succesfully (1)"; |
| } |
| |
| # let() should restore the variable if the block failed (returned a false |
| # value). |
| { |
| my $a = 42; |
| { |
× | is(eval('let $a = 23; $a'), 23, "let() changed the variable (1)"); |
| undef; |
| } |
√ | is $a, 42, "let() should restore the variable, as our block failed"; |
| } |
| |
| # Test that let() restores the variable at scope exit, not at subroutine |
| # entry. (This might be a possibly bug.) |
| { |
| my $a = 42; |
| my $get_a = { $a }; |
| { |
× | is(eval('let $a = 23; $a'), 23, "let() changed the variable (2-1)"); |
× | is $get_a(), 23, "let() changed the variable (2-2)"; |
| 1; |
| } |
× | is $a, 23, "let() should not restore the variable, as our block exited succesfully (2)"; |
| } |
| |
| # Test that let() restores variable even when not exited regularly (using a |
| # (possibly implicit) call to return()), but when left because of an exception. |
| { |
| my $a = 42; |
| try { |
× | is(eval('let $a = 23; $a'), 23, "let() changed the variable in a try block"); |
| die 57; |
| }; |
√ | is $a, 42, "let() restored the variable, the block was exited using an exception"; |
| } |
| |
| { |
| my @array = (0, 1, 2); |
| { |
× | is(eval('let @array[1] = 42; @array[1]'), 42, "let() changed our array element"); |
| undef; |
| } |
√ | is @array[1], 1, "let() restored our array element"; |
| } |
From t/var/temp.t lines 7–102 (4 √, 6 ×): (skip)
| # L<S04/The Relationship of Blocks and Declarations/function has been renamed> |
| { |
| my $a = 42; |
| { |
× | is(eval('temp $a = 23; $a'), 23, "temp() changed the variable (1)"); |
| } |
√ | is $a, 42, "temp() restored the variable (1)"; |
| } |
| |
| # Test that temp() restores the variable at scope exit, not at subroutine |
| # entry. |
| { |
| my $a = 42; |
| my $get_a = { $a }; |
| { |
× | is(eval('temp $a = 23; $a'), 23, "temp() changed the variable (2-1)"); |
× | is $get_a(), 23, "temp() changed the variable (2-2)"; |
| } |
√ | is $a, 42, "temp() restored the variable (2)"; |
| } |
| |
| # temp() shouldn't change the variable containers |
| { |
| my $a = 42; |
| my $get_a = { $a }; |
| { |
× | ok(eval('temp $a = 23; $a =:= $get_a()'), "temp() shouldn't change the variable containers"); |
| } |
| } |
| |
| { |
| our $pkgvar = 42; |
| { |
× | is(eval(q/temp $pkgvar = 'not 42'; $pkgvar/), 'not 42', "temp() changed the package variable (3-1)"); |
| } |
√ | is $pkgvar, 42, "temp() restored the package variable (3-2)"; |
| } |
| |
| # Test that temp() restores variable even when not exited regularly (using a |
| # (possibly implicit) call to return()), but when left because of an exception. |
| { |
| my $a = 42; |
| try { |
× | is(eval('temp $a = 23; $a'), 23, "temp() changed the variable in a try block"); |
| die 57; |
| }; |
√ | is $a, 42, "temp() restored the variable, the block was exited using an exception"; |
| } |
| |
| eval(' |
| { |
| my @array = (0, 1, 2); |
| { |
| temp @array[1] = 42; |
| is @array[1], 42, "temp() changed our array element"; |
| } |
| is @array[1], 1, "temp() restored our array element"; |
| } |
| "1 - delete this line when the parsefail eval() is removed"; |
| ') or skip(2, "parsefail: temp \@array[1]"); |
| |
| eval(' |
| { |
| my %hash = (:a(1), :b(2), :c(3)); |
| { |
| temp %hash<b> = 42; |
| is %hash<b>, 42, "temp() changed our hash element"; |
| } |
| is %hash<b>, 2, "temp() restored our array element"; |
| } |
| "1 - delete this line when the parsefail eval() is removed"; |
| ') or skip(2, "parsefail: temp \%hash<b>"); |
| |
| eval(' |
| { |
| my $struct = [ |
| "doesnt_matter", |
| { |
| doesnt_matter => "doesnt_matter", |
| key => [ |
| "doesnt_matter", |
| 42, |
| ], |
| }, |
| ]; |
| |
| { |
| temp $struct[1]<key>[1] = 23; |
| is $struct[1]<key>[1], 23, "temp() changed our nested arrayref/hashref element"; |
| } |
| is $struct[1]<key>[1], 1, "temp() restored our nested arrayref/hashref element", :todo<feature>; |
| } |
| "1 - delete this line when the parsefail eval() is removed"; |
| ') or skip(2, "parsefail: temp \$struct[1]<key>[1]"); |
| |
| # Block TEMP{} |
temp $x;
causes $x to start with its current value. Use
temp undefine $x;
to get the Perl 5 behavior.
Note that temporizations that are undone upon scope exit must be prepared to be redone if a continuation within that scope is taken.
The return value of a block is the value of its final statement. (This is subtly different from Perl 5's behavior, which was to return the value of the last expression evaluated, even if that expression was just a conditional.)
A line ending with a closing brace "}", followed by nothing but whitespace or comments, will terminate a statement if an end of statement can occur there. That is, these two statements are equivalent:
my $x = sub { 3 }
my $x = sub { 3 };
End-of-statement cannot occur within a bracketed expression, so this still works:
From t/blocks/pointy.t lines 41–49 (4 √, 0 ×): (skip)
| # L<S04/Statement-ending blocks/End-of-statement cannot occur within a bracketed expression> |
| my @a; |
√ | ok eval('@a = ("one", -> $x { $x**2 }, "three")'), |
| 'pointy sub without preceding comma'; |
√ | is @a[0], 'one', 'pointy sub in list previous argument'; |
√ | isa_ok @a[1], 'Code', 'pointy sub in list'; |
√ | is @a[2], 'three', 'pointy sub in list following argument'; |
| |
| |
my $x = [
sub { 3 }, # this comma is not optional
sub { 3 } # the statement won't terminate here
];
However, a hash composer may never occur at the end of a line. If the parser sees anything that looks like a hash composer at the end of the line, it fails with "closing hash curly may not terminate line" or some such.
my $hash = {
1 => { 2 => 3, 4 => 5 }, # OK
2 => { 6 => 7, 8 => 9 } # ERROR
};
Because subroutine declarations are expressions, not statements, this is now invalid:
From t/spec/S02-whitespace_and_comments/unspace.t lines 168–174 (no results): (skip)
| # L<S04/"Statement-ending blocks"/"Because subroutine declarations are expressions">
|
| #XXX probably shouldn't be in this file...
|
|
|
| eval('sub f { 3 } sub g { 3 }');
|
| is(eval('f'), undef, 'semicolon or newline required between blocks');
|
| is(eval('g'), undef, 'semicolon or newline required between blocks');
|
|
|
sub f { 3 } sub g { 3 } # two terms occur in a row
But these two are valid:
sub f { 3 }; sub g { 3 };
sub f { 3 }; sub g { 3 } # the trailing semicolon is optional
Though certain control statements could conceivably be parsed in a self-contained way, for visual consistency all statement-terminating blocks that end in the middle of a line must be terminated by semicolon unless they are naturally terminated by some other statement terminator:
while yin() { yang() } say "done"; # ILLEGAL
while yin() { yang() }; say "done"; # okay, explicit semicolon
@yy := [ while yin() { yang() } ]; # okay within outer [...]
while yin() { yang() } ==> sort # okay, ==> separates statements
From t/statements/if.t lines 9–117 (20 √, 0 ×): (skip)
| L<S04/"Conditional statements"> |
| |
| =cut |
| |
| plan 24; |
| |
| my $x = 'test'; |
√ | if ($x eq $x) { pass("if ($x eq $x) {} works"); } else { flunk("if ($x eq $x) {} failed"); } |
√ | if ($x ne $x) { flunk("if ($x ne $x) {} failed"); } else { pass("if ($x ne $x) {} works"); } |
√ | if (1) { pass("if (1) {} works"); } else { flunk("if (1) {} failed"); } |
√ | if (0) { flunk("if (0) {} failed"); } else { pass("if (0) {} works"); } |
√ | if (undef) { flunk("if (undef) {} failed"); } else { pass("if (undef) {} works"); } |
| |
| # die called in the condition part of an if statement should die immediately |
| # rather than being evaluated as true |
| my $foo = 1; |
| try { if (die "should die") { $foo = 3 } else { $foo = 2; } }; |
| #say '# $foo = ' ~ $foo; |
√ | is $foo, 1, "die should stop execution immediately."; |
| |
| { |
| my $foo = 1; # just in case |
| if 1 > 2 { $foo = 2 } else { $foo = 3 }; |
√ | is $foo, 3, 'if with no parens'; |
| }; |
| |
| # if...elsif |
| { |
| my $foo = 1; |
| if (1) { $foo = 2 } elsif (1) { $foo = 3 }; |
√ | is $foo, 2, 'if (1) {} elsif (1) {}'; |
| } |
| |
| { |
| my $foo = 1; |
| if (1) { $foo = 2 } elsif (0) { $foo = 3 }; |
√ | is $foo, 2, 'if (1) {} elsif (0) {}'; |
| } |
| |
| { |
| my $foo = 1; |
| if (0) { $foo = 2 } elsif (1) { $foo = 3 }; |
√ | is $foo, 3, 'if (0) {} elsif (1) {}'; |
| } |
| |
| { |
| my $foo = 1; |
| if (0) { $foo = 2 } elsif (0) { $foo = 3 }; |
√ | is $foo, 1, 'if (0) {} elsif (0) {}'; |
| } |
| |
| # if...elsif...else |
| |
| { |
| my $foo = 1; |
| if (0) { $foo = 2 } elsif (0) { $foo = 3 } else { $foo = 4 }; |
√ | is $foo, 4; |
| } |
| |
| { |
| my $foo = 1; |
| if (1) { $foo = 2 } elsif (0) { $foo = 3 } else { $foo = 4 }; |
√ | is $foo, 2; |
| } |
| |
| { |
| my $foo = 1; |
| if (1) { $foo = 2 } elsif (1) { $foo = 3 } else { $foo = 4 }; |
√ | is $foo, 2; |
| } |
| |
| { |
| my $foo = 1; |
| if (0) { $foo = 2 } elsif (1) { $foo = 3 } else { $foo = 4 }; |
√ | is $foo, 3; |
| } |
| |
| { |
| my $foo = 1; |
| if ({ 1 > 0 }) { $foo = 2 } else { $foo = 3 }; |
√ | is $foo, 2, 'if with no parens, and closure as cond'; |
| } |
| |
| { |
| my $var = 9; |
| my sub func( $a, $b, $c ) { $var }; |
| if func 1, 2, 3 { $var = 4 } else { $var = 5 }; |
√ | is $var, 4, 'if with no parens, and call a function without parenthesis'; |
| } |
| |
| # I'm not sure where this should go |
| |
| { |
√ | is( |
| eval('if ( my $x = 2 ) == 2 { $x; }'), |
| 2, |
| "'my' variable within 'if' conditional"); |
| } |
| |
| { |
√ | isnt(eval('if 1; 2'), 2, 'test "if 1"'); |
| } |
| |
| |
| {# .... if condition; |
| my $var = 5 if 1; |
√ | is $var, 5, ' <action> if <cond> ; - works'; |
| } |
| |
The if and unless statements work much as they do in Perl 5. However, you may omit the parentheses on the conditional:
From t/statements/unless.t lines 13–66 (8 √, 0 ×): (skip)
| # L<S04/Conditional statements/unless statements |
| # work as in Perl 5> |
| |
| my $x = 'test'; |
| { |
| my $found = 0; |
| unless $x ne $x { $found = 1; }; |
√ | ok($found, 'unless $x ne $x works'); |
| } |
| |
| { |
| my $found = 1; |
| unless $x eq $x { $found = 0; } |
√ | ok($found, 'unless $x eq $x is not executed'); |
| } |
| |
| { |
| my $found = 0; |
| unless 0 { $found = 1; } |
√ | ok($found, 'unless 0 is executed'); |
| } |
| |
| { |
| my $found = 1; |
| unless 1 { $found = 0; } |
√ | ok($found, 'unless 1 is not executed'); |
| } |
| |
| { |
| my $found = 0; |
| unless undef { $found = 1; } |
√ | ok($found, 'unless undef is executed'); |
| } |
| |
| # with paratheses |
| { |
| my $found = 0; |
| unless ($x ne $x) { $found = 1; }; |
√ | ok($found, 'unless ($x ne $x) works'); |
| } |
| |
| { |
| my $found = 1; |
| unless (5+2) { $found = 0; } |
√ | ok($found, 'unless (5+2) is not executer'); |
| } |
| |
| # die called in the condition part of an if statement should die immediately |
| # rather than being evaluated as a boolean |
| my $foo = 1; |
| try { unless (die "should die") { $foo = 3 }}; |
| #say '# $foo = ' ~ $foo; |
√ | is $foo, 1, "die should stop execution immediately."; |
| |
if $foo == 123 {
...
}
elsif $foo == 321 {
...
}
else {
...
}
If the final statement is a conditional which does not execute any branch, the return value is undef in item context and () in list context.
The unless statement does not allow an elsif or else in Perl 6.
From t/statements/unless.t lines 67–74 (2 √, 0 ×): (skip)
| # L<S04/Conditional statements/"The unless statement does not allow an elsif"> |
| |
√ | eval_dies_ok( |
| q[ unless 1 { 2 } else { 3 } ], |
| 'no else allowed in unless'); |
√ | eval_dies_ok( |
| q[ unless 1 { 2 } elsif 4 { 3 } ], |
| 'no elsif allowed in unless'); |
The value of the conditional expression may be optionally bound to a closure parameter:
From t/statements/if.t lines 118–150 (no results): (skip)
| # L<S04/"Conditional statements"/The value of the conditional expression may be optionally bound to a closure parameter> |
| { |
| my ($got, $a_val, $b_val); |
| my sub testa { $a_val }; |
| my sub testb { $b_val }; |
| |
| $a_val = 'truea'; |
| $b_val = 0; |
| if testa() -> $a { $got = $a } |
| elsif testb() -> $b { $got = $b } |
| else -> $c { $got = $c } |
| is $got, 'truea', 'if test() -> $a { } binding'; |
| |
| $a_val = 0; |
| $b_val = 'trueb'; |
| if testa() -> $a { $got = $a } |
| elsif testb() -> $b { $got = $b } |
| else -> $c { $got = $c } |
| is $got, 'trueb', 'elsif test() -> $b { } binding'; |
| |
| $a_val = ''; |
| $b_val = 0; |
| if testa() -> $a { $got = $a } |
| elsif testb() -> $b { $got = $b } |
| else -> $c { $got = $c } |
| is $got, 0, 'else -> $c { } binding previous elsif'; |
| |
| $a_val = ''; |
| $b_val = 0; |
| if testa() -> $a { $got = $a } |
| else -> $c { $got = $c } |
| is $got, '', 'else -> $c { } binding previous if'; |
| } |
if testa() -> $a { say $a }
elsif testb() -> $b { say $b }
else -> $b { say $b }
Note that the value being evaluated for truth and subsequently bound is not necessarily a value of type Bool. (All normal types in Perl may be evaluated for truth. In fact, this construct would be relatively useless if you could bind only boolean values as parameters, since within the closure you already know whether it evaluated to true or false.) Binding within an else automatically binds the value tested by the previous if or elsif, which, while known to be false, might nevertheless be an interesting value of false. (By similar reasoning, an unless allows binding of a false parameter.)
An explicit placeholder may also be used:
if blahblah() { return $^it }
However, use of $_ with a conditional statement's block is not considered sufficiently explicit to turn a 0-ary block into a 1-ary function, so both these methods use the same invocant:
if .haste { .waste }
(Contrast with a non-conditional statement such as:
for .haste { .waste }
where each call to the block would bind a new invocant for the .waste method, each of which is likely different from the original invocant to the .haste method.)
Conditional statement modifiers work as in Perl 5. So do the implicit conditionals implied by short-circuit operators. Note though that the first expression within parens or brackets is parsed as a statement, so you can say:
From t/statements/values_in_bool_context.t lines 7–89 (24 √, 0 ×): (skip)
| # L<S04/Conditional statements/Conditional statement |
| # modifiers work as in Perl 5> |
| |
| ## scalar checking ## |
| |
| { |
| my $var = 20; |
| |
| my ($a, $b, $c, $d, $e, $f, $g, $h); |
| |
| $a = 1 if 1; |
| $b = 1 if 0; |
| $c = 1 if "true"; |
| $d = 1 if ""; |
| $e = 1 if "1"; |
| $f = 1 if "0"; |
| $g = 1 if undef; |
| $h = 1 if $var; |
| |
√ | ok $a, 'literal in bool context - numeric true value'; |
√ | ok !$b, 'literal in bool context - numeric false value'; |
√ | ok $c, 'literal in bool context - string true value'; |
√ | ok !$d, 'literal in bool context - string false value'; |
√ | ok $e, 'literal in bool context - stringified true value'; |
√ | ok !$f, 'literal in bool context - stringified false value'; |
√ | ok !$g, 'literal in bool context - undef value'; |
√ | ok $h, 'literal in bool context - scalar variable'; |
| } |
| |
| ## array checking ## |
| |
| { |
| my @array = (1, 0, "true", "", "1", "0", undef); |
| |
| my ($a, $b, $c, $d, $e, $f, $g, $h); |
| |
| $a = 1 if @array[0]; |
| $b = 1 if @array[1]; |
| $c = 1 if @array[2]; |
| $d = 1 if @array[3]; |
| $e = 1 if @array[4]; |
| $f = 1 if @array[5]; |
| $g = 1 if @array[6]; |
| $h = 1 if @array; |
| |
√ | ok $a, 'array in bool context - numeric true value'; |
√ | ok !$b, 'array in bool context - numeric false value'; |
√ | ok $c, 'array in bool context - string true value'; |
√ | ok !$d, 'array in bool context - string false value'; |
√ | ok $e, 'array in bool context - stringified true value'; |
√ | ok !$f, 'array in bool context - stringified false value'; |
√ | ok !$g, 'array in bool context - undef value'; |
√ | ok $h, 'array in bool context array as a whole'; |
| } |
| |
| ## hash checking ## |
| |
| { |
| my %hash = ( |
| 0 => 1, 1 => 0, 2 => "true", |
| 3 => "", 4 => "1", 5 => "0", 6 => undef |
| ); |
| |
| my ($a, $b, $c, $d, $e, $f, $g, $h); |
| |
| $a = 1 if %hash{0}; |
| $b = 1 if %hash{1}; |
| $c = 1 if %hash{2}; |
| $d = 1 if %hash{3}; |
| $e = 1 if %hash{4}; |
| $f = 1 if %hash{5}; |
| $g = 1 if %hash{6}; |
| $h = 1 if %hash; |
| |
√ | ok $a, 'hash in bool context - numeric true value'; |
√ | ok !$b, 'hash in bool context - numeric false value'; |
√ | ok $c, 'hash in bool context - string true value'; |
√ | ok !$d, 'hash in bool context - string false value'; |
√ | ok $e, 'hash in bool context - stringified true value'; |
√ | ok !$f, 'hash in bool context - stringified false value'; |
√ | ok !$g, 'hash in bool context - undef value'; |
√ | ok $h, 'hash in bool context - hash as a whole'; |
| } |
From t/statements/modifiers/unless.t lines 9–20 (2 √, 0 ×): (skip)
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5> |
| { |
| my $a = 1; |
| $a = 4 unless 'a' eq 'a'; |
√ | is($a, 1, "post unless"); |
| } |
| |
| { |
| my $a = 1; |
| $a = 5 unless 'a' eq 'b'; |
√ | is($a, 5, "post unless"); |
| } |
From t/statements/modifiers/if2.t lines 7–25 (2 √, 2 ×): (skip)
| # L<S04/"Conditional statements"/that the first expression within parens or brackets is parsed as a statement> |
| |
| { |
| my $answer = 1; |
| my @x = 41, eval q[(42 if $answer)], 43; |
| my @y = 41, ($answer ?? 42 !! ()), 43; |
| my @z = 41, 42, 43; |
√ | is @y, @z, "sanity check"; |
× | is @x, @y, "if expr on true cond", :todo<feature>; |
| } |
| |
| { |
| my $answer = 0; |
| my @x = 41, eval q[(42 if $answer)], 43; |
| my @y = 41, ($answer ?? 42 !! ()), 43; |
| my @z = 41, 43; |
√ | is @y, @z, "sanity check"; |
× | is @x, @y, "if expr on false cond", :todo<feature>; |
| } |
From t/statements/modifiers/until.t lines 7–28 (3 √, 0 ×): (skip)
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5> |
| |
| # test the ``until'' statement modifier |
| { |
| my ($a, $b); |
| $a += $b += 1 until $b >= 10; |
√ | is($a, 55, "post until"); |
| } |
| |
| { |
| my @a = ('a', 'b', 'a'); |
| my $a = 'b'; |
| $a ~= ', ' ~ shift @a until !+@a; |
√ | is($a, "b, a, b, a", "post until"); |
| } |
| |
| { |
| my @a = 'a'..'e'; |
| my $a; |
| $a ++ until shift(@a) eq 'c'; |
√ | is($a, 2, "post until"); |
| } |
From t/statements/modifiers/for.t lines 7–69 (8 √, 0 ×): (skip)
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5> |
| |
| # test the for statement modifier |
| { |
| my $a; |
| $a ~= $_ for ('a', 'b', 'a', 'b', 'a'); |
√ | is($a, "ababa", "post for with parens"); |
| } |
| |
| # without parens |
| { |
| my $a; |
| $a ~= $_ for 'a', 'b', 'a', 'b', 'a'; |
√ | is($a, "ababa", "post for without parens"); |
| } |
| |
| { |
| my $a; |
| $a += $_ for (1 .. 10); |
√ | is($a, 55, "post for 1 .. 10 with parens"); |
| } |
| |
| { |
| my $a; |
| $a += $_ for 1 .. 10; |
√ | is($a, 55, "post for 1 .. 10 without parens"); |
| } |
| |
| { |
| my @a = (5, 7, 9); |
| my $a = 3; |
| $a *= $_ for @a; |
√ | is($a, 3 * 5 * 7 * 9, "post for array"); |
| } |
| |
| { |
| my @a = (5, 7, 9); |
| my $i = 5; |
| my sub check(Int $n){ |
√ | is($n, $i, "sub Int with post for"); |
| $i += 2; |
| } |
| check $_ for @a; |
| } |
| |
| # The following tests are all legal syntactically, but neither |
| # of these do anything other than produce a closure muliple |
| # times without calling it. |
| # See Larry's clarification on p6l: |
| # L<http://www.nntp.perl.org/group/perl.perl6.language/26071> |
| |
| { |
| my $a; |
| { $a++ } for 1..3; |
√ | is $a, undef, 'the closure was never called'; |
| } |
| |
| { |
| my $a; |
| -> $i { $a += $i } for 1..3; |
√ | is $a, undef, 'the closure was never called'; |
| } |
| |
From t/statements/modifiers/given.t lines 7–19 (2 √, 0 ×): (skip)
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5> |
| |
| # test the ``given'' statement modifier |
| { |
| my $a = $_ given 2 * 3; |
√ | is($a, 6, "post given"); |
| } |
| |
| { |
| my $a = $_ given 'a'; |
√ | is($a, 'a', "post given"); |
| } |
| |
From t/statements/modifiers/if.t lines 7–20 (2 √, 0 ×): (skip)
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5> |
| |
| # test the if statement modifier |
| { |
| my $a = 1; |
| $a = 2 if 'a' eq 'a'; |
√ | is($a, 2, "post if"); |
| } |
| |
| { |
| my $a = 1; |
| $a = 3 if 'a' eq 'b'; |
√ | is($a, 1, "post if"); |
| } |
From t/statements/modifiers/while.t lines 7–29 (3 √, 0 ×): (skip)
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5> |
| |
| # test the ``while'' statement modifier |
| { |
| my $a; |
| my $b; |
| $a += $b += 1 while $b < 10; |
√ | is($a, 55, "post while"); |
| } |
| |
| { |
| my @a = 'b'..'d'; |
| my $a = 'a'; |
| $a ~= ', ' ~ shift @a while @a; |
√ | is($a, "a, b, c, d", "post while"); |
| } |
| |
| { |
| my @a = 'a'..'e'; |
| my $a; |
| ++$a while shift(@a) ne 'd'; |
√ | is($a, 3, "post while"); |
| } |
@x = 41, (42 if $answer), 43;
and that is equivalent to:
@x = 41, ($answer ?? 42 !! ()), 43
Looping statement modifiers are the same as in Perl 5 except that, for ease of writing list comprehensions, a looping statement modifier is allowed to contain a single conditional statement modifier:
@evens = ($_ * 2 if .odd for 0..100);
Loop modifiers next, last, and redo also work as in Perl 5. However, the labelled forms use method call syntax: LABEL.next, etc. The .next and .last methods take an optional argument giving the final value of that loop iteration. So the old next LINE syntax is still allowed but is really short for next LINE: using indirect object syntax. Any block object can be used, not just labels, so to return a value from this iteration of the current block you can say:
From t/statements/next.t lines 6–149 (11 √, 1 ×): (skip)
| L<S04/"Loop statements"/next> |
| next |
| next if <condition>; |
| <condition> and next; |
| next <label>; |
| next in nested loops |
| next <label> in nested loops |
| |
| =cut |
| |
| plan 12; |
| |
| # test for loops with next |
| |
| { |
| my $tracker=0;for (1..2) { next; $tracker++;} |
√ | is( |
| $tracker, |
| 0, |
| "tracker is 0 because next before increment", |
| ); |
| } |
| |
| { |
| my $tracker = 0; for (1..5) { next if 2 < $_ < 4; $tracker = $_;} |
× | is( |
| $tracker, |
| 3, |
| "... nothing before or after 3 (next if <cond>)", |
| :todo<bug> |
| ); |
| } |
| |
| { |
| my $tracker = 0; for (1..5) { $_ > 3 && next; $tracker = $_;} |
√ | is( |
| $tracker, |
| 3, |
| "... nothing after 3 (<cond> && next)", |
| ); |
| } |
| |
| { |
| my $tracker = 0; for (1..5) { $_ > 3 and next; $tracker = $_;} |
√ | is( |
| $tracker, |
| 3, |
| "... nothing after 3 (<cond> and next)", |
| ); |
| } |
| |
| { |
| my $tracker="err"; eval '$tracker = 0; DONE: for (1..2) { next DONE; $tracker++;}'; |
√ | is( |
| $tracker, |
| 0, |
| "tracker is 0 because next before increment", |
| ); |
| } |
| |
| { |
| my $tracker=0;for (1..5)->$out {for (10..11)->$in {next if $out > 2;$tracker = $in + $out;}} |
√ | is($tracker, |
| 13, |
| 'inner loop skips once inner is run twice (next inside nested loops)', |
| ); |
| } |
| |
| { |
| my $tracker="err"; eval '$tracker = 0; OUT: for (1..2) { IN: for (1..2) { next OUT; $tracker++; } }'; |
√ | is( |
| $tracker, |
| 0, |
| "tracker is 0 because next before increment in nested loop", |
| ); |
| } |
| |
| =pod |
| |
| Check that C<next> works on the correct loop/block |
| |
| =cut |
| |
| { |
| my $foo; |
| for 1..2 -> $a { |
| $foo ~= "A"; |
| for 1..2 -> $b { |
| $foo ~= "B"; |
| next; # works on higher level loop, should work on inner |
| } |
| } |
√ | is($foo, "ABBABB", "next works on inner loop of 2"); |
| } |
| |
| { |
| my $bar; |
| for 1..2 -> $a { |
| $bar ~= "A"; |
| for 1..2 -> $b { |
| $bar ~= "B"; |
| for 1..2 -> $c { |
| $bar ~= "C"; |
| next; # same thing |
| } |
| } |
| } |
√ | is($bar, "ABCCBCCABCCBCC", "next works on inner loop of 3"); |
| } |
| |
| { |
| my @log; |
| my $i; |
| while ++$i < 2 { |
| push @log, "before"; |
| next; |
| push @log, "after"; |
| } |
| |
√ | is(~@log, "before", "statements after next are not executed"); |
| } |
| |
| { |
| my $i = 0; |
| |
| for (1, 1, 0, 1, 0, 1) -> $x { |
| if ($x) { next } |
| $i++; |
| } |
| |
√ | is($i, 2, '$i++ executed only twice, because next ') |
| } |
| |
| { |
| my $i = 0; |
| my $j; |
| |
| loop ($j = 0; $j < 6; $j++) { |
| if ($j % 2 == 0) { next } |
| $i++; |
| } |
| |
√ | is($i, 3, '$i++ was not executed when next was called before it in loop {}'); |
| } |
From t/statements/redo.t lines 4–104 (9 √, 1 ×): (skip)
| # L<S04/"Loop statements"/redo> |
| plan 10; |
| |
| { |
| my $i = 0; |
| while (defined($i)) { if (++$i < 3) { redo }; last } |
√ | is($i, 3, "redo caused reapplication of block"); |
| } |
| |
| { |
| my @log; |
| my $i; |
| while ++$i < 5 { |
| push @log, "before"; |
| if (++$i == 2) { |
| redo; |
| } else { |
| push @log, "no_redo"; |
| } |
| push @log, "after"; |
| } |
| |
√ | is(~@log, "before before no_redo after before no_redo after", "statements after redo are not executed"); |
| } |
| |
| { |
| my $i = 0; |
| my $j = 0; |
| |
| for (1, 0) -> $x { |
| if ($x && (++$i % 2 == 0)) { redo }; |
| $j++; |
| } |
| |
√ | is($j, 2, '$j++ encountered twice'); |
√ | is($i, 1, '$i++ encountered once'); |
| } |
| |
| |
| { |
| my $i = 0; |
| my $j = 0; |
| |
| for (1, 0, 1, 0) -> $x { |
| if ($x && (++$i % 2 == 0)) { redo }; |
| $j++; |
| } |
| |
√ | is($j, 4, '$j++ encountered four times'); |
√ | is($i, 3, '$i++ encountered three times'); |
| } |
| |
| |
| { |
| my $i = 0; |
| my $j; |
| |
| loop ($j = 0; $j < 4; $j++) { |
| if ($j % 2 == 0 and $i++ % 2 == 0) { redo } |
| $i-=2; |
| } |
| |
√ | is($j, 4, '$j unaltered by the fiasco'); |
√ | is($i, -4, '$i incremented and decremented correct number of times'); |
| } |
| |
| { |
| # rubicon TestLoopStuff.rb |
| # def testRedoWithFor |
| # sum = 0 |
| # for i in 1..10 |
| # sum += i |
| # i -= 1 |
| # if i > 0 |
| # redo |
| # end |
| # end |
| # assert_equal(220, sum) |
| # end |
| my $stopping = 100; |
| my $sum = 0; |
| for 1..10 -> $i is copy { |
| $sum += $i; |
| $i -= 1; |
| last if !$stopping--; |
| if $i > 0 { redo } |
| } |
× | is($sum, 220, "testRedoWithFor", :todo<bug>); |
| |
| $stopping = 100; |
| $sum = 0; |
| my $j = 1; |
| my $i; |
| while do{$i = $j; $j++ <= 10} { |
| $sum += $i; |
| $i -= 1; |
| last if !$stopping--; |
| if $i > 0 { redo } |
| } |
√ | is($sum, 220, "test redo with while"); |
| } |
From t/statements/last.t lines 6–91 (6 √, 2 ×): (skip)
| L<S04/"Loop statements"/last> |
| last |
| last if <condition>; |
| <condition> and last; |
| last <label>; |
| last in nested loops |
| last <label> in nested loops |
| |
| =cut |
| |
| plan 8; |
| |
| # test for loops with last |
| |
| { |
√ | is( |