Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Document if/until/else/elsif
Add some syntax notes and a lead-in to section on "do"
Could not paper over the Rakudo "Nil" fossil, so put in some caveats
   (those will have to be pulled later.)
  • Loading branch information
skids committed Apr 17, 2015
1 parent dd04158 commit 69e5dbb
Showing 1 changed file with 186 additions and 8 deletions.
194 changes: 186 additions & 8 deletions lib/Language/control.pod
Expand Up @@ -25,13 +25,36 @@ Most of the flow control constructs covered below are just ways to
tell perl6 when, how, and how many times, to enter blocks like that
second block.
Before we go into those, an important side-note on syntax: If there is
nothing (or nothing but comments) on a line after a closing curly brace where
you would normally put semicolon, then you do not need the semicolon:
# All three of these lines can appear as a group, as is, in a program
{ 42.say } #-> says "42"
{ 43.say } #-> says "43"
{ 42.say }; { 43.say } #-> says "42" then says "43"
...but:
{ 42.say } { 43.say } #-> Syntax error
{ 42.say; } { 43.say } #-> Also a syntax error, of course
So, be careful when you backspace in a line-wrapping editor:
{ "Without semicolons line-wrapping can be a bit treacherous.".say } \
{ 43.say } #-> Syntax error
You have to watch out for this in most languages anyway to prevent things
from getting accidentally commented out. Many of the examples below may
have unnecessary semicolons for clarity.
=head2 do
The simplest way to run a block where it cannot be a stand-alone statement
is by writing C<do> before it:
# This dies half of the time
do { say "Heads I win, tails I die."; Bool.pick } or die; say "I win."
do { say "Heads I win, tails I die."; Bool.pick } or die; say "I win.";
Note that you need a space between the do and the block.
Expand All @@ -51,17 +74,174 @@ In other words, it follows the same reification rules as everything else.
Technically, C<do> is a loop which runs exactly one iteration.
A C<do> may actually also be used on a statement (without brackets)
A C<do> may also be used on a bare statement (without brackets)
but this is mainly just useful for avoiding the syntactical need to
parenthesize a statement if it is the last thing in an expression:
1, do if (1) { 2 } ; #-> 1, 2
1, (if (1) { 2 }) ; #-> 1, 2
1, if (1) { 2 } ; # Syntax error
3, do if 1 { 2 } ; #-> 3, 2
3, (if 1 { 2 }) ; #-> 3, 2
3, if 1 { 2 } ; # Syntax error
...which brings us to C<if>.
=head2 if
To conditionally run a block of code, use a C<if> followed by a condition.
The condition, an expression, will be evaluated immediately after the
statement before the C<if> finishes. The block attached to the condition will
only be evaluated if the condition means True when coerced to C<Bool>.
Unlike some languages the condition does not have to be parenthesized,
instead the C<{> and C<}> around the block are mandatory:
if 1 { "1 is true".say } ; # says "1 is true"
if 1 "1 is true".say ; # syntax error, missing block
if 0 { "0 is true".say } ; # does not say anything, because 0 is false
if 42.say and 0 { 43.say }; # says "42" but does not say "43"
There is also a form of C<if> called a "statement modifier" form. In this
case, the if and then the condition come after the code you want to run
conditionally. Also, you should not use the C<{> and C<}>. Do note that the
condition is still always evaluated first:
43.say if 42.say and 0; # says "42" but does not say "43"
43.say if 42.say and 1; # says "42" and then says "43"
say "It is easier to read code when 'if's are kept on left of screen"
if True; # says the above, because it is true
{ 43.say } if True; # creates a closure, does not run it
The last case is one reason why we have the L<do> keyword. The statement
modifier form is probably best used sparingly.
The C<if> statement itself will either return an empty list, if it does not
run the block, or it will return the value which the block produces:
my $c = 0; say (1, (if 1 { $c += 42; 2; }), 3, $c); # says "1 2 3 42"
my $d = 0; say (1, (if 0 { $d += 42; 2; }), 3, $d); # says "1 3 0"
Implementation note: Currently, Rakudo will say "1 Nil 3 0" for the last
example because it is not caught up to this part of the design yet.
For the statement modifier it is the same, except you have the value
of the statement instead of a block:
say (1, (42 if True) , 2); # says "1 42 2"
say (1, (42 if False), 2); # says "1 2"
say (1, 42 if False , 2); # says "1 42" because "if False, 2" is true
Implementation note: Currently, Rakudo will say "1 Nil 2" for the second
example because it is not caught up to this part of the design yet.
The C<if> does not change the topic (C<$_>) by default. In order to access
the value which the conditional expression produced, you have to ask
for it more strongly:
$_ = 1; if 42 { $_.say } ; # says "1"
$_ = 1; if 42 -> $_ { $_.say } ; # says "42"
$_ = 1; if 42 -> $a { $_.say; $a.say } ; # says "1" then says "42"
$_ = 1; if 42 { $_.say; $^a.say } ; # says "1" then says "42"
=head3 else/elsif
A compound conditional may be produced by following an C<if> conditional
with C<else> to provide an alternative block to run when the conditional
expression is false:
if 0 { say "no" } else { say "yes" } ; # says "yes"
if 0 { say "no" } else{ say "yes" } ; # syntax error, space is required
The C<else> cannot be separated from the conditional statement by a
semicolon, but as a special case, it is OK to have a newline.
if 0 { say "no" }; else { say "yes" } ; # syntax error
if 0 { say "no" }
else { say "yes" } ; # says "yes"
Additional conditions may be sandwiched between the C<if> and the
C<else> using C<elsif>. An extra condition will only be evaluated
if all the conditions before it were false, and only the block next to
the first true condition will be run. You can end with an C<elsif>
instead of an C<else> if you want.
if 0 { say "no" } elsif False { say "NO" } else { say "yes" } # says "yes"
if 0 { say "no" } elsif True { say "YES" } else { say "yes" } # says "YES"
if 0 { say "no" } elsif False { say "NO" } # does not say anything
sub right { "Right!".say; True }
sub wrong { "Wrong!".say; False }
if wrong() { say "no" } elsif right() { say "yes" } else { say "maybe" }
# The above says "Wrong!" then says "Right!" then says "yes"
You cannot use the statement modifier form with C<else> or C<elsif>:
42.say if 0 else { 43.say } # syntax error
All the same rules for semicolons and newlines apply, consistenty:
if 0 { say 0 }; elsif 1 { say 1 } else { say "how?" } ; # syntax error
if 0 { say 0 } elsif 1 { say 1 }; else { say "how?" } ; # syntax error
if 0 { say 0 } elsif 1 { say 1 } else { say "how?" } ; # says "1"
if 0 { say 0 } elsif 1 { say 1 }
else { say "how?" } ; # says "1"
if 0 { say 0 }
elsif 1 { say 1 } else { say "how?" } ; # says "1"
if 0 { say "no" }
elsif False { say "NO" }
else { say "yes" } ; # -> says "yes"
The whole tamale produces either an empty list (if no blocks were run)
or the value produced by the block which did run:
my $c = 0; say (1,
(if 0 { $c += 42; "two"; } else { $c += 43; 2; }),
3, $c); # says "1 2 3 43"
my $d = 0; say (1,
(if 0 { $d += 42; "two"; } elsif False { $d += 43; 2; }),
3, $d); # says "1 3 0"
Implementation note: Currently, Rakudo will say "1 Nil 3 0" for the last
example because it is not caught up to this part of the design yet.
Each block must individually say whether it wants to know what the
conditional expression evaluated to. In the case of an C<else> that
has one or more C<elsif>s in front of it, the last C<elsif> determines
what the C<else> sees, since it was the last one tried:
$_ = 1; if 0 { } else -> $a { "$_ $a".say } ; # says "1 0"
$_ = 1; if False { } else -> $a { "$_ $a".say } ; # says "1 False"
if False { } elsif 0 { } else -> $a { $a.say } ; # says "0"
=head3 unless
When you get sick of typing "if not (X)" you may use C<unless> to invert
the sense of a conditional statement. You cannot use C<else> or C<elsif>
with C<unless> because that ends up getting confusing. Other than those
two differences C<unless> works the same as L<if>:
unless 1 { "1 is false".say } ; #-> does not say anything, since 1 is true
unless 1 "1 is false".say ; #-> syntax error, missing block
unless 0 { "0 is false".say } ; #-> says "0 is false"
unless 42.say and 1 { 43.say } ; #-> says "42" but does not say "43"
43.say unless 42.say and 0; #-> says "42" and then says "43"
43.say unless 42.say and 1; #-> says "42" but does not say "43"
$_ = 1; unless 0 { $_.say } ; # -> says "1"
$_ = 1; unless 0 -> $_ { $_.say } ; # -> says "0"
$_ = 1; unless False -> $a { $a.say } ; # -> says "False"
my $c = 0; say (1, (unless 0 { $c += 42; 2; }), 3, $c); #-> says "1 2 3 42"
my $c = 0; say (1, (unless 1 { $c += 42; 2; }), 3, $c); #-> says "1 3 0"
Implementation note: Currently, Rakudo will say "1 Nil 3 0" for the last
example because it is not caught up to this part of the design yet.
=begin comment
=head2 if/elsif/else
=head2 for
Expand Down Expand Up @@ -233,8 +413,6 @@ This can also be written quite naturally with C<until>:
=begin comment
=head2 unless
=head2 while
=head2 return
Expand Down

0 comments on commit 69e5dbb

Please sign in to comment.