Yesterday i started reading The Ruby Programming Language By David Flanagan, Yukihiro Matsumoto
Today i have read next 3 chapters (4-5-6) from the book and I wanted to share few important quotations i found from the those chapters.
I skipped Section 6.8 about "Functional Programming" as authors suggested to read first next chapters and then try to read this section.
Tomorrow i will continue updating this ruby series with last 4 chapters.
1) There are four kinds of variables in Ruby, and lexical rules govern their names. Variables that begin with $ are global variables, visible throughout a Ruby program. Variables that begin with @ and @@ are instance variables and class variables.And variables whose names begin with an underscore or a lowercase letter are local variables, defined only within the current method or block.
2) Variables always have simple, unqualified names. If a . or :: appears in an expression, then that expression is either a reference to a constant or a method invocation. For example, Math::PI is a reference to a constant, and the expression item.price is an invocation of the method named price on the value held by the variable item.
3) In general, you should always assign a value to, or initialize, your variables before using them in expressions. In some circumstances, however, Ruby will allow you to use variables that have not yet been initialized. The rules are different for different kinds of variables:
Class variables
Class variables must always have a value assigned to them before they are used. Ruby raises a NameError if you refer to a class variable to which no value has been assigned.
Instance variables
If you refer to an uninitialized instance variable, Ruby returns nil. It is considered bad programming to rely on this behavior, however. Ruby will issue a warning about the uninitialized variable if you run it with the -w option.
Global variables
Uninitialized global variables are like uninitialized instance variables: they evaluate to nil, but cause a warning when Ruby is run with the -w flag.
Local variables
This case is more complicated than the others because local variables don't have a punctuation character as a prefix. This means that local variable references look just like method invocation expressions. If the Ruby interpreter has seen an assignment to a local variable, it knows it is a variable and not a method, and it can return the value of the variable. If there has been no assignment, then Ruby treats the expression as a method invocation. If no method by that name exists, Ruby raises a NameError.
In general, therefore, attempting to use a local variable before it has been initialized results in an error. There is one quirk—a variable comes into existence when the Ruby interpreter sees an assignment expression for that variable. This is the case even if that assignment is not actually executed. A variable that exists but has not been assigned a value is given the default value nil. For example:
a = 0.0 if false # This assignment is never executed
print a # Prints nil: the variable exists but is not assigned
print b # NameError: no variable or method named b exists
4) Method Invocations
A method invocation expression has four parts:
An arbitrary expression whose value is the object on which the method is invoked. This expression is followed by . or :: to separate it from the method name that follows. The expression and separator are optional; if omitted, the method is invoked on self.
The name of the method being invoked. This is the only required piece of a method invocation expression.
The argument values being passed to the method. The list of arguments may be enclosed in parentheses, but these are usually optional.If there is more than one argument, they are separated from each other with commas. The number and type of arguments required depend on the method definition. Some methods expect no arguments.
An optional block of code delimited by curly braces or by a do/end pair. The method may invoke this code using the yield keyword. This ability to associate arbitrary code with any method invocation is the basis for Ruby's powerful iterator methods.
5) Methods defined by Kernel are global functions, as are any methods defined at the top-level, outside of any classes.Global functions are defined as private methods of the Object class. Because global functions are methods of Object, these methods can always be invoked (implicitly) in any context, regardless of the value of self.
Example: puts "hello world"
6) Consider the following line of code, assuming that the variable a holds an array:
a[0]
We might again think that this is a special kind of variable reference expression, where the variable in question is actually an array element. Again, however, this is method invocation. The Ruby interpreter converts the array access into this:
a.[](0)
The array access becomes an invocation of the method named [] on the array, with the array index as its argument. This array access syntax is not limited to arrays. Any object is allowed to define a method named []. When the object is "indexed" with square brackets, any values within the brackets will be passed to the method. If the [] method is written to expect three arguments, then you should put three comma-separated expressions within the square brackets.
Assignment to arrays is also done via method invocation. If the object o defines a method named []=, then the expression o[x]=y becomes o.[]=(x,y), and the expression o[x,y]=z becomes o.[]=(x,y,z).
7) Ruby has no syntax to explicitly declare a variable: variables simply come into existence when they are assigned. Also, local variable names and method names look the same—there is no prefix like $ to distinguish them. Thus, a simple expression such as x could refer to a local variable named x or a method of self named x.
Example:
class Ambiguous
def Ambiguous.x; 1; end # A method named "x".Always returns 1
print x # No variable has been seen; refers to method above: prints 1
# The line below is never evaluated, because of the "if false" clause. But
# the parser sees it and treats x as a variable for the rest of the method
x=0 if false
print x # x is a variable, but has never been assigned to: prints nil
x=2 # This assignment does get evaluated
print x # So now this line prints 2
end
8) Constants are different from variables in an obvious way: their values are intended to remain constant throughout the execution of a program. Therefore, there are some special rules for assignment to constants:
Assignment to a constant that already exists causes Ruby to issue a warning. Ruby does execute the assignment, however, which means that constants are not really constant.
Assignment to constants is not allowed within the body of a method. Ruby assumes that methods are intended to be invoked more than once; if you could assign to a constant in a method, that method would issue warnings on every invocation after the first. So, this is simply not allowed.
Unlike variables, constants do not come into existence until the Ruby interpreter actually executes the assignment expression. A nonevaluated expression like the following does not create a constant:
N = 100 if false
9) One lvalue, multiple rvalues
When there is a single lvalue and more than one rvalue, Ruby creates an array to hold the rvalues and assigns that array to the lvalue:
x = 1, 2, 3 # x = [1,2,3]
10) Exponentiation: **
** performs exponentiation, raising its first argument to the power of the second. Note that you can compute roots of a number by using a fractional number as the second operand. For example, the cube root of x is x**(1.0/3.0). Similarly, x**-y is the same as 1/(x**y). The ** operator is right-associative, so x**y**z is the same thing as x**(y**z). Finally, note that ** has higher precedence than the unary minus operator, so -1**0.5 is the same thing as -(1**0.5). If you really want to take the square root of -1, you must use parentheses: (-1)**0.5. (The imaginary result is not-a-number, and the expression evaluates to NaN.)
11) .. and ... are operators rather than just range literal syntax.
The .. and ... operators are not method-based and cannot be redefined. They have relatively low precedence, which means that they can usually be used without putting parentheses around the left or right operands:
x+1..x*x
The value of these operators is a Range object. x..y is the same as:
Range.new(x,y)
And x...y is the same as:
Range.new(x,y,true)
12) Boolean 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. The extraordinarily unusual thing about a flip-flop expression, however, is that its value depends on the value of previous evaluations. This means that a flip-flop expression has state associated with it; it must remember information about previous evaluations. Because it has state, you would expect a flip-flop to be an object of some sort. But it isn't—it's a Ruby expression, and the Ruby interpreter stores the state (just a single Boolean value) it requires in its internal parsed representation of the expression.
With that background in mind, 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. It remains in that state, and subsequent evaluations return true until the righthand expression evaluates to true. When that happens, the flip-flop "flops" back to a persistent false state. Subsequent evaluations of the expression return false until the lefthand expression becomes true again.
In the code example, the flip-flop is evaluated repeatedly, for values of x from 1 to 10. It starts off in the false state, and evaluates to false when x is 1 and 2. When x==3, the flip-flop flips to true and returns true. It continues to return true when x is 4 and 5. When x==5, however, the flip-flop flops back to false, and returns false for the remaining values of x. The result is that this code prints 345.
13) Flip-flops can be written with either .. or .... The difference is that when a .. flip-flop flips to true, it returns true but also tests its righthand expression to see if it should flop its internal state back to false. The ... form waits for its next evaluation before testing the righthand expression. Consider these two lines:
# Prints "3". Flips and flops back when x==3
(1..10).each {|x| print x if x==3..x>=3 }
# Prints "34". Flips when x == 3 and flops when x==4
(1..10).each {|x| print x if x==3...x>=3 } # Prints "34"
14) unless
unless, as a statement or a modifier, is the opposite of if: it executes code only if an associated expression evaluates to false or nil. Its syntax is just like if, except that elsif clauses are not allowed:
The unless statement, like the if statement, requires that the condition and the code are separated by a newline, a semicolon, or the then keyword. Also like if, unless statements are expressions and return the value of the code they execute, or nil if they execute nothing:
# Call the to_s method on object o, unless o is nil
s = unless o.nil? # newline separator
o.to_s
end
s = unless o.nil? then o.to_s end # then separator
15) while and until As Modifiers
If the body of a loop is a single Ruby expression, you can express that loop in a particularly compact form by using while or until as a modifier after the expression. For example:
x = 0 # Initialize loop variable
puts x = x + 1 while x < 10 # Output and increment in a single expression
16) The times, each, map, and upto methods are all iterators, and they interact with the block of code that follows them. The complex control structure behind this is yield. The yield statement temporarily returns control from the iterator method to the method that invoked the iterator.Specifically,control flow goes from the iterator to the block of code associated with the invocation of the iterator. When the end of the block is reached, the iterator method regains control and execution resumes at the first statement following the yield. In order to implement some kind of looping construct, an iterator method will typically invoke the yield statement multiple times
17) Numeric Iterators
The core Ruby API provides a number of standard iterators. The Kernel method loop behaves like an infinite loop, running its associated block repeatedly until the block executes a return, break, or other statement that exits from the loop.
The Integer class defines three commonly used iterators. The upto method invokes its associated block once for each integer between the integer on which it is invoked and the integer which is passed as an argument. For example:
4.upto(6) {|x| print x} # => prints "456"
As you can see, upto yields each integer to the associated block, and it includes both the starting point and the end point in the iteration. In general, n.upto(m) runs its block m-n+1 times.
The downto method is just like upto but iterates from a larger number down to a smaller number.
When the Integer.times method is invoked on the integer n, it invokes its block n times, passing values 0 through n-1 on successive iterations. For example:
3.times {|x| print x } # => prints "012"
If you want to do a numeric iteration using floating-point numbers, you can use the more complex step method defined by the Numeric class. The following iterator, for example, starts at 0 and iterates in steps of 0.1 until it reaches Math::PI:
0.step(Math::PI, 0.1) {|x| puts Math.sin(x) }
18) The select method invokes the associated block for each element in the enumerable object, and returns an array of elements for which the block returns a value other than false or nil. For example:
evens = (1..10).select {|x| x%2 == 0} # => [2,4,6,8,10]
The reject method is simply the opposite of select; it returns an array of elements for which the block returns nil or false. For example:
odds = (1..10).reject {|x| x%2 == 0} # => [1,3,5,7,9]
19) 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. The second argument is the next element of the enumerable object. The return value of the block becomes the first block argument for the next iteration, or becomes the return value of the iterator after the last iteration. 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. (In this case, the block is invoked just once for the first two elements.) Examples make inject more clear:
data = [2, 5, 3, 4]
sum = data.inject {|sum, x| sum + x } # => 14 (2+5+3+4)
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)
20) Although while, until, and for loops are a core part of the Ruby language, it is probably more common to write loops using special methods known as iterators. Iterators are one of the most noteworthy features of Ruby, and examples such as the following are common in introductory Ruby tutorials:
3.times { puts "thank you!" } # Express gratitude three times
data.each {|x| puts x } # Print each element x of data
[1,2,3].map {|x| x*x } # Compute squares of array elements
factorial = 1 # Compute the factorial of n
2.upto(n) {|x| factorial *= x }
21) redo
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. If you come to Ruby from C-like languages, then redo is probably a new control structure for you.
redo transfers control to the first expression in the body of the loop or in a block. It does not retest the loop condition, and it does not fetch the next element from an iterator. The following while loop would normally terminate after three iterations, but a redo statement makes it iterate four times:
i = 0
while(i < 3) # Prints "0123" instead of "012"
# Control returns here when redo is executed
print i
i += 1
redo if i == 3
end
22) Raising Exceptions with raise
The Kernel method raise raises an exception. fail is a synonym that is sometimes used when the expectation is that the exception will cause the program to exit. There are several ways to invoke raise:
If raise is called with no arguments, it creates a new RuntimeError object (with no message) and raises it. Or, if raise is used with no arguments inside a rescue clause, it simply re-raises the exception that was being handled.
If raise is called with a single Exception object as its argument, it raises that exception. Despite its simplicity, this is not actually a common way to use raise.
If raise is called with a single string argument, it creates a new RuntimeError exception object, with the specified string as its message, and raises that exception. This is a very common way to use raise.
If the first argument to raise is an object that has an exception method, then raise invokes that method and raises the Exception object that it returns. The Exception class defines an exception method, so you can specify the class object for any kind of exception as the first argument to raise.
raise accepts a string as its optional second argument. If a string is specified, it is passed to the exception method of the first argument. This string is intended for 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 method caller).
The following code defines a simple method that raises an exception if invoked with a parameter whose value is invalid:
Code View:
def factorial(n) # Define a factorial method with argument n
raise "bad argument" if n < 1 # Raise an exception for bad n
return 1 if n == 1 # factorial(1) is 1
n * factorial(n-1) # Compute other factorials recursively
end
23) Handling exceptions by type
The rescue clauses shown here handle any exception that is a StandardError (or subclass) and ignore any Exception object that is not a StandardError. 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 the rescue clause. Here's how you would write a rescue clause that would handle any kind of exception:
class Exceptions
def Exceptions.factorial(n) # Define a factorial method with argument n
raise "bad argument" if n < 1 # Raise an exception for bad n
return 1 if n == 1 # factorial(1) is 1
n * factorial(n-1) # Compute other factorials recursively
end
begin
x = factorial('pras')
print x
rescue ArgumentError => ex
puts "Try again with a value >= 1"
rescue TypeError => ex
puts "Try again with an integer"
end
end
24) Blocks, like methods, are not objects that Ruby can manipulate. But it's possible to create an object that represents a block, and this is actually done with some frequency in Ruby programs. A Proc object represents a block. Like a Method object, we can execute the code of a block through the Proc that represents it. There are two varieties of Proc objects, called procs and lambdas, which have slightly different behavior. Both procs and lambdas are functions rather than methods invoked on an object. An important feature of procs and lambdas is that they are closures: they retain access to the local variables that were in scope when they were defined, even when the proc or lambda is invoked from a different scope.
25) Variable-Length Argument Lists and Arrays
Sometimes we want to write methods that can accept an arbitrary number of arguments. To do this, we put an * before one of the method's parameters. Within the body of the method, this parameter will refer to an array that contains the zero or more arguments passed at that position. For example:
class MultiArguments
# Return the largest of the one or more arguments passed
def MultiArguments.max(first, *rest)
# Assume that the required first argument is the largest
max = first
# Now loop through each of the optional arguments looking for bigger ones
rest.each {|x| max = x if x > max }
# Return the largest one we found
max
end
print max(1) # first=1, rest=[]
print max(1,2) # first=1, rest=[2]
print max(1,2,3) # first=1, rest=[2,3]
end
26) Passing arrays to methods
We've seen how * can be used in a method declaration to cause multiple arguments to be gathered or coalesced into a single array. It can also be used in a method invocation to scatter, expand, or explode the elements of an array (or range or enumerator) so that each element becomes a separate method argument. The * is sometimes called the splat operator, although it is not a true operator.
Suppose we wanted to find the maximum value in an array (and that we didn't know that Ruby arrays have a built-in max method!). We could pass the elements of the array to the max method (defined earlier) like this:
data = [3, 2, 1]
m = max(*data) # first = 3, rest=[2,1] => 3
Consider what happens without the *:
m = max(data) # first = [3,2,1], rest=[] => [3,2,1]
In this case, we're passing an array as the first and only argument, and our max method returns that first argument without performing any comparisons on it.
The * can also be used with methods that return arrays to expand those arrays for use in another method invocation.
27) That a block is a chunk of Ruby code associated with a method invocation, and that an iterator is a method that expects a block. Any method invocation may be followed by a block, and any method that has a block associated with it may invoke the code in that block with the yield statement
# Generate a sequence of n numbers m*i + c and pass them to the block
def sequence2(n, m, c)
i = 0
while(i < n) # loop n times
yield i*m + c # pass next element of the sequence to the block
i += 1
end
end
# Here is how you might use this version of the method
sequence2(5, 2, 2) {|x| puts x } # Print numbers 2, 4, 6, 8, 10
28) Using & in method invocation
& can also be used in definitions and invocations.When & is used before a Proc object in a method invocation, it treats the Proc as if it was an ordinary block following the invocation.
a, b = [1,2,3], [4,5] # Start with some data.
sum = a.inject(0) {|total,x| total+x } # => 6. Sum elements of a.
sum = b.inject(sum) {|total,x| total+x } # => 15. Add the elements of b in.
The important thing to notice about this example is that the two blocks are identical. Rather than having the Ruby interpreter parse the same block twice, we can create a Proc to represent the block, and use the single Proc object twice.
a, b = [1,2,3], [4,5] # Start with some data.
summation = Proc.new {|total,x| total+x } # A Proc object for summations.
sum = a.inject(0, &summation) # => 6
sum = b.inject(sum, &summation) # => 15
If you use & in a method invocation, it must appear before the last argument in the invocation. Blocks can be associated with any method call, even when the method is not expecting a block, and never uses yield. In the same way, any method invocation may have an & argument as its last argument.
29) In Ruby, procs and lambdas are closures. The term "closure" comes from the early days of computer science; it refers to an object that is both an invocable function and a variable binding for that function. When you create a proc or a lambda, the resulting Proc object holds not just the executable block but also bindings for all the variables used by the block.
In the following code, for example, the block associated with the collect iterator uses the method argument n:
# multiply each element of the data array by n
def multiply(data, n)
data.collect {|x| x*n }
end
puts multiply([1,2,3], 2) # Prints 2,4,6
What is more interesting, and possibly even surprising, is that if the block were turned into a proc or lambda, it could access n even after the method to which it is an argument had returned. The following code demonstrates:
# Return a lambda that retains or "closes over" the argument n
def multiplier(n)
lambda {|data| data.collect{|x| x*n } }
end
doubler = multiplier(2) # Get a lambda that knows how to double
puts doubler.call([1,2,3]) # Prints 2,4,6
The multiplier method returns a lambda. Because this lambda is used outside of the scope in which it is defined, we call it a closure; it encapsulates or "closes over" (or just retains) the binding for the method argument n.
30) Closures and Shared Variables
It is important to understand that a closure does not just retain the value of the variables it refers to—it retains the actual variables and extends their lifetime. Another way to say this is that the variables used in a lambda or proc are not statically bound when the lambda or proc is created. Instead, the bindings are dynamic, and the values of the variables are looked up when the lambda or proc is executed.
As an example, the following code defines a method that returns two lambdas. Because the lambdas are defined in the same scope, they share access to the variables in that scope. When one lambda alters the value of a shared variable, the new value is available to the other lambda:
Code View:
# Return a pair of lambdas that share access to a local variable.
def accessor_pair(initialValue=nil)
value = initialValue # A local variable shared by the returned lambdas.
getter = lambda { value } # Return value of local variable.
setter = lambda {|x| value = x } # Change value of local variable.
return getter,setter # Return pair of lambdas to caller.
end
getX, setX = accessor_pair(0) # Create accessor lambdas for initial value 0.
puts getX[] # Prints 0. Note square brackets instead of call.
setX[10] # Change the value through one closure.
puts getX[] # Prints 10. The change is visible through the other
31) Closures and Bindings
The Proc class defines a method named binding. Calling this method on a proc or lambda returns a Binding object that represents the bindings in effect for that closure.
A Binding object doesn't have interesting methods of its own, but it can be used as the second argument to the global eval function , providing a context in which to evaluate a string of Ruby code.
# Return a lambda that retains or "closes over" the argument n
def multiplier(n)
lambda {|data| data.collect{|x| x*n } }
end
doubler = multiplier(2) # Get a lambda that knows how to double
puts doubler.call([1,2,3]) # Prints 2,4,6
32) Method objects work very much like Proc objects and can usually be used in place of them. When a true Proc is required, you can use Method.to_proc to convert a Method to a Proc. This is why Method objects can be prefixed with an ampersand and passed to a method in place of a block. For example:
def square(x); x*x; end
puts (1..10).map(&method(:square))
About the Author
David Flanagan is a computer programmer who spends most of his time writing about JavaScript and Java. His books with O'Reilly include Java in a Nutshell, Java Examples in a Nutshell, Java Foundation Classes in a Nutshell, JavaScript: The Definitive Guide, and JavaScript Pocket Reference. David has a degree in computer science and engineering from the Massachusetts Institute of Technology. He lives with his wife and children in the U.S. Pacific Northwest bewteen the cities of Seattle, Washington and Vancouver, British Columbia. David has a blog at www.davidflanagan.com.
Yukihiro Matsumoto ("Matz"), the creator of Ruby, is a professional programmer who worked for the Japanese open source company, netlab.jp. Matz is also known as one of the open source evangelists in Japan. He's released several open source products, including cmail, the emacs-based mail user agent, written entirely in emacs lisp. Ruby is his first piece of software that has become known outside of Japan.
Tuesday, 19 August 2008
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment