Couple of Days back i started reading The Ruby Way By Hal Fulton
Today i have read next 3 chapters(9-10-11) from the book and I wanted to share few important quotations i found from these chapters.
Tomorrow i will continue updating this ruby series with next 3 chapters.
1) Working with Sets
Ruby has a Set class that hides more of the detail from the programmer.
A simple require makes the Set class available:
require 'set'
Creating a new set is easy. The [] method works much as for hashes. The new method takes an optional enumerable object and an optional block. If the block is specified, it is used as a kind of "preprocessor" for the list (like a map operation).
s1 = Set[3,4,5] # {3,4,5} in math notation
arr = [3,4,5]
s2 = Set.new(arr) # same
s3 = Set.new(arr) {|x| x.to_s } # set of strings, not numbers
2) Union is accomplished by the union method (aliases are | and +):
x = Set[1,2,3]
y = Set[3,4,5]
a = x.union(y) # Set[1,2,3,4,5]
b = x | y # same
c = x + y # same
Intersection is done by intersection or &, which is an alias:
x = Set[1,2,3]
y = Set[3,4,5]
a = x.intersection(y) # Set[3]
b = x & y # same
The unary minus is set difference.
diff = Set[1,2,3] - Set[3,4,5] # Set[1,2]
3) We can test the relationship of two sets: Is the receiver a subset of the other set? Is it a proper subset? Is it a superset?
x = Set[3,4,5]
y = Set[3,4]
x.subset?(y) # false
y.subset?(x) # true
y.proper_subset?(x) # true
x.subset?(x) # true
x.proper_subset?(x) # false
x.superset?(y) # true
4) Finally, two sets can be tested for equality in an intuitive way:
Set[3,4,5] == Set[5,4,3] # true
5) It's possible of course to iterate over a set, but (as with hashes) do not expect a sensible ordering because sets are inherently unordered, and Ruby does not guarantee a sequence. (You may even get consistent, unsurprising results at times, but it is unwise to depend on that fact.)
s = Set[1,2,3,4,5]
s.each {|x| puts x; break } # Output: 5
6) Implementing a Stricter Stack
Example:
class Stack
def initialize
@store = []
end
def push(x)
@store.push x
end
def pop
@store.pop
end
def peek
@store.last
end
def empty?
@store.empty?
end
end
class StackDemo
st=Stack.new
st.push(100)
st.push(200)
puts st.peek
end
7) In ruby implementing a sorted binary tree is quite easy when compared to java and i can assure that.
class Tree
# Assumes definitions from
# previous example...
attr_accessor :left
attr_accessor :right
attr_accessor :data
def initialize(x=nil)
@left = nil
@right = nil
@data = x
end
def insert(x)
if @data == nil
@data = x
elsif x <= @data
if @left == nil
@left = Tree.new x
else
@left.insert x
end
else
if @right == nil
@right = Tree.new x
else
@right.insert x
end
end
end
def inorder()
@left.inorder {|y| yield y} if @left != nil
yield @data
@right.inorder {|y| yield y} if @right != nil
end
def preorder()
yield @data
@left.preorder {|y| yield y} if @left != nil
@right.preorder {|y| yield y} if @right != nil
end
def postorder()
@left.postorder {|y| yield y} if @left != nil
@right.postorder {|y| yield y} if @right != nil
yield @data
end
end
items = [50, 20, 80, 10, 30, 70, 90, 5, 14,28, 41, 66, 75, 88, 96]
tree = Tree.new
items.each {|x| tree.insert(x)}
tree.inorder {|x| print x, " "}
print "\n"
tree.preorder {|x| print x, " "}
print "\n"
tree.postorder {|x| print x, " "}
print "\n"
# Output:
# 5 10 14 20 28 30 41 50 66 70 75 80 88 90 96
# 50 20 10 5 14 30 28 41 80 70 66 75 90 88 96
# 5 14 10 28 41 30 20 66 75 70 88 96 90 80 50 print "\n"
8) Converting a Tree to a String or Array
The same old tricks that allow us to traverse a tree will allow us to convert it to a string or array if we want. Here we assume an inorder traversal, though any other kind could be used:
class Tree
# Assumes definitions from
# previous example...
def to_s
"[" +
if left then left.to_s + "," else "" end +
data.inspect +
if right then "," + right.to_s else "" end + "]"
end
def to_a
temp = []
temp += left.to_a if left
temp << data
temp += right.to_a if right
temp
end
end
items = %w[bongo grimace monoid jewel plover nexus synergy]
tree = Tree.new
items.each {|x| tree.insert x}
str = tree.to_a * ","
# str is now "bongo,grimace,jewel,monoid,nexus,plover,synergy"
arr = tree.to_a
# arr is now:
# ["bongo",["grimace",[["jewel"],"monoid",[["nexus"],"plover",
# ["synergy"]]]]]
Note that the resulting array is as deeply nested as the depth of the tree from which it came. You can, of course, use flatten to produce a non-nested array.
9) The core of all Ruby I/O is the IO class, which defines behavior for every kind of input/output operation. Closely allied to IO (and inheriting from it) is the File class. There is a nested class within File called Stat, which encapsulates various details about a file that we might want to examine (such as its permissions and time stamps). The methods stat and lstat return objects of type File::Stat.
10) Opening and Closing Files
The class method File.new, which instantiates a File object also opens that file. The first parameter is naturally the filename.
file1 = File.new("one") # Open for reading
file2 = File.new("two", "w") # Open for writing
The optional second parameter is called the mode string, telling how to open the file (whether for reading, writing, and so on). (The mode string has nothing to do with the mode as in permissions.) This defaults to "r" for reading.
File.open("somefile","w") do |file|
file.puts "Line 1"
file.puts "Line 2"
file.puts "Third and final line"
end
# The file is now closed
11) a File
Suppose that we want to open a file for reading and writing. This is done simply by adding a plus sign (+) in the file mode when we open the file.
f1 = File.new("file1", "r+")
# Read/write, starting at beginning of file.
f2 = File.new("file2", "w+")
# Read/write; truncate existing file or create a new one.
f3 = File.new("file3", "a+")
# Read/write; start at end of existing file or create a
# new one.
12) Frequently we want to store and retrieve data in a more transparent manner. The Marshal module offers simple object persistence, and the PStore library builds on that functionality.
In many cases we want to create an object and simply save it for use later. Ruby provides rudimentary support for such object persistence or marshaling. The Marshal module enables programs to serialize and unserialize Ruby objects in this way.
# array of elements [composer, work, minutes]
works = [["Leonard Bernstein","Overture to Candide",11],
["Aaron Copland","Symphony No. 3",45],
["Jean Sibelius","Finlandia",20]]
# We want to keep this for later...
File.open("store","w") do |file|
Marshal.dump(works,file)
end
# Much later...
File.open("store") do |file|
works = Marshal.load(file)
end
13) KirbyBase is one of those little libraries that everyone should learn to use. At present it is not a standard library packaged with Ruby; if it were, its usefulness might increase even more.
KirbyBase is the work of Jamey Cribbs (apparently named after his dog). Although it is in many ways a full-fledged database, it is mentioned here rather than along with MySQL and Oracle for a few reasons.
First, it is not a separate application. It is a pure-Ruby library and is not usable without Ruby. Second, it doesn't know SQL at all; so if you are dependent on SQL for some reason, it isn't for you. Third, if your application is sophisticated enough, you may have issues with KirbyBase's functionality or its speed
Example:
require 'kirbybase'
require 'date'
# To run local, single-user, uncomment next line.
db = KirbyBase.new
#----------------------- Drop Table Example --------------------------------
# If table exists, delete it.
db.drop_table(:plane) if db.table_exists?(:plane)
#----------------------- Create Table Example ------------------------------
# Create a table.
plane_tbl = db.create_table(:plane, :name, :String, :country, :String,
:role, :String, :speed, :Integer, :range, :Integer, :began_service, :Date,
:still_flying, :Boolean) { |obj| obj.encrypt = false }
# 1) Insert a record using an array for the input values.
plane_tbl.insert('FW-190', 'Germany', 'Fighter', 399, 499, Date.new(1942,12,1), false)
# 1) Update record using a Hash, Struct, or an Array.
plane_tbl.update(:speed => 405, :range => 1210) { |r| r.name == 'P-51' }
# Delete 'FW-190' record.
plane_tbl.delete { |r| r.name == 'FW-190' }
# Select all records, including all fields in result set.
plane_tbl.select.each { |r|
puts(('%s ' * r.members.size) % r.to_a)
}
14) Connecting to External Databases
Interfacing to MySQL
Ruby's MySQL interface is among the most stable and fully functional of its database interfaces. It is an extension and must be installed after both Ruby and MySQL are installed and running.
There are three steps to using this module after you have it installed. First load the module in your script; then connect to the database; and finally work with your tables. Connecting requires the usual parameters for host, username, password, database, and so on.
Example:
require 'mysql'
m = Mysql.new("localhost","root","","ruby")
r = m.query("SELECT * FROM people ORDER BY name")
r.each_hash do |f|
print "#{f['name']} - #{f['email']}"
end
15) There is no real "constructor" in Ruby as there is in C++ or Java. The concept is certainly there because objects have to be instantiated and initialized, but the behavior is somewhat different.
In Ruby, a class has a class method new, which is used to instantiate new objects. The new method calls the user-defined special method initialize, which then initializes the attributes of the object appropriately, and new returns a reference to the new object.
Solution: There is nothing to prevent creation of additional class methods that return new objects.
Example:
class ColoredRectangle
def initialize(r, g, b, s1, s2)
@r, @g, @b, @s1, @s2 = r, g, b, s1, s2
end
def ColoredRectangle.white_rect(s1, s2)
new(0xff, 0xff, 0xff, s1, s2)
end
def ColoredRectangle.gray_rect(s1, s2)
new(0x88, 0x88, 0x88, s1, s2)
end
def ColoredRectangle.colored_square(r, g, b, s)
new(r, g, b, s, s)
end
def ColoredRectangle.red_square(s)
new(0xff, 0, 0, s, s)
end
def inspect
"#@r #@g #@b #@s1 #@s2"
end
end
a = ColoredRectangle.new(0x88, 0xaa, 0xff, 20, 30)
b = ColoredRectangle.white_rect(15,25)
c = ColoredRectangle.red_square(40)
16) Inheriting from a Superclass
We can inherit from a class by using the < symbol:
class Boojum < Snark
# ...
end
17) Copying an Object
The Ruby built-in methods Object#clone and #dup produce copies of their receiver. They differ in the amount of context they copy. The dup method copies just the object's content, whereas clone also preserves things such as singleton classes associated with the object
18) Using initialize_copy
When you copy an object with dup or clone, the constructor is bypassed. All the state information is copied.
But what if you don't want this to happen? Consider this example:
class Document
attr_accessor :title, :text
attr_reader :timestamp
def initialize(title, text)
@title, @text = title, text
@timestamp = Time.now
end
end
doc1 = Document.new("Random Stuff",File.read("somefile"))
sleep 300 # Wait awhile...
doc2 = doc1.clone
doc1.timestamp == doc2.timestamp # true
# Oops... the timestamps are the same!
When a Document is created, it is given a time stamp. If we copy that object, we copy the time stamp also. But what if we wanted instead to capture the time that the copy operation happened?
Defining an initialize_copy makes this possible. This method is called when an object is copied. It is analogous to initialize, giving us complete control over the object's state.
class Document # Reopen the class
def initialize_copy(other)
@timestamp = Time.now
end
end
19) Understanding allocate
In rare circumstances you might want to create an object without calling its constructor (bypassing initialize). For example, maybe you have an object whose state is determined entirely by its accessors; then it isn't necessary to call mew (which calls initialize) unless you really want to. Imagine you are gathering data a piece at a time to fill in the state of an object; you might want to start with an "empty" object rather than gathering all the data up front and calling the constructor.
The allocate method was introduced in Ruby 1.8 to make this easier. It returns a "blank" object of the proper class, yet uninitialized.
class Person
attr_accessor :name, :age, :phone
def initialize(n,a,p)
@name, @age, @phone = n, a, p
end
end
p1 = Person.new("John Smith",29,"555-1234")
p2 = Person.allocate
p p1.age # 29
p p2.age # nil
20) Working with Modules
There are two basic reasons to use modules in Ruby. The first is simply namespace management; we'll have fewer name collisions if we store constants and methods in modules. A method stored in this way (a module method) is called with the module name; that is, without a real receiver. This is analogous to the way a class method is called. If we see calls such as File.ctime and FileTest.exist?, we can't tell just from context that File is a class and FileTest is a module.
The second reason is more interesting: We can use a module as a mixin. A mixin is like a specialized implementation of multiple inheritance in which only the interface portion is inherited.
We've talked about module methods, but what about instance methods? A module isn't a class, so it can't have instances, and an instance method can't be called without a receiver.
As it turns out, a module can have instance methods. These become part of whatever class does the include of the module.
module MyMod
def meth1
puts "This is method 1"
end
end
class MyClass
include MyMod
# ...
end
x = MyClass.new
x.meth1 # This is method 1
21) It is also possible to mix in the instance methods of a module as class methods.
Example:
module MyMod
def meth3
puts "Module instance method meth3"
puts "can become a class method."
end
end
class MyClass
class << self # Here, self is MyClass
include MyMod
end
end
MyClass.meth3
# Output:
# Module instance method meth3
# can become a class method.
The extend method is useful here. This example simply becomes:
class MyClass
extend MyMod
end
22) Freezing Objects
Sometimes we want to prevent an object from being changed. The freeze method (in Object) allows us to do this, effectively turning an object into a constant.
str = "This is a test. "
str.freeze
begin
str << " Don't be alarmed." # Attempting to modify
rescue => err
puts "#{err.class} #{err}"
end
arr = [1, 2, 3]
arr.freeze
begin
arr << 4 # Attempting to modify
rescue => err
puts "#{err.class} #{err}"
end
# Output:
# TypeError: can't modify frozen string
# TypeError: can't modify frozen array
However, bear in mind that freeze operates on an object reference, not on a variable.
23) sort_by method is now standard in Ruby, and it is more efficient than this one because it implements a Schwartzian transform (named for Randal Schwartz of Perl fame), saving the transformed values so as to avoid recomputing them.
Example
class Person
attr_reader :name, :age, :height
def initialize(name, age, height)
@name, @age, @height = name, age, height
end
def inspect
"#@name #@age #@height"
end
end
class Array
def sort_by(sym) # Our own version of sort_by
self.sort {|x,y| x.send(sym) <=> y.send(sym) }
end
end
people = []
people << Person.new("Hansel", 35, 69)
people << Person.new("Gretel", 32, 64)
people << Person.new("Ted", 36, 68)
people << Person.new("Alice", 33, 63)
p1 = people.sort_by(:name)
p2 = people.sort_by(:age)
p3 = people.sort_by(:height)
p p1 # [Alice 33 63, Gretel 32 64, Hansel 35 69, Ted 36 68]
p p2 # [Gretel 32 64, Alice 33 63, Hansel 35 69, Ted 36 68]
p p3 # [Alice 33 63, Gretel 32 64, Ted 36 68, Hansel 35 69]
24) Storing Code As Objects
Ruby gives you several alternatives when it comes to storing a chunk of code in the form of an object.
The built-in class Proc is used to wrap Ruby blocks in an object. Proc objects, like blocks, are closures, and therefore carry around the context in which they were defined.
myproc = Proc.new { |a| puts "Param is #{a}" }
myproc.call(99) # Param is 99
Proc objects are also created automatically by Ruby when a method defined with a trailing & parameter is called with a block:
def take_block(x, &block)
puts block.class
x.times {|i| block[i, i*i] }
end
take_block(3) { |n,s| puts "#{n} squared is #{s}" }
This example also shows the use of brackets ([]) as an alias for the call method. The output is shown here:
Proc
0 squared is 0
1 squared is 1
2 squared is 4
If you have a Proc object, you can pass it to a method that's expecting a block, preceding its name with an &, as shown here:
myproc = proc { |n| print n, "... " }
(1..3).each(&myproc) # 1... 2... 3...
25) When a module is included into a class, Ruby in effect creates a proxy class as the immediate ancestor of that class. You may or may not find this intuitive. Any methods in an included module are "masked" by any methods that appear in the class.
module MyMod
def meth
"from module"
end
end
class ParentClass
def meth
"from parent"
end
end
class ChildClass < ParentClass
include MyMod
def meth
"from child"
end
end
x = ChildClass.new
p x.meth # from child
Here's a similar example, where the child method invokes super instead of returning a simple string. What do you expect it to return?
# MyMod and ParentClass unchanged
class ChildClass < ParentClass
include MyMod
def meth
"from child: super = " + super
end
end
x = ChildClass.new
p x.meth # from child: super = from module
This previous example shows that the module is really the new parent of the class. What if we also let the module invoke super in the same way?
module MyMod
def meth
"from module: super = " + super
end
end
# ParentClass is unchanged
class ChildClass < ParentClass
include MyMod
def meth
"from child: super = " + super
end
end
x = ChildClass.new
p x.meth # from child: super = from module: super = from parent
The meth from MyMod can call super only because there actually is a meth in the superclass (that is, in at least one ancestor). What would happen if we called this in another circumstance?
module MyMod
def meth
"from module: super = " + super
end
end
class Foo include MyMod
end
x = Foo.new
x.meth
This code would result in a NoMethodError (or a call to method_missing if one existed).
26) Dynamically Instantiating a Class by Name
Given a string containing the name of a class, how can we create an instance of that class?
The proper way is with const_get, which we just saw. All classes in Ruby are normally named as constants in the "global" namespacethat is, members of Object.
classname = "Array"
klass = Object.const_get(classname)
x = klass.new(4, 1) # [1, 1, 1, 1]
What if the names are nested? It turns out this doesn't work:
const_get donot work and there is one alternative using inject method
27) Removing Definitions
The dynamic nature of Ruby means that pretty much anything that can be defined can also be undefined. One conceivable reason to do this is to decouple pieces of code that are in the same scope by getting rid of variables after they have been used; another reason might be to specifically disallow certain dangerous method calls. Whatever your reason for removing a definition, it should naturally be done with caution because it can conceivably lead to debugging problems.
The radical way to undefine something is with the undef keyword (not surprisingly the opposite of def). You can undef methods, local variables, and constants at the top level. Although a class name is a constant, you cannot remove a class definition this way.
class Parent
def alpha
puts "parent alpha"
end
def beta
puts "parent beta"
end
end
class Child < Parent
def alpha
puts "child alpha"
end
def beta
puts "child beta"
end
remove_method :alpha # Remove "this" alpha
undef_method :beta # Remove every beta
end
x = Child.new
x.alpha # parent alpha
x.beta # Error!
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.
Tuesday, 26 August 2008
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment