Skip to content

Instantly share code, notes, and snippets.

@patriques82
Last active July 12, 2017 09:59
Show Gist options
  • Save patriques82/6677188 to your computer and use it in GitHub Desktop.
Save patriques82/6677188 to your computer and use it in GitHub Desktop.
A list of Ruby tricks from the book "the ruby programming language"
Equality
The equal? method is defined by Object to test whether two values refer to exactly the same
object. For any two distinct objects, this method always returns false:
a = "Ruby" # One reference to one String object
b = c = "Ruby" # Two references to another String object
a.equal?(b) # false: a and b are different objects
b.equal?(c) # true: b and c refer to the same object
By convention, subclasses never override the equal? method. The == operator is the most common
way to test for equality. In the Object class, it is simply a synonym for equal?, and it tests
whether two object references are identical. Most classes redefine this operator to allow distinct
instances to be tested for equality.
a = "Ruby" # One String object
b = "Ruby" # A different String object with the same content
a.equal?(b) # false: a and b do not refer to the same object
a == b # true: but these two distinct objects have equal values
Expressions and Statements
Many programming languages distinguish between low-level expressions and higherlevel statements,
such as conditionals and loops. In these languages, statements control the flow of a program, but
they do not have values. They are executed, rather than evaluated. In Ruby, there is no clear
distinction between statements and expressions; everything in Ruby, including class and method
definitions, can be evaluated as an expression and will return a value. The fact that if
statements return a value means that, for example, the multiway conditional shown previously can
be elegantly rewritten as follows:
name = if x == 1 then "one"
elsif x == 2 then "two"
elsif x == 3 then "three"
elsif x == 4 then "four"
else "many"
end
Instead of writing:
if expression then code end
we can simply write:
code if expression
When used in this form, if is known as a statement (or expression) modifier. To use if as a
modifier, it must follow the modified statement or expression immediately, with no intervening
line break.
y = x.invert if x.respond_to? :invert
y = (x.invert if x.respond_to? :invert)
If x does not have a method named invert, then nothing happens at all, and the value of y is not
modified. In the second line, the if modifier applies only to the method call. If x does not have
an invert method, then the modified expression evaluates to nil, and this is the value that is
assigned to y. Note that an expression modified with an if clause is itself an expression that can
be modified. It is therefore possible to attach multiple if modifiers to an expression:
# Output message if message exists and the output method is defined
puts message if message if defined? puts
This should be avoided for clarity:
puts message if message and defined? puts
The ||= idiom
You might use this line:
results ||= []
Think about this for a moment. It expands to:
results = results || []
The righthand side of this assignment evaluates to the value of results, unless that is nil or
false. In that case, it evaluates to a new, empty array. This means that the abbreviated
assignment shown here leaves results unchanged, unless it is nil or false, in which case it
assigns a new array.
Other assignments
x = 1, 2, 3 # x = [1,2,3]
x, = 1, 2, 3 # x = 1; other values are discarded
x, y, z = [1, 2, 3] # Same as x,y,z = 1,2,3
x, y, z = 1, 2 # x=1; y=2; z=nil
x, y, z = 1, *[2,3] # Same as x,y,z = 1,2,3
x,*y = 1, 2, 3 # x=1; y=[2,3]
x = y = z = 0 # Assign zero to variables x, y, and z
x,(y,z) = a, b
This is effectively two assignments executed at the same time:
x = a
y,z = b
To make it clearer
x,y,z = 1,[2,3] # No parens: x=1;y=[2,3];z=nil
x,(y,z) = 1,[2,3] # Parens: x=1;y=2;z=3
Case is a alternative to if/elsif/esle. The last expression evaluated in the case expression
becomes the return value of the case statement. === is the case equality operator. For many
classes, such as the Fixnum class used earlier, the === operator behaves just the same as ==. But
certain classes define this operator in interesting ways.Here is a example of a using a range in
a case:
# 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
Flip-flops
When the .. and ... operators are used in a conditional, such as an if statement, or in a loop,
such as a while loop, they do not create Range objects. Instead, they create a special kind of
Boolean expression called a flip-flop. A flip-flop expression evaluates to true or false, just as
comparison and equality expressions do. Consider the flip-flop in the following code. Note that
the first .. in the code creates a Range object. The second one creates the flip-flop
expression:
(1..10).each {|x| print x if x==3..x==5 }
The flip-flop consists of two Boolean expressions joined with the .. operator, in the context of a
conditional or loop. A flip-flop expression is false unless and until the lefthand expression
evaluates to true. Once that expression has become true, the expression “flips” into a persistent
true state. The following simple Ruby program demonstrates a flip-flop. It reads a text file
line-by-line and prints any line that contains the text “TODO”. It then continues printing lines
until it reads a blank line:
ARGF.each do |line| # For each line of standard in or of named files
print line if line=~/TODO/..line=~/^$/ # Print lines when flip-flop is true
end
Iterators
The defining feature of an iterator method is that it invokes a block of code associated with the
method invocation. You do this with the yield statement. The following method is a trivial
iterator that just invokes its block twice
def twice
yield
yield
end
Other examples:
squares = [1,2,3].collect {|x| x*x} # => [1,4,9]
evens = (1..10).select {|x| x%2 == 0} # => [2,4,6,8,10]
odds = (1..10).reject {|x| x%2 == 0} # => [1,3,5,7,9]
The inject method is a little more complicated than the others. It invokes the associated block
with two arguments. The first argument is an accumulated value of some sort from previous iterations.
data = [2, 5, 3, 4]
sum = data.inject {|sum, x| sum + x } # => 14 (2+5+3+4)
The initial value of the accumulator variable is either the argument to inject, if there is one,
or the first element of the enumerable object, as the two examples below shows
floatprod = data.inject(1.0) {|p,x| p*x } # => 120.0 (1.0*2*5*3*4)
max = data.inject {|m,x| m>x ? m : x } # => 5 (largest element)
If a method is invoked without a block, it is an error for that method to yield, because there is
nothing to yield to. Sometimes you want to write a method that yields to a block if one is
provided but takes some default action (other than raising an error) if invoked with no block. To
do this, use block_given? to determine whether there is a block associated with the invocation.
Example:
def sequence(n, m, c)
i, s = 0, [] # Initialize variables
while(i < n) # Loop n times
y = m*i + c # Compute value
yield y if block_given? # Yield, if block
s << y # Store the value
i += 1
end
s # Return the array of values
end
Normally, enumerators with next methods are created from Enumerable objects that have an each
method. If, for some reason, you define a class that provides a next method for external
iteration instead of an each method for internal iteration, you can easily implement each in
terms of next. In fact, turning an externally iterable class that implements next into an
Enumerable class is as simple as mixing in a module.
module Iterable
include Enumerable # Define iterators on top of each
def each # And define each on top of next
loop { yield self.next }
end
end
The “gang of four” define and contrast internal and external iterators quite clearly in their
design patterns book:
"A fundamental issue is deciding which party controls the iteration, the iterator or the client
that uses the iterator. When the client controls the iteration, the iterator is called an external
iterator, and when the iterator controls it, the iterator is an internal iterator. Clients that use
an external iterator must advance the traversal and request the next element explicitly from the
iterator. In contrast, the client hands an internal iterator an operation to perform, and the
iterator applies that operation to every element...."
In Ruby, iterator methods like each are internal iterators; they control the iteration and “push”
values to the block of code associated with the method invocation. Enumerators have an each method
for internal iteration, but in Ruby 1.9 and later, they also work as external iterators—client code
can sequentially “pull” values from an enumerator with next.
Suppose you have two Enumerable collections and need to iterate their elements in pairs: the first
elements of each collection, then the second elements, and so on. Without an external iterator, you
must convert one of the collections to an array (with the to_a method defined by Enumerable ) so
that you can access its elements while iterating the other collection with each. Below shows three
different methods to iterate through such collections in parallell:
# Call the each method of each collection in turn.
# This is not a parallel iteration and does not # require enumerators.
def sequence(*enumerables, &block)
enumerables.each do |enumerable|
enumerable.each(&block)
end
end
# Iterate the specified collections, interleaving their elements.
# This can't be done efficiently without external iterators.
# Note the use of the uncommon else clause in begin/rescue.
def interleave(*enumerables)
# Convert to an array of enumerators
enumerators = enumerables.map {|e| e.to_enum }
# Loop until we don't have any enumerators
until enumerators.empty?
begin
# Take the first enumerator
e = enumerators.shift
yield e.next # Get its next and pass to the bloc
rescue StopIteration # If no exception occurred
else
enumerators << e # Put the enumerator back
end
end
end
# Iterate the specified collections, yielding
# tuples of values, one value from each of the
# collections. See also Enumerable.zip.
def bundle(*enumerables)
enumerators = enumerables.map {|e| e.to_enum }
loop { yield enumerators.map {|e| e.next} }
end
# Examples of how these iterator methods work
a,b,c = [1,2,3], 4..6, 'a'..'e'
sequence(a,b,c) {|x| print x} # prints "123456abcde"
interleave(a,b,c) {|x| print x} # prints "14a25b36cde"
bundle(a,b,c) {|x| print x} # '[1, 4, "a"][2, 5, "b"][3, 6, "c"]'
In general, Ruby’s core collection of classes iterate over live objects rather than private copies
or “snapshots” of those objects, and they make no attempt to detect or prevent concurrent
modification to the collection while it is being iterated.
a = [1,2,3,4,5] # prints "1,1\n3,2\n5,3"
a.each {|x| puts "#{x},#{a.shift}" } '
Blocks
Blocks may not stand alone; they are only legal following a method invocation. You can, however,
place a block after any method invocation; if the method is not an iterator and never invokes the
block with yield, the block will be silently ignored. Blocks are delimited with curly braces or
with do and end keywords.
Consider the Array.sort method. If you associate a block with an invocation of this method, it will
yield pairs of elements to the block, and it is the block’s job to sort them. The block’s return
value (–1, 0, or 1) indicates the ordering of the two arguments. The “return value” of the block is
available to the iterator method as the value of the yield statement.
# The block takes two words and "returns" their relative order.
words.sort! {|x,y| y <=> x }
Blocks define a new variable scope: variables created within a block exist only within that block
and are undefined outside of the block. Be cautious, however; the local variables in a method are
available to any blocks within that method. Ruby 1.9 is different: block parameters are always local
to their block, and invocations of the block never assign values to existing variables.Ruby 1.9 is
different in another important way, too. Block syntax has been extended to allow you to declare
block-local variables that are guaranteed to be local, even if a variable by the same name already
exists in the enclosing scope. To do this, follow the list of block parameters with a semicolon and
a comma-separated list of block local variables. Here is an example:
# local variables
x = y = 0 # x and y are local to block
1.upto(4) do |x;y| # x and y "shadow" the outer variables
y = x + 1 # Use y as a scratch var
puts y*y # Prints 4, 9, 16, 25
end [x,y] # => [0,0]: block does not alter these
In this code, x is a block parameter: it gets a value when the block is invoked with yield. y is a
block-local variable. It does not receive any value from a yield invocation, but it has the value
nil until the block actually assigns some other value to it.Blocks can have more than one parameter
and more than one local variable, of course. Here is a block with two parameters and three local
variables:
hash.each {|key,value; i,j,k| ... }
In Ruby 1.8, only the last block parameter may have an * prefix. Ruby 1.9 lifts this restriction and
allows any one block parameter, regardless of its position in the list, to have an * prefix:
def five; yield 1,2,3,4,5; end # Yield 5 values
# Extra values go into body array
five do |head, *body, tail|
print head, body, tail # Prints "1[2,3,4]5"
end
return may optionally be followed by an expression, or a comma-separated list of expressions. If
there is no expression, then the return value of the method is nil. If there is one expression,
then the value of that expression becomes the return value of the method. If there is more than one
expression after the return keyword, then the return value of the method is an array containing the
values of those expressions.
Most Ruby programmers omit return when it is not necessary. Instead of writing return x as the last
line of a method, they would simply write x. The return value in this case is the value of the last
expression in the method. return is useful if you want to return from a method prematurely, or if
you want to return more than one value.
def double(x)
return x, x.dup
end
When the return statement is used in a block, it does not just cause the block to return. And it
does not just cause the iterator that invokes the block to return. return always causes the
enclosing method to return, just like it is supposed to, since a block is not a method.
def find(array, target)
array.each_with_index do |element,index| # return element from find, not from block
return index if (element == target)
end
nil # If we didn't find the element
end
Like return keyword, break and next (continue in java) can be used alone or together with
expressions, or comma-separated expressions. We have seen already what return does in a block,
when next or break is used together with values in a block the values are what is "yielded".
squareroots = data.collect do |x|
next 0 if x < 0 # 0 for negative values
Math.sqrt(x)
end
As with the return statement, it is not often necessary to explicitly use next to specify a value.
squareroots = data.collect do |x|
if (x < 0)
then 0
else
Math.sqrt(x)
end
end
The redo statement restarts the current iteration of a loop or iterator. This is not the same
thing as next. next transfers control to the end of a loop or block so that the next iteration
can begin, whereas redo transfers control back to the top of the loop or block so that the
iteration can start over.
i = 0
while(i < 3) # Prints "0123" instead of "012"
print i # Control returns here when redo is executed
i += 1
redo if i == 3
end
One use, however, is to recover from input errors when prompting a user for input.
puts "Please enter the first word you think of"
words = %w(apple banana cherry)
response = words.collect do |word| # Control returns here when redo is executed
print word + "> " # Prompt the user
response = gets.chop # Get a response
if response.size == 0
word.upcase! # Emphasize the prompt
redo # And skip to the top of the block
end
response # Return the response
end
The retry statement is normally used in a rescue clause to re-execute a block of code that raised
an exception.
Throw and catch
throw and catch are Kernel methods that define a control structure that can be thought of as a
multilevel break. throw doesn’t just break out of the current loop or block but can actually
transfer out any number of levels, causing the block defined with a catch to exit. If you are
familiar with languages like Java and JavaScript, then you probably recognize throw and catch as
the keywords those languages use for raising and handling exceptions.
Ruby does exceptions differently, using raise and rescue, which we’ll learn about later. But the
parallel to exceptions is intentional. Calling throw is very much like raising an exception. And
the way a throw propagates out through the lexical scope and then up the call stack is very much
the same as the way an exception propagates out and up. Despite the similarity to exceptions, it
is best to consider throw and catch as a general-purpose (if perhaps infrequently used) control
structure rather than an exception mechanism. Here is an example:
for matrix in data do # Process a deeply nested data structure.
catch :missing_data do # Label this statement so we can break out.
for row in matrix do
for value in row do
throw :missing_data unless value # Break out of two loops at once.
# Otherwise, do some actual data processing here.
end
end
end
# We end up here after the nested loops finish processing each matrix.
# We also get here if :missing_data is thrown.
end
If no catch call matches the symbol passed to throw, then a NameError exception is raised.
Raise and rescue
An exception is an object that represents some kind of exceptional condition; it indicates that
something has gone wrong. Raising an exception transfers the flow-of control to exception
handling code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment