Flow Control
From RubySpec
Ruby provides most standard flow-control keywords as well as a few unusual (but frequently useful) keywords of its own.
Contents |
Flow control expressions
The conditionals comprise if, unless and case. Loop operations comprise while,
until, and for. The use of blocks (TODO: xref) allows easy creation of user- or library-defined flow control operations, see for example loop and each (TODO: xrefs).
In Ruby, conditional and looping ``statements are actually expressions, and return values.
Truth values
TODO: this may be in the wrong place, because it also applies to conditional expressions (? :). Maybe move it somewhere else?
Syntax
condition: expression
A number of the flow-control expressions include conditions. A condition is an expression that is required to evaluate
to a truthvalue, which is either true or false. A truthvalue is false if and only if its value is either false or nil. Anything else is considered to be true, including 0, [], and any other empty values.
Conditionals
if..elsif..end / unless..end
Ruby features the standard if conditional in both expression and expression modifier form. unless condition is exactly equivalent to if not condition in all contexts.
An if/unless expression returns the value of its last expression.
Block form
Syntax
if condition [then] code [elsif condition [then] code] [else code] end
unless condition code [else code] end
Explanation
The basic expression form of if looks like this, and must end with an end:
if some_condition_expression # This section executed if some_condition_expression is true end
The else-keyword can be used to provide a section that will be executed if the condition expression evaluates to false. Only one else may appear in an if-expression:
if some_condition_expression # This section executed if some_condition_expression is true else # This section executed if some_condition_expression is false end
The elsif-keyword can be used to create multiple mutually exclusive conditionals. If the conditional in the initial if is not true, then each elsif conditional is evaluated in descending order until one is true. If none is, the else-section is executed if there is one:
if cond_one # cond_one is true elsif cond_two # cond_one is not true and cond_two is true elsif cond_three # cond_one is not true and cond_two is not true and cond_three is true else # cond_one, cond_two and cond_three are all not true end
With unless, it is not possible to define elsifs, just else.
The if and unless expressions may have an optional then added, but it is usually elided.
if some_condition_expression then # ... elsif some_other_condition_expression then # ... end unless some_condition_expression then # ... else some_other_condition_expression then # ... end
However, then is necessary if you want to keep the if or unless expression on a single line.
if some_condition_expression then do_something end unless some_condition_expression then do_something end
Ruby accepts a postfix notation for conditionals if and unless if you keep the if expression in a single line:
do_something if some_condition_expression do_something_else unless some_other_condition_expression
case...when...else...end
A case statement matches the result of the case expression against each of the results of the when expression using the === matching operator. There can be any number of when clauses, but only the body of the first match is evaluated. If no when clauses match, and the programmer has provided an else clause, that body is evaluated. If there is no match and no else clause, the case expression returns nil.
case degrees_in_kelvin when 0..273 puts "solid" when 274..372 puts "liquid" else puts "gas" end
The case...when..else..end construct determines matching with the === method. The default === method inherited from Object is an equality test using equal? which performs strict object equality. This method is implemented as an alias, however, so redefining the equal? method on a subclass will not change the behavior of the default inherited === method.
Many core classes like RegExp and Range provide === methods which match whole groups of objects.
case "hello" when /^ye/ puts "yee-haw" when /^he/ puts "hee-haw" end
When the match is performed, the object from the when expression is the invocant and the object from the case expression is the argument. From the example above, the successful match is found with the following expression: /^he/ === "hello".
Like most Ruby special forms, the case...when...else...end expression returns its result. This will be the result of the last expression evaluated inside the block
puts case x
when 0...10
puts "this is not the value"
0 # but this will be if x is in between 0 and 10
when 10...100
1
when 100...1000
2
end
Like the if statement, the when clause can take an optional then to separate it from the result expression. Also like if, the then keyword becomes required when the result is on the same line as the when clause:
case degrees_in_kelvin
when 0..273 then "solid"
when 274..372 then "liquid"
else "gas"
end
Looping
while/until
A while executes the body as long as the condition is true.
while test
puts "hello #{i}"
end
Its counterpart, until, iterates while the loop condition is false; thus the above is equivalent to
until !test
puts "hello#{i}"
end
Syntax
while-expression: while condition (do | newline) expressions end until-expression: until condition (do | newline) expressions end
Semantics
A while-expression is equivalent to the following
pseudocode, assuming a fictitious goto statement.
returnvalue = nil; L_cond: if !condition then goto L_finished; L_redo: expressions goto L_cond L_finished:
The identifiers returnvalue, L_cond,
L_redo and
L_finished are replaced by identifiers not used elsewhere in the
program.
An until-expression is equivalent to the following
pseudocode.
returnvalue = nil; L_cond: if condition then goto L_finished; L_redo: expressions goto L_cond L_finished:
The identifiers returnvalue, L_cond,
L_redo and
L_finished are replaced by identifiers not used elsewhere in the
program.
The value returned by a while- or
until-expression is nil unless a
break is executed inside the loop (TODO: xref).
TODO: I'm not wildly keen about using gotos here. It seems less offensive than defining loops in terms of recursive functions, though, so that's why I did it. I think the expansions I've shown here need begin/end around them to get the scopes right. End of TODO.
loop
TODO: maybe this should be in the description of Kernel,
with a cross-reference from here?
The Kernel method loop takes a block and no arguments. The block is invoked repeatedly until terminated by one of the standard ways iteration is halted (break, return, raise, throw, continuations). Iteration keywords behave as expected.
loop do puts "hi" end
For example, break can be used to add termination conditions.
loop do break if STDIN.eof? puts STDIN.gets end
It's important to remember that loop is not a Ruby keyword. It is a method. This means among other things that it can be redefined or overwritten.
Many languages like Smalltalk implement all their control structures using methods and blocks, but this isn't an option for Ruby which lacks a concise syntax for methods that take multiple block parameters.
for..in
The for..in construct can be used to iterate over any object that responds to the each method. The following two code snippets showing iteration over an Array are essentially equivalent:
for item in %w{Item1 Item2 Item3 Item4}
puts item
end
%w{Item1 Item2 Item3 Item4}.each do |item|
puts item
end
The one difference to note is the scope of the iterator variable. In the case of the for..in construct, the iterator variable will be created/modified in the local scope regardless of whether or not it had previously existed. In the case of the each method, the iterator variable will not be created in the local scope. However, if the iterator variable is the name of a variable that already exists in the local scope, that variable will be modified.
Multiple variables may be used with the for..in construct just as with the each method as the following example to iterate over a Hash shows:
for key, value in {'item1' => 1, 'item2' => 2, 'item3' => 3}
puts "#{key} => #{value}"
end
TODO: precise syntax and semantics
next, break, redo
Inside the body of a loop, the special control transfer instructions next, break, and redo can be used to control looping.
The next construct causes execution to be transferred back to the top of the enclosing loop with the loop condition being re-evaluated. The following example illustrates how this could be used to skip comment lines when parsing a File. When a line beginning with a # character is encountered, the next construct causes the for loop to jump back to the top and start processing the next line from the file.
The redo construct causes execution to be transferred back to the top of the enclosing loop just like the next construct. However, unlike the next construct, the redo construct does not cause the loop condition to be re-evaluated. The following example illustrates how this could be used to re-process a line when parsing a File. When a line beginning with ERR: is found, the redo construct causes the for loop to jump back to the top and re-process the current line after ERR has been replaced by ERROR (not how you would do it... but I can't think of a better example for redo at the moment).
The break construct causes the enclosing loop to terminate immediately. The following example illustrates how this could be used to terminate a loop if an error line is found when parsing a File. When a line starting with ERROR: is found, the break construct is used to terminate the for loop.
File.open('test.txt') do |f|
for line in f
next if /^#/ =~ line
break if /^ERROR:/ =~ line
if /^ERR:/ =~ line
line.gsub!(/^ERR/, 'ERROR')
redo
end
puts line
end
end
Syntax
break-expression: break [expression] next-expression: next redo-expression: redo
Semantics
A break-expression inside a loop is executed as though it were
returnvalue = expression; goto L_finished
If no expression is provided, no assignment is executed (thus the loop's return value is nil).
A next expression inside a loop is executed as though it were
goto L_cond
A redo expression inside a loop is executed as though it were
goto L_redo
Exception handling
begin...rescue...else...ensure...end
A begin...end block can offset a section of code. It is typically used for exception handling.
TODO: there are at least 3 different uses for begin/end: creating local scope, begin...end while condition, and exception handling. Maybe they should be presented separately? Suggestion: syntax as in following block
begin-expression: begin expressions [exception-machinery] end exception-machinery: rescue except-spec [else expressions] [ensure expressions]
End of TODO
begin puts "a bare begin/end alone isn't terribly useful' end
begin
puts "however when used with a rescue, exception handling results"
raise Exception.new("my exception")
rescue Exception => e
puts e.backtrace
end
A begin...end block can stand alone, can be written with one or more rescue blocks, and can contain one or both of the optional else and ensure blocks.
begin
puts "ensure blocks are useful for cleaning up resources regardless of errors that may occur"
f = File.open("myfile")
raise Exception.new
rescue NameError => n
# no NameError raised, so we'll not get here
else
# we'll only get here if the main block executes without exception
ensure
# but we'll always get here, unless the interpreter or thread terminates
f.close
end
Note that Ruby's standard scoping for local variables holds for begin...end blocks as well: there is only one scope in a given method (not counting block-scoped variables).
The anonymous form of rescue only catches exceptions that descend from StandardError, as follows:
begin eval "\"" # raises a SyntaxError rescue => e # The SyntaxError will not be caught end
Compare to:
begin
raise NameError.new("Some name error")
rescue => e
# The NameError will be caught
end
Other
TODO: would it make sense to describe and define blocks here? Or maybe
earlier in this section, before each?
throw-catch
callcc
yield
Ruby methods can optionally be supplied a block on invocation. This block can optionally be made available as a Proc object using the & prefix on the final parameter of a method, but remains invocable at all times through the yield keyword.
The yield keyword looks syntactically just like a method call on the current object. It takes a list of parameters just like method calls and returns a value.
yield(1, 2) yield 3 x = yield(4)
Even if the block argument is named in a method, code in that method can still invoke the block using yield, instead of calling the Proc object.
def both_ways(&block) block.call(1) yield(1) end
However, these two forms are not identical. For example, redefinitions of Proc#call will affect the first invocation, but not the second.
Much like blocks can be named and captured as Proc objects inside a method, Procs can also be inserted into the block slot at method call time using the & prefix on the last argument of the call.
def only_uses_yield
yield
end
a_proc = proc{ puts "called" }
only_uses_yield(&a_proc)
Does anyone know if this mechanism depends on Proc#call or extracts the block from the Proc?

