DOC

advertisement
CSC 517
1
Regular expressions
Ruby was invented as “a better Perl,” and hence has excellent built-in
support for regular expressions.
This is why it is useful as a scripting language. Here’s an example:
"faculty".sub(/c/, "")
>>
"cheese".gsub(/e/, "o")
>>
There are many facets to regular expressions. A Ruby regular expression
is an object, and can be created by a call to new.
e = Regexp.new('\d\w*')
It can also be specified like this:
e = /\d\w*/
The \d and \w are character-class abbreviations.
We can, of course, use regular-expression objects in pattern matching:
'a0a0a0'.sub(e, "")
Any PCRE (Perl-compatible regular expression) can go between the two
slashes.
Making substitutions couldn't be easier than this:
str = str.gsub(/Steel/,'Titanium')
puts str
>>Titanium is the toughest material
We can constrain matching to occur at the start (^) or end ($) of a string.
"cheese".sub(/^e/, "o")
>> "cheese"
Lecture 6
Object-Oriented Languages and Systems
1
A character class is a set of characters between brackets. Any character
within the class matches a character class.
"anecdote".sub(/[aeio]/, "u")
>>
A hyphen may be used to signify a range:
"now is the time".sub(/[a-y]/, "z")
>>
Repetition can be specified by several special characters.
"choose".sub(/o+/, "e")
>>
"choose".sub(/o?/, "e")
>>
Modules and Mixins
Modules in Ruby are a way to group together methods, classes and
constants. They are similar to namespaces in languages such as
C++.
Of course, whenever you mix namespaces, you have the possibility
of name clashes.
Say you have a graphics library that contains classes for Window and
(window.rb) and Border (border.rb).1
Both window.rb and border.rb have a top method, which gives
the position of the top of the Window and the top of the Border,
respectively.
1
A considerably cornier example can be found in the Module chapter of
Programming Ruby.
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
2
CSC 517
3
An application programmer wants to lay out windows with borders,
and thus loads both window.rb and border.rb into the program.
What’s the problem here?
Let’s consider how we might “solve” this. Take a minute, and write
down all the possibilities you can think of.
Ruby’s answer is the module mechanism.
Modules define a namespace. What’s a namespace?
The window functions can go into one module
module Window
def Window.top
# ..
end
def Window.bottom
# ..
end
end
and the border functions can go into another
module Border
def Border.top
# ...
end
def Border.width
# ..
end
end
Look at the names of module constants and methods. What do they
remind you of? Class constants and methods! Same conventions.
Lecture 6
Object-Oriented Languages and Systems
3
When a program needs to use these modules, it can simply load the
two files using the Ruby require statement, and reference the
qualified names.
require 'window'
require 'border'
trueTop = Window.top + Border.Top
Some differences between require and include:
 include makes features available, but does not execute the
code.
 require loads and executes the code one time (like a C
#include).
 load loads and executes the code every time it is
encountered.
However, only include will work to create mixins (see below).
Mixins
The most interesting use of modules is to define mixins.
When you include a module within a class, all its functionality
becomes available to the class.
Not only can modules contain class methods; they can also contain
instance methods.
Let’s contrast this with #include in (what languages?) How are
mixins different? #include files define methods that can be called
(like class methods) but not applied to objects (like instance
methods).
Let’s contrast this with multiple inheritance in (what languages?).
How are mixins different? The same methods can be added to any
number of different classes, regardless of where they are in the
inheritance hierarchy.
So, instead of implementing interfaces as in Java, one uses mixins in
Ruby when one wants to simulate multiple inheritance.
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
4
CSC 517
5
Consider the following code:
module Introspect
def kind
puts "#{self.class.name}"
end
end
class Animal
include Introspect
def initialize(name)
@name = name
end
end
class Car
include Introspect
def initialize(model)
@model = model
end
end
d = Animal.new("Cat")
c = Car.new("Ferrari")
d.kind # kind method is available through …
c.kind # .. the mixin Introspect
>>Animal
>>Car
Lecture 6
Object-Oriented Languages and Systems
5
Exercise: Take a look at the TeachingAssistant example defined here
and answer here.
 What happens if you remove the different to_s methods?
 What does this tell you about the way that Ruby resolves
conflicts among names inherited from mixins?
Comparable
A good example of the power of modules is Comparable, from the
Ruby library.
To use comparable in a class, the class needs to define a method
called <=> (sometimes called “rocket”).
Once we define this method, we get a lot of useful comparison
functions, such as <, >, <=, >=, == and the method between? for
free.
What is this like in Java?
Here is an example. Suppose that we have a Line class:
class Line
def initialize(x1, y1, x2, y2)
@x1, @y1, @x2, @y2 = x1, y1, x2, y2
end
end
We compare two lines on the basis of their lengths.
We add the Comparable mixin as follows:
class Line
include Comparable
def length_squared
(@x2-@x1) * (@x2-@x1) + (@y2-@y1) * (@y2-@y1)
end
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
6
CSC 517
7
def <=>(other)
self.length_squared <=> other.length_squared
end
end
<=> returns 1,0, or –1, depending on whether the first argument is
greater than, equal to, or less than the second argument.
We delegate the call to <=> of the Fixnum class, which compares
the squares of the lengths.
Now we can use the Comparable methods on Line objects:
l1 = Line.new(1, 0, 4, 3)
l2 = Line.new(0, 0, 10, 10)
puts l1.length_squared
if l1 < l2
puts "Line 1 is shorter than Line 2"
else if l1 > l2
puts "Line 1 is longer than Line 2"
else
puts "Line 1 is just as long as Line 2"
end
end
>>Line 1 is shorter than Line 2
Composing Modules
Enumerable (p. 120 of Pickaxe book) is a standard mixin, which can be
included in any class.
It has a very useful method inject, which can be used to repeatedly
apply an operation to adjacent elements in a set:
[1, 2, 3, 4, 5].inject {|v, n| v+n }
>>15
Lecture 6
Object-Oriented Languages and Systems
7
Many built-in classes include Enumerable, including Array and Range.
(‘a’ .. ‘z’).inject {|v, n| v+n }
Exercise: Use inject to define a factorial method.
Let’s define a VowelFinder class that includes Enumerable. It will
have an each method for returning successive vowels from a string.
class VowelFinder
include Enumerable
def initialize(string)
@string = string
end
def each
@string.scan(/[aeiou]/) do |vowel|
yield vowel
end
end
end
Here’s an example of its use:
VowelFinder.new("abacadabra").inject {|v, n| v+n}
>>aaaaa
However, would be nice not to have to type out the “inject” each time, if
we just want to work on a different collection. “+” does different things for
different classes.
 When used with numbers, it
 When used with strings, it
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
8
CSC 517
9
Let’s define a module that encapsulates the call to inject and +.
module Reducible
def sum_reduce
inject {|v, n| v+n}
end
end
class Array
include Reducible
end
class Range
include Reducible
end
class VowelFinder
include Reducible
end
[1, 2, 3, 4, 5].sum_reduce
('a' .. 'z').sum_reduce
vf = VowelFinder.new
("The quick brown fox jumped over the lazy dog.")
puts vf.sum_reduce
Exercise: Define a method that returns a string containing the vowels, so
that the above can be done with only one method call.
Simulating Multiple Inheritance
Modules can be used to simulate multiple inheritance in Ruby.
Lecture 6
Object-Oriented Languages and Systems
9
Suppose one wants to add tags to Strings. Then one can define
a Taggable module and include it into the class.
require 'set' # A collection of unordered values
with no duplicates
# Include this module to make your class taggable. The names of the
# instance variable and the setup method are prefixed with
"taggable_"
# to reduce the risk of namespace collision. You must call
# taggable_setup before you can use any of this module's methods.
module Taggable
attr_accessor :tags
def taggable_setup
@tags = Set.new
end
def add_tag(tag)
@tags << tag
end
def remove_tag(tag)
@tags.delete(tag)
end
end
class TaggableString < String
include Taggable
def initialize(*args)
super
taggable_setup
end
end
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
10
CSC 517
s = TaggableString.new('It was the best of times,
it was the worst of times.')
s.add_tag 'dickens'
s.add_tag 'quotation'
s.tags # =>
#<Set: {"dickens", "quotation"}>
What happens if we execute
puts s ?
Exercise: Add a to_s method to the taggable-string example. Then
submit it here.
Is Multiple Inheritance Good?
Multiple inheritance is generally used in one of four ways.
• Multiple independent protocols. A class is created by
combining completely different superclasses.
For example, in Eiffel, the library class WINDOW is a
subclass of SCREENMAN, RECTANGLE, and
TWO_WAY_TREE.
• Mix and match. Several classes are created specially for
subsequent combination. Mixins are an example of this.
• Submodularity. While creating a situation, modularity of
subparts is noticed and factored out. For example, in a class
representing mortgages, one might factor out FIXED_RATE
and ADJUSTABLE mortgages.
• Separation of interface and implementation. An abstract
class is created to define the interface.
A second class is created to encapsulate the implementation
details.
E.g., a Stack class could be created as a subclass of
StackInterface and StackImplementation.
Lecture 6
Object-Oriented Languages and Systems
11
11
Problems posed by multiple inheritance: Multiple inheritance can
increase reusability and consistency in a system.
However, with its power also comes complexity and ambiguity.
The following problems arise:
• Name collision. Two features with the same name are
inherited from different superclasses.
The parent classes may be correct and consistent. But there
is a conflict when a descendant class inherits a method with
the same name (e.g., initialize) from each.
• Repeated inheritance. Because there may be more than
one path from a class to its ancestor classes, a class can
inherit from the same superclass more than once.
We will consider this problem shortly.
• Method combination. An object may need to execute
methods with the same name defined in different
superclasses (e.g., intialize).
• Implementation difficulties. Because classes may be
combined in various ways, it is difficult to represent objects
and find methods without a lot of search or indirection.
• Misuse. Because a class has the ability to inherit from as
many other classes as necessary, there is a tendency to use
inheritance more often than it should be used.
Consider declaring an ApplePie class to inherit from
Apple and Cinnamon.
Why is this not good?
°
°
ApplePie should simply be a client of Apple and
Cinnamon
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
12
CSC 517
Resolution of naming conflicts: Suppose class C inherits two different
methods for copy, from two different superclasses.
In Ruby, compound selectors are used (Window.top, etc).
Renaming in Eiffel: In Eiffel, no name conflicts are permitted between
inherited features.
There is a rename clause to remove them:
class C inherit
A rename x as x1, y as y1;
B rename x as x2, y as y2;
feature …
This inherit clause would be illegal without renaming.
Sometimes renaming can be used to give more appropriate names to
features.
The TREE class defines a routine called insert_subtree.
For class WINDOW, this is not an appropriate name; add_subwindow
is much better.
Renaming allows the better name to be used.
Extending Specific Objects
There are two common methods for specifying objects: set based or
prototype based.
Within a set-based language:
 We describe a class that abstracts what we know to be true of
the object we are attempting to specify.
 We create instances of the object from the class to perform the
actual work.
 Just as classes describe objects, classes are described by
metaclasses.
 Each object instance is unique and holds its own internal values.
Lecture 6
Object-Oriented Languages and Systems
13
13
 We can create more specific subclasses of a given class that
either modify the properties of the parent or add additional
properties.
 When an object receives a message it does not understand, it
consults its ancestors, asking each of them to handle the
message.
Within a prototype-based language:
 We create an object that is a concrete representation of the object
we are attempting to specify, called a prototype.
 We copy, or clone, the prototype to obtain multiple instances of
the object.
 Each instance holds only what is unique to itself and a reference
to its prototype, called an extension.
 There is no true distinction between an instance and a prototype.
Any instance can be cloned, becoming the prototypical object for
its duplicates.
 When an object receives a message it does not understand, it
delegates the message to its prototypes, asking each of them to
grant it the ability to handle the message.
We can quickly see the philosophical differences between sets and
prototypes.
In a set-based object system, we define objects top down by specifying
the abstract and using that specification to create the specific.
For example, to describe a Cat object we collect properties that we believe
to be common to all Cats and create a Cat class. Then we use that class
to create unique Cats.
In a prototype object system, we define objects bottom up by specifying a
specific instance and modifying it as necessary to represent other
instances.
For example, to describe a Cat Escher we create an object that contains
her properties (female, black fur, annoying).
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
14
CSC 517
If we later discover a Cat Berlioz, we specify what we know about
Berlioz (male, orange fur) and assume that the remainder is identical to the
prototypical Cat Escher.
This system naturally evolves the assumption that all Cats are annoying.
Some researchers believe that prototype object systems make better
usage of the human ability to generalize from their experience with
concrete situations.
How does this apply to Ruby?
Ruby can act as a set-based language: Using include augments a class
definition, and hence adds functionality to all objects of a class.
But it can also act as a prototype-based language: There is a way to add
functionality to just specific instances of a class, by using the
Object#extend method.
Let us suppose that there is a mild-mannered Person class.
class Person
attr_reader :name, :age, :occupation
def initialize(name, age, occupation)
@name, @age, @occupation = name, age, occupation
end
def mild_mannered?
true
end
end
jimmy = Person.new('Jimmy Olsen', 21, 'cub reporter')
clark = Person.new('Clark Kent', 35, 'reporter')
jimmy.mild_mannered?
# => true
clark.mild_mannered?
# => true
Lecture 6
Object-Oriented Languages and Systems
15
15
But it happens that some Person objects are not as mild-mannered as
they might appear. Some of them have super powers.
module SuperPowers
def fly
'Flying!'
end
def leap(what)
"Leaping #{what} in a single bound!"
end
def mild_mannered?
false
end
def superhero_name
'Superman'
end
end
If we use include to mix the SuperPowers module into the Person
class, it will give every person super powers. Some people are bound to
misuse such power. Instead, we'll use extend to give super powers only
to certain people:
clark.extend(SuperPowers)
puts clark.superhero_name
puts clark.fly
puts clark.mild_mannered?
puts jimmy.mild_mannered?
>>
>>
>>
>>
CSC/ECE 517 Lecture Notes
© 2010 Edward F. Gehringer
16
Download