DOC

advertisement
CSC 517
Review from last time
Closures
A closure is a block of code that “closes over”; that is, can access the
lexical environment of its definition.
# Class to generate adders
class AdderGen
def initialize(n)
@block = lambda {|a| n + a}
end
def add(a)
@block.call a
end
end
twoAdder = AdderGen.new 2
incrementer = AdderGen.new 1
puts incrementer.add(4)
puts twoAdder.add(6)
>>5
>>8
Here the instance variable @block is a closure.
 It remembers the parameter with which the initialize method was
called even after the initialize method exits.
 This value is used when the block is called in the add method.
 lambda comes from lambda calculus in functional programming. It is
used to generate new functions dynamically.
Exercise: Recall the example from the last class:
y = 5
f = lambda { |x| x + y }
puts f.call(5)
y = 6
puts f.call(5)
Ruby
1
CSC 517
If we change y, and then call f, the new value of y is used. What happens
if we change the value used in our adder above?
z = 2
capriciousAdder = AdderGen.new(z)
puts capriciousAdder.add(10)
z = 5
puts capriciousAdder.add(10)
Explain.
Uses of closures:
Write a program that prints out the contents of a file line by line, and
insures that the file is always closed. Let’s add to the built-in file class.
class File
def File.open_and_process(*args)
f = File.open(*args)
yield f
f.close()
end
end
File.open_and_process("testfile", "r") do |file|
while line = file.gets
puts line
end
end
This is line one
This is line two
This is line three
And so on...
Ruby
2
CSC 517
Currying
Currying means creating a new function out of an existing function by fixing
the value of some of its input parameters.
A simple example illustrates the concept. Consider a function which raises
its first parameter to the power specified by the second parameter.
def power(x,y)
val = 1
1.upto(y) {|a| val = val * x}
val
end
Now, one might want to define a function to square or cube a number.
Instead of defining a new method, the existing one can be curried. In
Ruby, we can curry a method using lambda. This is how the square and
cube functions are created:
square = lambda {|x| power(x,2)}
cube = lambda {|x| power(x,3)}
puts square.call(4)
puts cube.call(3)
>>16
>>27
OOP in Ruby
Ruby is purely object oriented. Everything in Ruby is an object.
Even numbers and strings are objects.
Ruby’s inheritance hierarchy: Singly rooted, with the class Object being
the superclass of all classes, similar to
.
The following examples demonstrate the pure object-oriented nature of
Ruby. Try this in the Interactive Ruby Browser:
15.modulo 4
>> 3
Ruby
3
CSC 517
3.14159.between? 3, 4
>> true
[11, 12, 13, 14, 15].slice 2, 4
>> [13, 14, 15]
Classes
Let us define a Person class with attributes name and height. It has a class
variable count, which keeps track of the number of instances of the class
created.
class Person
@@count = 0
def initialize(name, height)
@name = name
@height = height
@@count += 1
end
def self.info
puts "Number of instances = #{@@count}"
end
def to_s
"Name: #{@name} Height: #{@height}"
end
end
p = Person.new("Tom",170)
p2 = Person.new("Harry",165)
puts p
puts p2
Person.info
To instantiate an object of the new class, we write varname =
classname.new.
Ruby
4
CSC 517
This automatically calls the class’s initialize method. The arguments
to the new method are passed to the initialize method. The
initialize method may cause any component objects to be created.
Exercise: Answer the following questions about the code.
How are instance variables referred to?
How are class variables referred to?
What are methods whose name begins with “self”?
Whenever an object is printed its,
method is called.
Note: While printing, values of variables in double-quoted strings may be
accessed with
.
Attributes
In languages that provide information hiding, one usually defines getter and
setter methods. Why?
Ruby has shortcuts to define these. Since Ruby classes are open to
extension, we can add accessors to the definition of the Person class we
defined above.
class Person
attr_reader :name
attr_writer :height
attr_accessor :sex
end
p = Person.new("Tom",170)
p2 = Person.new("Harry",165)
p2.height = 190
puts p2
p.sex = "male"
puts p.sex
Ruby
5
CSC 517
The above code adds to the definition of the Person class.
 attr_reader creates a getter method
 attr_writer creates a setter
 attr_accessor creates both a getter and a setter.
Why would one want to create only attr_reader?
Accessors can also be written the “long way.”
class Person
def weight
@weight
end
def weight=(w)
@weight=w
end
end
Note that this defines two methods—not a way of performing an
assignment!
Is this shorter than equivalent Java code … in terms of lines of code, or
syntactic elements.
Inheritance
In Ruby, the < operator indicates inheritance. Let’s define class
Employee to inherit from the Person class defined above.
class Employee < Person
attr_accessor :salary
def initialize(name, height, salary)
super(name, height)
@salary = salary
end
def to_s
super + " Salary: #{@salary}"
Ruby
6
CSC 517
end
end
We add a new attribute salary to the Employee class. The
initialize method first calls the initialize method of the parent
class using super, and then sets the salary. Similarly the to_s method is
overridden in the Employee class.
What does super do?
e = Employee.new("Rob", 180, 80000)
puts e
>> Name: Rob Height: 180 Salary: 80000
Access Control
Ruby provides three levels of access control – [Questions]
 Public methods can be called by anyone—no access control is
enforced. Methods are public by default (except for
,
which is always private).
 Protected methods can be invoked only by objects of the defining
class and its subclasses. Access is kept within the family.
 Private methods cannot be called with an explicit receiver—the
receiver is always self. This means that private methods can be
called only in the context of the current object; you can’t invoke
another object’s private methods.
By contrast, if a method is protected, it may be called by any instance of
the defining class or its subclasses.
Can you think of a case where a private method would need to be called
by another object of the same class?
Ruby
7
CSC 517
If a method is private, it may be called only within the context of the calling
object—it is never possible to access another object’s private methods
directly, even if the object is of the same class as the caller.
class MyClass
def method1 # default is “public”
#...
end
protected # subsequent methods will be “protected”
def method2 # will be “protected”
#...
end
private # subsequent methods will be “private”
def method3 # will be “private”
#...
end
public # subsequent methods will be “public”
def method4 # and this will be “public”
#...
end
end
Alternatively, you can set access levels of named methods by listing them
as arguments to the access-control functions.
class MyClass
def method1
end
# ... and so on
public :method1, :method4
protected :method2
private :method3
end
Ruby
8
CSC 517
Abstract Methods
Ruby does not have abstract methods like Java or pure virtual functions
like C++.
However the same functionality can be simulated as follows (similarly to
subclassResponsibility in Smalltalk):
class Shape
def draw
raise NotImplementedError.new("Method not
implemented")
end
end
What will happen if we execute …
s = Shape.new.draw
Subclasses, then, have to provide an implementation for the draw method.
Why do you think that Ruby and other dynamic o-o languages don’t have
an official “abstract method” construct?
Duck Typing (Unbounded Polymorphism)
Ruby has dynamic type checking.
A method can be invoked on a variable whenever the type of object
assigned to the variable has that method defined on it.
This means that if the parameter passed to a method supports the methods
invoked on it by the called method, then the calls work.
This is unbounded polymorphism, which can only be found in dynamically
typed languages.
What’s the typical rule in statically typed languages?
Ruby
9
CSC 517
The dynamic approach is also called “duck typing,” after the expression, “If
it walks like a duck and talks like a duck then it must be a duck”. Here is
some code illustrating the concept:
class Duck
class Pig
def quack
def eat
puts "Quack!"
puts "I just eat!"
end
end
end
def walk
puts "Waddle"
class Test
end
def test_duck(duck)
end
duck.quack
duck.walk
class Toy
end
def quack
puts "Kwack!" end
end
t = Test.new
def walk
t.test_duck(Duck.new)
puts "Waggle"
t.test_duck(Toy.new)
end
t.test_duck(Pig.new)
end
What is the result of executing this code?
The test_duck method duck does not check the object type of the input
argument that is passed in.
So any object which has the methods quack and walk can be passed to
the method and the methods called on it will be successfully executed.
When we try to pass in an object lacking those methods, a
NoMethodError is thrown at run time.
Ruby
10
CSC 517
Duck typing trades off safety to save lines of code. Any adverse impact on
safety can be mitigated by carefully checking the code.
Unbounded polymorphism allegedly makes it possible to add more
functionality without affecting the existing design making one’s programs
more flexible.
Answer these questions on unbounded polymorphism.
Ruby
11
Download