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