Ruby Notes 4 Statements and Control Structures
A sequential program that are executed one line of code after the other without branching or repetition.
x = ARGV[0].to_f # Convert first argument to a number
y = ARGV[1].to_f # Convert second argument to a number
sum = x + y # Add the arguments
puts sum # Print the sum
Alter the sequential execution, or flow-of-control, of a program with Ruby’s control structures.
- Conditionals
- Loops
- Iterators and blocks
- Flow-altering satements like return and break
- Exceptions
- The special-case BEGIN and END statements
- The esoteric control structures known as fibers and continuations
Conditionals
-
if
if expression code end
The
code
betweenif
andend
is executed if (and only if) theexpression
evaluates to something other thanfalse
ornil
.The
code
must be separated from theexpression
with a newline or semicolon or the keywordthen
.# If x is less than 10, increment it if x < 10 # newline separator x += 1 end if x < 10 then x += 1 end # then separator if x < 10 then x += 1 end
-
else
if expression code else code end
-
elsif
if expression1 code1 elsif expression2 code2 . . . elsif expressionN codeN else code end
-
Return value
The return value of an
if
“statement” (i.e., the value that results from evaluating an if expression) is the value of the last expression in the code that was executed, ornil
if no block of code was executed.
-
-
if As a Modifer
Instead of writing:
if expression then code end
we can simply write:
code if expression
-
unless
unless
, as a statement or a modifier, is the opposite ofif
: it executes code only if an associated expression evaluates tofalse
ornil
. Its syntax is just likeif
, except thatelsif
clauses are not allowed:# single-way unless statement unless condition code end # two-way unless statement unless condition code else code end # unless modifier code unless condition
-
case
The
case
statement is a multiway conditional. There are two forms of this statement.-
if/elsif/else
name = case name = when x == 1 then "one" if x == 1 then "one" when x == 2 then "two" elsif x == 2 then "two" when x == 3 then "three" elsif x == 3 then "three" when x == 4 then "four" elsif x == 4 then "four" else "many" else "many" end end
the
then
keyword replaced with a newline or semicoloncase when x == 1 "one" when x == 2 "two" when x == 3 "three" end
-
case equality
name = case x when 1 # Just the value to compare to x "one" when 2 then "two" # Then keyword instead of newline when 3; "three" # Semicolon instead of newline else "many" # Optional else clause at end end
same as
name = case when 1 === x then "one" when 2 === x then "two" when 3 === x then "three" else "many" end
eg.1
# Take different actions depending on the class of x puts case x when String then "string" when Numeric then "number" when TrueClass, FalseClass then "boolean" else "other" end
eg.2
# Compute 2006 U.S. income tax using case and Range objects tax = case income when 0..7550 income * 0.1 when 7550..30650 755 + (income-7550)*0.15 when 30650..74200 4220 + (income-30655)*0.25 when 74200..154800 15107.5 + (income-74201)*0.28 when 154800..336550 37675.5 + (income-154800)*0.33 else 97653 + (income-336550)*0.35 end
eg.3
# Get user's input and process it, ignoring comments and exiting # when the user enters the word "quit" while line=gets.chomp do # Loop, asking the user for input each time case line when /^\s*#/ # If input looks like a comment... next # skip to the next line. when /^quit$/i # If input is "quit" (case insensitive)... break # exit the loop. else # Otherwise... puts line.reverse # reverse the user's input and print it. end end
-
Loops
- while and until
-
while
x = 10 # Initialize a loop counter variable while x >= 0 do # Loop while x is greater than or equal to 0 puts x # Print out the value of x x = x - 1 # Subtract 1 from x end # The loop ends here
-
until
# Count back up to 10 using an until loop x = 0 # Start at 0 (instead of -1) until x > 10 do # Loop until x is greater than 10 puts x x = x + 1 end # Loop ends here
-
-
while and until As Modifiers
-
while
x = 0 # Initialize loop variable puts x = x + 1 while x < 10 # Output and increment in a single expression
-
until
a = [1,2,3] # Initialize an array puts a.pop until a.empty? # Pop elements from array until empty
-
-
The for/in Loop
for var in collection do body end
Altering Control Flow
-
return
Causes a method to exit and return a value to its caller.
-
break
Causes a loop (or iterator) to exit.
-
next
Causes a loop (or iterator) to skip the rest of the current iteration and move on to the next iteration.
-
redo
Restarts a loop or iterator from the begining.
-
retry
Restarts an iterator, reevaluating the entire expression. The
retry
keyword can aslo be used in exception handling. -
throw/catch
A very general control struture that is named like and works like an exception progagation and handling mechanism.
throw
andcatch
are not Ruby’s primary exception mechanism (that would beraise
andrescue
). Instead, they are used as a kind of multilevel or labeledbreak
.
Exceptions and Exception Handling
-
Exception Classes and Exception Objects
Object +--Exception +--NoMemoryError +--ScriptError | +--LoadError | +--NotImplementedError | +--SyntaxError +--SecurityError # Was a StandardError in 1.8 +--SignalException | +--Interrupt +--SystemExit +--SystemStackError # Was a StandardError in 1.8 +--StandardError +--ArgumentError +--FiberError # New in 1.9 +--IOError | +--EOFError +--IndexError | +--KeyError # New in 1.9 | +--StopIteration # New in 1.9 +--LocalJumpError +--NameError | +--NoMethodError +--RangeError | +--FloatDomainError +--RegexpError +--RuntimeError +--SystemCallError +--ThreadError +--TypeError +--ZeroDivisionError
The Ruby Exception Class Hierarchy
-
The methods of exception objects
-
message
The
message
method returns a string that my provide human-readable details about what went wrong. -
backtrace
The
backtrace
method returns an array of strings that represents the call stack at the point that exception was raised.Each element of the array is a string of the form:
filename:linenumber:in `methodname'
-
-
-
Raising Exceptions with
raise
The Kernel method
raise
raises an exception.fail
is a synonym that is sometimes used when the exceptation is that the exception will cause the program to exit.-
raise
with on argumentsIf
raise
is called with no arguments, it creates a newRuntimeError
object (with no message) and raises it. Or, ifraise
is used with no arguments inside arescue
clause, it simply re-raises the exception that was being handled. -
raise
with a singleException
objectIf
raise
is called with a singleException
object as its argument, it raises that exception. Despite its simplicity, this is not actually a common way to useraise
. -
‘raise` with a single string argument
If
raise
is called with a single string argument, it creates a newRuntimeError
exception object, with the specified string as its message, and raises that exception. This is a very common way to useraise
. -
raise
with an object that has anexception
methodIf the first argument to
raise
is an object that has anexception
method, thenraise
invokes that method and raises theException
object that its returns. TheException
class defines anexception
method, so you can specify the class object for any kind of exception as the first argument toraise
.raise
accepts a string as its optional second argument to use as the exception message.raise
also accepts an optional third argument. An array of strings may be specified here, and they will be used as the backtrace for the exception object. If this third argument is not specified,raise
sets the backtrace of the exception itself (using the Kernel methodcaller
).
-
-
Handling Exceptions with
rescue
raise
is a Kernel method.A
rescue
clause, by contrast, is a fundamental part of the Ruby language.rescue
is not a statement in its own right, but rather a clause that can be attached to other Ruby statements.Most commonly, a
rescue
clause is attached to abegin
statement.The
begin
statement exists simply to delimit the block of code within which exceptions are to be handled.begin # Any number of Ruby statements go here. # Usually, they are executed without exceptions and # execution continues after the end statement. rescue # This is the rescue clause; exception-handling code goes here. # If an exception is raised by the code above, or propagates up # from one of the methods called above, then execution jumps here. end
-
Naming the exception object
In a
rescue
clause, the global variable$!
refers to theException
object that is being handled.If your program includes the line:
require 'English'
then you can use the global variable
$ERROR_INFO
instead.A better alternative to
$!
or$ERROR_INFO
is to specify a variable name for the exception object in therescue
itself:rescue => ex
The statements of this
rescue
clause can now use the varibleex
to refer to theException
object that describes the exception.begin # Handle exceptions in this block x = factorial(-1) # Note illegal argument rescue => ex # Store exception in variable ex puts "#{ex.class}: #{ex.message}" # Handle exception by printing message end # End the begin/rescue block
-
Handling exceptions by type
The
rescue
clause show here handle any exception that is aStandardError
(or subclass) and ignore anyException
object that is notStandardError
.If you want to handle nonstandard exceptions outside the
StandardError
hierarchy, or if you want to handle only specific types of exceptions, you must include one or more exception classes in therescue
clause.rescue Exception rescue ArgumentError => e rescue ArgumentError, TypeError => error
-
-
The
else
ClauseThe
else
clause is an alternative to therescue
clauses; it is used if none of therescue
clauses are needed.The code in an
else
clause is executed if the code in the body of the begin statement runs to completion without exceptions. -
The
ensure
ClauseThe
ensure
clause contains code that always runs, no matter what happens with the code followingbegin
:-
If the code runs to completion, then control jumps to the
else
clause—if there is one—and then to theensure
clause. -
If the code executes a
return
statement, then the execution skip theelse
clause and jump directly to theensure
clause before returing. -
If the code following
begin
raises an exception, then control jumps to the appropriaterescue
clause, then to theensure
clause. -
If there no
rescue
clause, or if norescue
clause can handle the exception, then control jumps directly to theensure
clause. The code in theensure
clause is executed before the exception propagates out to containing blocks or up the call stack.
The purpose of the
ensure
clause is to ensure that housekeeping details such as closing files, disconnecting database connections, and committing or aborting transactions get taken care of. -
-
rescue
As a Statement Modifier# Compute factorial of x, or use 0 if the method raises an exception y = factorial(x) rescue 0
BEGIN and END
BEGIN
and END
are reserved words in Ruby that declare code to be executed at the very beginning and very end of a Ruby program.
If there is more than one BEGIN
statement in a program, they are executed in the order which the interpreter encounters them.
If there is more than one END
statement, they are executed in the reverse of the order in which they are encountered—that is, the first one is executed last.
These statements are not commonly used in Ruby. They are inherited from Perl, which in turn inherited them from the awk text-processing language.
BEGIN { # The curly braces are required
# Global initialization code goes here
}
END {
# Global shutdown code goes here
}
Threads, Fibers, and Continuations
-
Threads for Concurrency
A thread of execution is a sequence of Ruby statements that run (or apear to run) in parallel with the main sequence of statements that the interpreter is running.
Threads are represented by
Thread
objects, but they can also be thought of as control structures for concurrency.Call
Thread.new
and associate a block with it to create new threads that will start running the code in the block.# This method expects an array of filenames. # It returns an array of strings holding the content of the named files. # The method creates one thread for each named file. def readfiles(filenames) # Create an array of threads from the array of filenames. # Each thread starts reading a file. threads = filenames.map do |f| Thread.new { File.read(f) } end # Now create an array of file contents by calling the value # method of each thread. This method blocks, if necessary, # until the thread exits with a value. threads.map {|t| t.value } end
-
Fibers for Coroutines
The name “fiber” has been used elsewhere for a kind of lightweight thread, but Ruby’s fibers are better described as coroutines or, more accurately, semi-coroutines.
The most common use for coroutines is to implement generators: objects that can compute a partial result, yield the result back to the caller, and save the state of the computaiton so that the caller can resume that compuation to obtain the next result.
In Ruby, the
Fiber
class is used to enable the automatic conversion of internal iterators, such as theeach
method, into enumerators or external iterators.Create a fiber with
Fiber.new
, and associate a block with it to specify the code that the fiber to run.Unlike a thread, the body of a fiber does not start executing right away.
To run a fiber, call the
resume
method of theFiber
object that represent it.The first time
resume
is called on a fiber, control is transferred to the begining of the fiber body.That fiber then runs unitl it reachs the end of the body, or unitl it executes the class method
Fiber.yield
.The
Fiber.yield
method transfer control back to the caller and makes the call toresume
return. It also saves the state of the fiber, so that the next call toresume
makes the fiber pick up where it left off.f = Fiber.new { # Line 1: Create a new fiber puts "Fiber says Hello" # Line 2: Fiber.yield # Line 3: puts "Fiber says Goodbye" # Line 4: goto line 11 } # Line 5: # Line 6: puts "Caller says Hello" # Line 7: f.resume # Line 8: goto line 2 puts "Caller says Goodbye" # Line 9: f.resume # Line 10: goto line 4 # Line 11:
-
Fiber arguments and return values
Fibers and their callers can exchange data through the arguments and return values of
resume
andyield
.The arguments to the first call to
resume
are passed to the block associated with the fiber: they become the values of the block parameters.On subsequent calls, the arguments to
resume
become the return value ofFiber.yield
.Conversely, any arguments to
Fiber.yield
become the return value ofresume
.And when the block exits, the value of the last expression evaluated also becomes the return value of resume.
f = Fiber.new do |message| puts "Caller said: #{message}" message2 = Fiber.yield("Hello") # "Hello" returned by first resume puts "Caller said: #{message2}" "Fine" # "Fine" returned by second resume end response = f.resume("Hello") # "Hello" passed to block puts "Fiber said: #{response}" response2 = f.resume("How are you?") # "How are you?" returned by Fiber.yield puts "Fiber said: #{response2}"
-
Implementing generators with fibers
class FibonacciGenerator def initialize @x,@y = 0,1 @fiber = Fiber.new do loop do @x,@y = @y, @x+@y Fiber.yield @x end end end def next # Return the next Fibonacci number @fiber.resume end def rewind # Restart the sequence @x,@y = 0,1 end end g = FibonacciGenerator.new # Create a generator 10.times { print g.next, " " } # Print first 10 numbers g.rewind; puts # Start over, on a new line 10.times { print g.next, " " } # Print first 10 again
-
-
Continuations
A continuation
is another complex and obscure control structure that most programmers will never need to use.
A continuation
takes the form of the Kernel method callcc and the Continuation
object.
Continuations are part of the core platformin Ruby 1.8, but they have been replaced by fibers and moved to the standard library in Ruby 1.9.