Thursday, 21 August 2008

The Ruby Way By Hal Fulton - Part 1

I started reading The Ruby Way By Hal Fulton

Today i have read first 4 chapters from the book and I wanted to share few important quotations i found from the first 4 chapters.

Tomorrow i will continue updating this ruby series with next pending chapters.

1) Ruby supports interface polymorphism but in a different way, providing modules whose methods may be mixed in to existing classes (interfacing to user-defined methods that are expected to exist). This, however, is not the way modules are usually used. A module consists of methods and constants that may be used as though they were actual parts of that class or object; when a module is mixed in via the include statement, this is considered to be a restricted form of multiple inheritance. (According to the language designer, Yukihiro Matsumoto, it can be viewed as single inheritance with implementation sharing.) This is a way of preserving the benefits of MI without suffering all the consequences.

2) Some attributes of Ruby

a) Ruby is an agile language. It is "malleable" and encourages frequent, easy refactoring.

b) Ruby is an interpreted language. Of course, there may be later implementations of a Ruby compiler for performance reasons, but we maintain that an interpreter yields great benefits not only in rapid prototyping but also in the shortening of the development cycle overall.

c) Ruby is an expression-oriented language. Why use a statement when an expression will do? This means, for instance, that code becomes more compact as the common parts are factored out and repetition is removed.

d) Ruby is a very high-level language (VHLL). One principle behind the language design is that the computer should work for the programmer rather than vice versa. The "density" of Ruby means that sophisticated and complex operations can be carried out with relative ease as compared to lower-level languages.

3) Variables and other identifiers normally start with an alphabetic letter or a special modifier. The basic rules are as follows:

Local variables (and pseudovariables such as self and nil) begin with a lowercase letter or an underscore.

Global variables begin with a $ (dollar sign).

Instance variables (within an object) begin with an @ (at sign).

Class variables (within a class) begin with two @ signs.

Constants begin with capital letters.

For purposes of forming identifiers, the underscore (_) may be used as a lowercase letter.

Special variables starting with a dollar sign (such as $1 and $/) are not dealt with here.

Here are some examples of each of these:

Local variables alpha, _ident, some_var

Pseudovariables self, nil, __FILE__

Constants K6chip, Length, LENGTH

Instance variables @foobar, @thx1138, @NOT_CONST

Class variable @@phydeaux, @@my_var, @@NOT_CONST

Global variables $beta, $B12vitamin, $NOT_CONST

4) A Sample Program

print "Please enter a temperature and scale (C or F): "
str = gets
exit if str.nil? or str.empty?
str.chomp!
temp, scale = str.split(" ")

abort "#{temp} is not a valid number." if temp !~ /-?\d+/

temp = temp.to_f
case scale
when "C", "c"
f = 1.8*temp + 32
when "F", "f"
c = (5.0/9.0)*(temp-32)
else
abort "Must specify C or F."
end

if f.nil?
print "#{c} degrees C\n"
else
print "#{f} degrees F\n"
end

Now, as for the mechanics of the program: We begin with a print statement, which is actually a call to the Kernel method print, to write to standard output. This is an easy way of leaving the cursor "hanging" at the end of the line.

Following this, we call gets (get string from standard input), assigning the value to str. We then do a chomp! to remove the trailing newline.

Note that any apparently "free-standing" function calls such as print and gets are actually methods of Object (probably originating in Kernel). In the same way, chop is a method called with str as a receiver. Method calls in Ruby usually can omit the parentheses; print "foo" is the same as print("foo").

The variable str holds a character string, but there is no reason it could not hold some other type instead. In Ruby, data have types, but variables do not. A variable springs into existence as soon as the interpreter sees an assignment to that variable; there are no "variable declarations" as such.

The exit is a call to a method that terminates the program. On this same line there is a control structure called an if-modifier. This is like the if statement that exists in most languages, but backwards; it comes after the action, does not permit an else, and does not require closing. As for the condition, we are checking two things: Does str have a value (is it non-nil), and is it a non-null string? In the case of an immediate end-of-file, our first condition will hold; in the case of a newline with no preceding data, the second condition will hold.

The same statement could be written this way:

exit if not str or not str[0]


The reason these tests work is that a variable can have a nil value, and nil evaluates to false in Ruby. In fact, nil and false evaluate as false, and everything else evaluates as true. Specifically, the null string "" and the number 0 do not evaluate as false.

The next statement performs a chomp! operation on the string (to remove the trailing newline). The exclamation point as a prefix serves as a warning that the operation actually changes the value of its receiver rather than just returning a value. The exclamation point is used in many such instances to remind the programmer that a method has a side effect or is more "dangerous" than its unmarked counterpart. The method chomp, for example, returns the same result but does not modify its receiver.

The next statement is an example of multiple assignment. The split method splits the string into an array of values, using the space as a delimiter. The two assignable entities on the left-hand side will be assigned the respective values resulting on the right-hand side.

The if statement that follows uses a simple regex to determine whether the number is valid; if the string fails to match a pattern consisting of an optional minus sign followed by one or more digits, it is an invalid number (for our purposes), and the program exits. Note that the if statement is terminated by the keyword end; though it was not needed here, we could have had an else clause before the end. The keyword then is optional; we tend not to use it in this book.

The to_f method is used to convert the string to a floating point number. We are actually assigning this floating point value back to temp, which originally held a string.

The case statement chooses between three alternativesthe cases in which the user specified a C, specified an F, or used an invalid scale. In the first two instances, a calculation is done; in the third, we print an error and exit.

5) Like many other modern programming languages, Ruby supports exceptions.

Exceptions are a means of handling errors that has significant advantages over older methods. Return codes are avoidable, as is the "spaghetti logic" that results from checking them, and the code that detects the error can be distinguished from the code that knows how to handle the error (since these are often separate anyway).

The raise statement raises an exception. Note that raise is not a reserved word but a method of the module Kernel. (There is an alias named fail.)

raise # Example 1
raise "Some error message" # Example 2
raise ArgumentError # Example 3
raise ArgumentError, "Bad data" # Example 4
raise ArgumentError.new("Bad data") # Example 5
raise ArgumentError, "Bad data", caller[0] # Example 6

class ExceptionDemo

y=10
z=0

begin
x = Math.sqrt(y/z)
rescue ArgumentError
puts "Error taking square root."
rescue ZeroDivisionError
puts "Attempted division by zero."
end

end

6) In Ruby, every object is an instance of some class; the class contains the implementation of the methods:

"abc".class # String
"abc".class.class # Class

In addition to encapsulating its own attributes and operations, an object in Ruby has an identity:

"abc".object_id # 53744407

7) If the object is mutable, a modification done to one variable will be reflected in the other:

x.gsub!(/a/,"x")
y

8) A mutable object can be made immutable using the freeze method:

x.freeze
x.gsub!(/b/,"y") # Error!

9) The terms module and mixin are nearly synonymous. A module is a collection of methods and constants that is external to the Ruby program. It can be used simply for namespace management, but the most common use of a module is to have its features "mixed" into a class (by using include). In this case, it is used as a mixin.

10) The class called Module contains methods called attr, attr_accessor, attr_reader, and attr_writer. These can be used (with symbols as parameters) to automatically handle controlled access to the instance data.

11) To create a class that inherits from another class, define it in this way:

class MyClass < OtherClass
# ...
end

12) Code can be constructed piecemeal and evaluated.

Example:

def calculate(op1, operator, op2)
string = op1.to_s + operator + op2.to_s
# operator is assumed to be a string; make one big
# string of it and the two operands
eval(string) # Evaluate and return a value
end

@alpha = 25
@beta = 12

puts calculate(2, "+", 2) # Prints 4
puts calculate(5, "*", "@alpha") # Prints 125
puts calculate("@beta", "**", 3) # Prints 1728

13) Do not confuse the .. and ... range operators. The former is inclusive of the upper bound, and the latter is exclusive. For example, 5..10 includes the number 10, but 5...10 does not.

14) In most languages, swapping two variables takes an additional temporary variable. In Ruby, multiple assignment makes this unnecessary: x, y = y, x will interchange the values of x and y.

15) A closure remembers the context in which it was created. One way to create a closure is by using a Proc object. As a crude example, consider the following:

def power(exponent)
proc {|base| base**exponent}
end

square = power(2)
cube = power(3)

a = square.call(11) # Result is 121
b = square.call(5) # Result is 25
c = cube.call(6) # Result is 216
d = cube.call(8) # Result is 512
Observe that the closure "knows" the value of exponent that it was given at the time it was created

16) Processing a Line at a Time

A Ruby string can contain newlines. For example, a file can be read into memory and stored in a single string. The default iterator each processes such a string one line at a time:

str = "Once upon\na time...\nThe End\n"
num = 0
str.each do |line|
num += 1
print "Line #{num}: #{line}"
end


The preceding code produces three lines of output:

Line 1: Once upon
Line 2: a time...
Line 3: The End

17) There are no built-in methods for detecting case, but this is easy to do with regular expressions, as shown in the following example:

if string =~ /[a-z]/
puts "string contains lowercase characters"
end

if string =~ /[A-Z]/
puts "string contains uppercase characters"
end

if string =~ /[A-Z]/ and string =~ /a-z/
puts "string contains mixed case"
end

if string[0..0] =~ /[A-Z]/
puts "string starts with a capital letter"
end


Note that all these methods ignore locale

18) If a regular expression is specified, the string matching that pattern will be returned. If there is no match, nil will be returned:

str = "Alistair Cooke"
sub1 = str[/l..t/] # "list"
sub2 = str[/s.*r/] # "stair"
sub3 = str[/foo/] # nil

19) Substituting in Strings
We've already seen how to perform simple substitutions in strings. The sub and gsub methods provide more advanced pattern-based capabilities. There are also sub! and gsub!, their in-place counterparts.

The sub method substitutes the first occurrence of a pattern with the given substitute-string or the given block:

s1 = "spam, spam, and eggs"
s2 = s1.sub(/spam/,"bacon") # "bacon, spam, and eggs"

s3 = s2.sub(/(\w+), (\w+),/,'\2, \1,') # "spam, bacon, and eggs"

s4 = "Don't forget the spam."
s5 = s4.sub(/spam/) { |m| m.reverse } # "Don't forget the maps."

s4.sub!(/spam/) { |m| m.reverse }
# s4 is now "Don't forget the maps."


As this example shows, the special symbols \1, \2, and so on may be used in a substitute string. However, special variables such as $& (or the English version $MATCH) may not.

If the block form is used, the special variables may be used. However, if all you need is the matched string, it will be passed into the block as a parameter. If it is not needed at all, the parameter can of course be omitted.

The gsub method (global substitution) is essentially the same except that all matches are substituted rather than just the first:

s5 = "alfalfa abracadabra"
s6 = s5.gsub(/a[bl]/,"xx") # "xxfxxfa xxracadxxra"
s5.gsub!(/[lfdbr]/) { |m| m.upcase + "-" }
# s5 is now "aL-F-aL-F-a aB-R-acaD-aB-R-a"

20) The strip method removes whitespace from the beginning and end of a string. Its counterpart strip! modifies the receiver in place.

str1 = "\t \nabc \t\n"
str2 = str1.strip # "abc"
str3 = str1.strip! # "abc"
# str1 is now "abc" also

21) Sometimes we might want to delay the interpolation of values into a string. There is no perfect way to do this. One way is to use a block:

str = proc {|x,y,z| "The numbers are #{x}, #{y}, and #{z}" }

s1 = str.call(3,4,5) # The numbers are 3, 4, and 5
s2 = str.call(7,8,9) # The numbers are 7, 8, and 9

A more heavyweight solution is to store a single-quoted string, wrap it in double quotes, and evaluate it:

str = '#{name} is my name, and #{nation} is my nation'
name, nation = "Stephen Dedalus", "Ireland"
s1 = eval('"' + str + '"')
# Stephen Dedalus is my name, and Ireland is my nation.

22) The class method Regexp.escape escapes any characters that are special characters used in regular expressions. Such characters include the asterisk, question mark, and brackets.

str1 = "[*?]"
str2 = Regexp.escape(str1) # "\[\*\?\]"

23) The term lookahead refers to a construct that matches a part of the string ahead of the current location. It is a zero-width assertion because even when a match succeeds, no part of the string is consumed (that is, the current location does not change).

In this next example, the string "New World" will be matched if it is followed by "Symphony" or "Dictionary"; however, the third word is not part of the match:

s1 = "New World Dictionary"
s2 = "New World Symphony"
s3 = "New World Order"

reg = /New World(?= Dictionary| Symphony)/
m1 = reg.match(s1)
m.to_a[0] # "New World"
m2 = reg.match(s2)
m.to_a[0] # "New World"
m3 = reg.match(s3) # nil


Here is an example of negative lookahead:

reg2 = /New World(?! Symphony)/
m1 = reg.match(s1)
m.to_a[0] # "New World"
m2 = reg.match(s2)
m.to_a[0] # nil
m3 = reg.match(s3) # "New World"


In this example, "New World" is matched only if it is not followed by "Symphony."

24) Accessing Backreferences
Each parenthesized piece of a regular expression will be a submatch of its own. These are numbered and can be referenced by these numbers in more than one way. Let's examine the more traditional "ugly" ways first.

The special global variables $1, $2, and so on, can be used to reference matches:

str = "a123b45c678"
if /(a\d+)(b\d+)(c\d+)/ =~ str
puts "Matches are: '#$1', '#$2', '#$3'"
# Prints: Matches are: 'a123', 'b45', 'c768'
end


About the Author

Hal Fulton has two degrees in computer science from the University of Mississippi. He taught computer science for four years at the community college level before moving to Austin, Texas, for a series of contracts (mainly at IBM Austin). He has worked for more than 15 years with various forms of UNIX, including AIX, Solaris, and Linux. He was first exposed to Ruby in 1999, and in 2001 he began work on the first edition of this book, which was the second Ruby book in the English language. He has attended six Ruby conferences and has given presentations at four of those, including the first European Ruby Conference in Karlsruhe, Germany. He currently works at Broadwing Communications in Austin, Texas, working on a large data warehouse and related telecom applications. He works daily with C++, Oracle, and of course, Ruby.

Hal is still active daily on the Ruby mailing list and IRC channel, and has several Ruby projects in progress. He is a member of the ACM and the IEEE Computer Society. In his personal life, he enjoys music, reading, writing, art, and photography. He is a member of the Mars Society and is a space enthusiast who would love to go into space before he dies. He lives in Austin, Texas.

No comments: