Last active
July 12, 2017 09:59
-
-
Save patriques82/6677188 to your computer and use it in GitHub Desktop.
A list of Ruby tricks from the book "the ruby programming language"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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