зеркало из https://github.com/github/ruby.git
640 строки
16 KiB
Plaintext
640 строки
16 KiB
Plaintext
= Control Expressions
|
|
|
|
Ruby has a variety of ways to control execution. All the expressions described
|
|
here return a value.
|
|
|
|
For the tests in these control expressions, +nil+ and +false+ are false-values
|
|
and +true+ and any other object are true-values. In this document "true" will
|
|
mean "true-value" and "false" will mean "false-value".
|
|
|
|
== +if+ Expression
|
|
|
|
The simplest +if+ expression has two parts, a "test" expression and a "then"
|
|
expression. If the "test" expression evaluates to a true then the "then"
|
|
expression is evaluated.
|
|
|
|
Here is a simple if statement:
|
|
|
|
if true then
|
|
puts "the test resulted in a true-value"
|
|
end
|
|
|
|
This will print "the test resulted in a true-value".
|
|
|
|
The +then+ is optional:
|
|
|
|
if true
|
|
puts "the test resulted in a true-value"
|
|
end
|
|
|
|
This document will omit the optional +then+ for all expressions as that is the
|
|
most common usage of +if+.
|
|
|
|
You may also add an +else+ expression. If the test does not evaluate to true
|
|
the +else+ expression will be executed:
|
|
|
|
if false
|
|
puts "the test resulted in a true-value"
|
|
else
|
|
puts "the test resulted in a false-value"
|
|
end
|
|
|
|
This will print "the test resulted in a false-value".
|
|
|
|
You may add an arbitrary number of extra tests to an if expression using
|
|
+elsif+. An +elsif+ executes when all tests above the +elsif+ are false.
|
|
|
|
a = 1
|
|
|
|
if a == 0
|
|
puts "a is zero"
|
|
elsif a == 1
|
|
puts "a is one"
|
|
else
|
|
puts "a is some other value"
|
|
end
|
|
|
|
This will print "a is one" as <code>1</code> is not equal to <code>0</code>.
|
|
Since +else+ is only executed when there are no matching conditions.
|
|
|
|
Once a condition matches, either the +if+ condition or any +elsif+ condition,
|
|
the +if+ expression is complete and no further tests will be performed.
|
|
|
|
Like an +if+, an +elsif+ condition may be followed by a +then+.
|
|
|
|
In this example only "a is one" is printed:
|
|
|
|
a = 1
|
|
|
|
if a == 0
|
|
puts "a is zero"
|
|
elsif a == 1
|
|
puts "a is one"
|
|
elsif a >= 1
|
|
puts "a is greater than or equal to one"
|
|
else
|
|
puts "a is some other value"
|
|
end
|
|
|
|
The tests for +if+ and +elsif+ may have side-effects. The most common use of
|
|
side-effect is to cache a value into a local variable:
|
|
|
|
if a = object.some_value
|
|
# do something to a
|
|
end
|
|
|
|
The result value of an +if+ expression is the last value executed in the
|
|
expression.
|
|
|
|
== Ternary if
|
|
|
|
You may also write a if-then-else expression using <code>?</code> and
|
|
<code>:</code>. This ternary if:
|
|
|
|
input_type = gets =~ /hello/i ? "greeting" : "other"
|
|
|
|
Is the same as this +if+ expression:
|
|
|
|
input_type =
|
|
if gets =~ /hello/i
|
|
"greeting"
|
|
else
|
|
"other"
|
|
end
|
|
|
|
While the ternary if is much shorter to write than the more verbose form, for
|
|
readability it is recommended that the ternary if is only used for simple
|
|
conditionals. Also, avoid using multiple ternary conditions in the same
|
|
expression as this can be confusing.
|
|
|
|
== +unless+ Expression
|
|
|
|
The +unless+ expression is the opposite of the +if+ expression. If the value
|
|
is false, the "then" expression is executed:
|
|
|
|
unless true
|
|
puts "the value is a false-value"
|
|
end
|
|
|
|
This prints nothing as true is not a false-value.
|
|
|
|
You may use an optional +then+ with +unless+ just like +if+.
|
|
|
|
Note that the above +unless+ expression is the same as:
|
|
|
|
if not true
|
|
puts "the value is a false-value"
|
|
end
|
|
|
|
Like an +if+ expression you may use an +else+ condition with +unless+:
|
|
|
|
unless true
|
|
puts "the value is false"
|
|
else
|
|
puts "the value is true"
|
|
end
|
|
|
|
This prints "the value is true" from the +else+ condition.
|
|
|
|
You may not use +elsif+ with an +unless+ expression.
|
|
|
|
The result value of an +unless+ expression is the last value executed in the
|
|
expression.
|
|
|
|
== Modifier +if+ and +unless+
|
|
|
|
+if+ and +unless+ can also be used to modify an expression. When used as a
|
|
modifier the left-hand side is the "then" statement and the right-hand side
|
|
is the "test" expression:
|
|
|
|
a = 0
|
|
|
|
a += 1 if a.zero?
|
|
|
|
p a
|
|
|
|
This will print 1.
|
|
|
|
a = 0
|
|
|
|
a += 1 unless a.zero?
|
|
|
|
p a
|
|
|
|
This will print 0.
|
|
|
|
While the modifier and standard versions have both a "test" expression and a
|
|
"then" statement, they are not exact transformations of each other due to
|
|
parse order. Here is an example that shows the difference:
|
|
|
|
p a if a = 0.zero?
|
|
|
|
This raises the NameError "undefined local variable or method `a'".
|
|
|
|
When ruby parses this expression it first encounters +a+ as a method call in
|
|
the "then" expression, then later it sees the assignment to +a+ in the "test"
|
|
expression and marks +a+ as a local variable.
|
|
|
|
When running this line it first executes the "test" expression, <code>a =
|
|
0.zero?</code>.
|
|
|
|
Since the test is true it executes the "then" expression, <code>p a</code>.
|
|
Since the +a+ in the body was recorded as a method which does not exist the
|
|
NameError is raised.
|
|
|
|
The same is true for +unless+.
|
|
|
|
== +case+ Expression
|
|
|
|
The +case+ expression can be used in two ways.
|
|
|
|
The most common way is to compare an object against multiple patterns. The
|
|
patterns are matched using the <tt>===</tt> method which is aliased to <tt>==</tt> on
|
|
Object. Other classes must override it to give meaningful behavior. See
|
|
Module#=== and Regexp#=== for examples.
|
|
|
|
Here is an example of using +case+ to compare a String against a pattern:
|
|
|
|
case "12345"
|
|
when /^1/
|
|
puts "the string starts with one"
|
|
else
|
|
puts "I don't know what the string starts with"
|
|
end
|
|
|
|
Here the string <code>"12345"</code> is compared with <code>/^1/</code> by
|
|
calling <code>/^1/ === "12345"</code> which returns +true+. Like the +if+
|
|
expression, the first +when+ that matches is executed and all other matches are
|
|
ignored.
|
|
|
|
If no matches are found, the +else+ is executed.
|
|
|
|
The +else+ and +then+ are optional, this +case+ expression gives the same
|
|
result as the one above:
|
|
|
|
case "12345"
|
|
when /^1/
|
|
puts "the string starts with one"
|
|
end
|
|
|
|
You may place multiple conditions on the same +when+:
|
|
|
|
case "2"
|
|
when /^1/, "2"
|
|
puts "the string starts with one or is '2'"
|
|
end
|
|
|
|
Ruby will try each condition in turn, so first <code>/^1/ === "2"</code>
|
|
returns +false+, then <code>"2" === "2"</code> returns +true+, so "the string
|
|
starts with one or is '2'" is printed.
|
|
|
|
You may use +then+ after the +when+ condition. This is most frequently used
|
|
to place the body of the +when+ on a single line.
|
|
|
|
case a
|
|
when 1, 2 then puts "a is one or two"
|
|
when 3 then puts "a is three"
|
|
else puts "I don't know what a is"
|
|
end
|
|
|
|
The other way to use a +case+ expression is like an if-elsif expression:
|
|
|
|
a = 2
|
|
|
|
case
|
|
when a == 1, a == 2
|
|
puts "a is one or two"
|
|
when a == 3
|
|
puts "a is three"
|
|
else
|
|
puts "I don't know what a is"
|
|
end
|
|
|
|
Again, the +then+ and +else+ are optional.
|
|
|
|
The result value of a +case+ expression is the last value executed in the
|
|
expression.
|
|
|
|
Since Ruby 2.7, +case+ expressions also provide a more powerful experimental
|
|
pattern matching feature via the +in+ keyword:
|
|
|
|
case {a: 1, b: 2, c: 3}
|
|
in a: Integer => m
|
|
"matched: #{m}"
|
|
else
|
|
"not matched"
|
|
end
|
|
# => "matched: 1"
|
|
|
|
The pattern matching syntax is described on
|
|
{its own page}[rdoc-ref:syntax/pattern_matching.rdoc].
|
|
|
|
== +while+ Loop
|
|
|
|
The +while+ loop executes while a condition is true:
|
|
|
|
a = 0
|
|
|
|
while a < 10 do
|
|
p a
|
|
a += 1
|
|
end
|
|
|
|
p a
|
|
|
|
Prints the numbers 0 through 10. The condition <code>a < 10</code> is checked
|
|
before the loop is entered, then the body executes, then the condition is
|
|
checked again. When the condition results in false the loop is terminated.
|
|
|
|
The +do+ keyword is optional. The following loop is equivalent to the loop
|
|
above:
|
|
|
|
while a < 10
|
|
p a
|
|
a += 1
|
|
end
|
|
|
|
The result of a +while+ loop is +nil+ unless +break+ is used to supply a
|
|
value.
|
|
|
|
== +until+ Loop
|
|
|
|
The +until+ loop executes while a condition is false:
|
|
|
|
a = 0
|
|
|
|
until a > 10 do
|
|
p a
|
|
a += 1
|
|
end
|
|
|
|
p a
|
|
|
|
This prints the numbers 0 through 11. Like a while loop the condition <code>a
|
|
> 10</code> is checked when entering the loop and each time the loop body
|
|
executes. If the condition is false the loop will continue to execute.
|
|
|
|
Like a +while+ loop, the +do+ is optional.
|
|
|
|
Like a +while+ loop, the result of an +until+ loop is nil unless +break+ is
|
|
used.
|
|
|
|
== +for+ Loop
|
|
|
|
The +for+ loop consists of +for+ followed by a variable to contain the
|
|
iteration argument followed by +in+ and the value to iterate over using #each.
|
|
The +do+ is optional:
|
|
|
|
for value in [1, 2, 3] do
|
|
puts value
|
|
end
|
|
|
|
Prints 1, 2 and 3.
|
|
|
|
Like +while+ and +until+, the +do+ is optional.
|
|
|
|
The +for+ loop is similar to using #each, but does not create a new variable
|
|
scope.
|
|
|
|
The result value of a +for+ loop is the value iterated over unless +break+ is
|
|
used.
|
|
|
|
The +for+ loop is rarely used in modern ruby programs.
|
|
|
|
== Modifier +while+ and +until+
|
|
|
|
Like +if+ and +unless+, +while+ and +until+ can be used as modifiers:
|
|
|
|
a = 0
|
|
|
|
a += 1 while a < 10
|
|
|
|
p a # prints 10
|
|
|
|
+until+ used as a modifier:
|
|
|
|
a = 0
|
|
|
|
a += 1 until a > 10
|
|
|
|
p a # prints 11
|
|
|
|
You can use +begin+ and +end+ to create a +while+ loop that runs the body once
|
|
before the condition:
|
|
|
|
a = 0
|
|
|
|
begin
|
|
a += 1
|
|
end while a < 10
|
|
|
|
p a # prints 10
|
|
|
|
If you don't use +rescue+ or +ensure+, Ruby optimizes away any exception
|
|
handling overhead.
|
|
|
|
== +break+ Statement
|
|
|
|
Use +break+ to leave a block early. This will stop iterating over the items in +values+ if one of them is even:
|
|
|
|
values.each do |value|
|
|
break if value.even?
|
|
|
|
# ...
|
|
end
|
|
|
|
You can also terminate from a +while+ loop using +break+:
|
|
|
|
a = 0
|
|
|
|
while true do
|
|
p a
|
|
a += 1
|
|
|
|
break if a < 10
|
|
end
|
|
|
|
p a
|
|
|
|
This prints the numbers 0 and 1.
|
|
|
|
+break+ accepts a value that supplies the result of the expression it is
|
|
"breaking" out of:
|
|
|
|
result = [1, 2, 3].each do |value|
|
|
break value * 2 if value.even?
|
|
end
|
|
|
|
p result # prints 4
|
|
|
|
== +next+ Statement
|
|
|
|
Use +next+ to skip the rest of the current iteration:
|
|
|
|
result = [1, 2, 3].map do |value|
|
|
next if value.even?
|
|
|
|
value * 2
|
|
end
|
|
|
|
p result # prints [2, nil, 6]
|
|
|
|
+next+ accepts an argument that can be used as the result of the current
|
|
block iteration:
|
|
|
|
result = [1, 2, 3].map do |value|
|
|
next value if value.even?
|
|
|
|
value * 2
|
|
end
|
|
|
|
p result # prints [2, 2, 6]
|
|
|
|
== +redo+ Statement
|
|
|
|
Use +redo+ to redo the current iteration:
|
|
|
|
result = []
|
|
|
|
while result.length < 10 do
|
|
result << result.length
|
|
|
|
redo if result.last.even?
|
|
|
|
result << result.length + 1
|
|
end
|
|
|
|
p result
|
|
|
|
This prints [0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11]
|
|
|
|
In Ruby 1.8, you could also use +retry+ where you used +redo+. This is no
|
|
longer true, now you will receive a SyntaxError when you use +retry+ outside
|
|
of a +rescue+ block. See {Exceptions}[rdoc-ref:syntax/exceptions.rdoc]
|
|
for proper usage of +retry+.
|
|
|
|
== Modifier Statements
|
|
|
|
Ruby's grammar differentiates between statements and expressions. All
|
|
expressions are statements (an expression is a type of statement), but
|
|
not all statements are expressions. Some parts of the grammar accept
|
|
expressions and not other types of statements, which causes code that
|
|
looks similar to be parsed differently.
|
|
|
|
For example, when not used as a modifier, +if+, +else+, +while+, +until+,
|
|
and +begin+ are expressions (and also statements). However, when
|
|
used as a modifier, +if+, +else+, +while+, +until+ and +rescue+
|
|
are statements but not expressions.
|
|
|
|
if true; 1 end # expression (and therefore statement)
|
|
1 if true # statement (not expression)
|
|
|
|
Statements that are not expressions cannot be used in contexts where an
|
|
expression is expected, such as method arguments.
|
|
|
|
puts( 1 if true ) #=> SyntaxError
|
|
|
|
You can wrap a statement in parentheses to create an expression.
|
|
|
|
puts((1 if true)) #=> 1
|
|
|
|
If you put a space between the method name and opening parenthesis, you
|
|
do not need two sets of parentheses.
|
|
|
|
puts (1 if true) #=> 1, because of optional parentheses for method
|
|
|
|
This is because this is parsed similar to a method call without
|
|
parentheses. It is equivalent to the following code, without the creation
|
|
of a local variable:
|
|
|
|
x = (1 if true)
|
|
p x
|
|
|
|
In a modifier statement, the left-hand side must be a statement and the
|
|
right-hand side must be an expression.
|
|
|
|
So in <code>a if b rescue c</code>, because <code>b rescue c</code> is a
|
|
statement that is not an expression, and therefore is not allowed as the
|
|
right-hand side of the +if+ modifier statement, the code is necessarily
|
|
parsed as <code>(a if b) rescue c</code>.
|
|
|
|
This interacts with operator precedence in such a way that:
|
|
|
|
stmt if v = expr rescue x
|
|
stmt if v = expr unless x
|
|
|
|
are parsed as:
|
|
|
|
stmt if v = (expr rescue x)
|
|
(stmt if v = expr) unless x
|
|
|
|
This is because modifier +rescue+ has higher precedence than <code>=</code>,
|
|
and modifier +if+ has lower precedence than <code>=</code>.
|
|
|
|
== Flip-Flop
|
|
|
|
The flip-flop is a slightly special conditional expression. One of its
|
|
typical uses is processing text from ruby one-line programs used with
|
|
<code>ruby -n</code> or <code>ruby -p</code>.
|
|
|
|
The form of the flip-flop is an expression that indicates when the
|
|
flip-flop turns on, <code>..</code> (or <code>...</code>), then an expression
|
|
that indicates when the flip-flop will turn off. While the flip-flop is on it
|
|
will continue to evaluate to +true+, and +false+ when off.
|
|
|
|
Here is an example:
|
|
|
|
selected = []
|
|
|
|
0.upto 10 do |value|
|
|
selected << value if value==2..value==8
|
|
end
|
|
|
|
p selected # prints [2, 3, 4, 5, 6, 7, 8]
|
|
|
|
In the above example, the `on' condition is <code>n==2</code>. The flip-flop
|
|
is initially `off' (false) for 0 and 1, but becomes `on' (true) for 2 and
|
|
remains `on' through 8. After 8 it turns off and remains `off' for 9 and 10.
|
|
|
|
The flip-flop must be used inside a conditional such as <code>!</code>,
|
|
<code>? :</code>, +not+, +if+, +while+, +unless+, +until+ etc. including the
|
|
modifier forms.
|
|
|
|
When you use an inclusive range (<code>..</code>), the `off' condition is
|
|
evaluated when the `on' condition changes:
|
|
|
|
selected = []
|
|
|
|
0.upto 5 do |value|
|
|
selected << value if value==2..value==2
|
|
end
|
|
|
|
p selected # prints [2]
|
|
|
|
Here, both sides of the flip-flop are evaluated so the flip-flop turns on and
|
|
off only when +value+ equals 2. Since the flip-flop turned on in the
|
|
iteration it returns true.
|
|
|
|
When you use an exclusive range (<code>...</code>), the `off' condition is
|
|
evaluated on the following iteration:
|
|
|
|
selected = []
|
|
|
|
0.upto 5 do |value|
|
|
selected << value if value==2...value==2
|
|
end
|
|
|
|
p selected # prints [2, 3, 4, 5]
|
|
|
|
Here, the flip-flop turns on when +value+ equals 2, but doesn't turn off on the
|
|
same iteration. The `off' condition isn't evaluated until the following
|
|
iteration and +value+ will never be two again.
|
|
|
|
== throw/catch
|
|
|
|
+throw+ and +catch+ are used to implement non-local control flow in Ruby. They
|
|
operate similarly to exceptions, allowing control to pass directly from the
|
|
place where +throw+ is called to the place where the matching +catch+ is
|
|
called. The main difference between +throw+/+catch+ and the use of exceptions
|
|
is that +throw+/+catch+ are designed for expected non-local control flow,
|
|
while exceptions are designed for exceptional control flow situations, such
|
|
as handling unexpected errors.
|
|
|
|
When using +throw+, you provide 1-2 arguments. The first argument is the
|
|
value for the matching +catch+. The second argument is optional (defaults to
|
|
+nil+), and will be the value that +catch+ returns if there is a matching
|
|
+throw+ inside the +catch+ block. If no matching +throw+ method is called
|
|
inside a +catch+ block, the +catch+ method returns the return value of the
|
|
block passed to it.
|
|
|
|
def a(n)
|
|
throw :d, :a if n == 0
|
|
b(n)
|
|
end
|
|
|
|
def b(n)
|
|
throw :d, :b if n == 1
|
|
c(n)
|
|
end
|
|
|
|
def c(n)
|
|
throw :d if n == 2
|
|
end
|
|
|
|
4.times.map do |i|
|
|
catch(:d) do
|
|
a(i)
|
|
:default
|
|
end
|
|
end
|
|
# => [:a, :b, nil, :default]
|
|
|
|
If the first argument you pass to +throw+ is not handled by a matching
|
|
+catch+, an UncaughtThrowError exception will be raised. This is because
|
|
+throw+/+catch+ should only be used for expected control flow changes, so
|
|
using a value that is not already expected is an error.
|
|
|
|
+throw+/+catch+ are implemented as Kernel methods (Kernel#throw and
|
|
Kernel#catch), not as keywords. So they are not usable directly if you are
|
|
in a BasicObject context. You can use Kernel.throw and Kernel.catch in
|
|
this case:
|
|
|
|
BasicObject.new.instance_exec do
|
|
def a
|
|
b
|
|
end
|
|
|
|
def b
|
|
c
|
|
end
|
|
|
|
def c
|
|
::Kernel.throw :d, :e
|
|
end
|
|
|
|
result = ::Kernel.catch(:d) do
|
|
a
|
|
end
|
|
result # => :e
|
|
end
|