CSC/ECE 517 Fall 2011/ch4 4b ds

From Expertiza_Wiki
Jump to navigation Jump to search

Wiki textbook chapter on OOLS Lecture 5. Covers the basic of closures and blocks, currying and OOP in Ruby. We will then go into the details of object-oriented concepts such as data encapsulation, inheritance and polymorphism. The wiki ends by covering the most popular feature in Ruby called duck typing (unbounded polymorphism).Since the lecture 5 material in the CSC517 covers object-oriented concepts specific to Ruby, thus we have restricted to the same.

Closures

A closure is a block of code that “closes over”. This means it can access the lexical environment of its definition. A closure may be defined in one scope and get called outside this scope. Thus a closure retains the values of all the variables that were in scope when the closure was defined. Closures <ref>Closures</ref>help in making the code short such that one can do more with less code. Ruby supports closures through Blocks and Procs.

Blocks

A block is a set of Ruby statements either between braces or a do/end pair, and appear only immediately after a method invocation. Ruby uses the following standard - braces for single-line blocks and do/end for multiline blocks.

{ puts "Hello World" }                # braces block

do
{ puts "Hello World 1" }              # do/end block
{ puts "Hello World 2" }
{ puts "Hello World 3" }
end

Blocks can be instantiated as a Proc object using the Proc or lambda method. The block is then bound to a set of local variables. Once bound, the block code may be called in different contexts through these set variables.Using the example mentioned in lecture 5 in class.

# Class to generate adders
class AdderGen
  def initialize(n)
	@block = lambda {|a| n + a}   #define lambda proc
  end
 
  def add(a)
	@block.call a                 #call proc object
  end
end
twoAdder = AdderGen.new 2
incrementer = AdderGen.new 1
puts incrementer.add(4)
puts twoAdder.add(6)
Output
5
8

@block is the Ruby closure that gets initialized when the lambda method gets called through the class initialize method. The block remembers the parameter with which the initialize method was called even after the initialize method exits. This value of the block is used during the add method invocation.

block = lambda { |x| "Hello #{x}!" }  
puts block.call 'World'
Output
Hello World!

Above code shows example of passing arguments using lambda.

Procs And Lambdas

There are three ways to create a Proc object in Ruby.

Proc.new

Proc.new<ref>Proc and Lamda</ref> takes in a block and returns a Proc object which will run the code in the block when call method is invoked. In Proc.new created proc the return control is from the method enclosing the Proc.

proc_object = Proc.new {puts "Create Proc.new object"}
proc_object.call

Proc method

The proc method is equivalent to Proc.new in Ruby 1.9, but in Ruby 1.8 it is equivalent to lambda.

proc_object = proc {puts "Create proc from Proc method"}
proc_object.call

Lambda method

This creates a proc object using the lambda method.In a lambda created proc, the return statement returns only from the proc itself. This is the difference from the Proc.new created proc.

proc_object = lambda {puts "Create proc from Lambda method"}
proc_object.call

Currying

Currying is the concept of creating a new function out of an existing function by fixing the value of some of its input parameters. Currying thus makes a generic function more specific to one's needs. Consider the following example where we have a Proc for the addition of two numbers. Using currying we can fix the value of the first argument so that when the curried function is called later on with only one argument, it can use the earlier fixed value as the other argument for its addition. Ruby 1.9 allows the creation of a curry-able proc by calling the curry method on it.

add = lambda {|a,b| a + b}
puts add[1,2]                        #original addition function add

curried_add = add.curry              #curried function curried_add 
add_to_ten = curried_add[10]         #only 1 number specified to curried function curried_add, by fixing one of the input values
add_to_twenty = curried_add[20]      #thus the curried function always adds 10 and 20 to the second number when we use it with
                                     #add_to_ten  and add_to_twenty respectively

puts add_to_ten[5]                   #adding second number 5 to the fixed value of 10
puts add_to_twenty[5]                #adding second number 5 to the fixed value of 20
Output
3
15
25

Class

A class is the blueprint from which individual objects are created. In object-oriented programming classes are the basic elements which provide inheritance, polymorphism and encapsulation, the three pillars. You can have class<ref>Ruby Classes</ref> methods and instance methods. Class methods are methods that are called on a class and instance methods are methods that are called on an instance of a class. Here is the example for a class BookInStock.

 class BookInStock                   #BookInStock is a class name
   def initialize(isbn, price)       #initialize is used to initialize instance variables
      @isbn = isbn                   #@isbn and @price are instance variables
      @price = Float(price)
   end
   def self.book                     #self denotes class method
       puts “Class method”
   end
 end 
 class ParkingLot                  
   def initialize(n)
     @spotsAvailable = n                     #initialize the variable
   end
   def park                                      
     puts "Park your car"
     @spotsAvailable = @spotsAvailable - 1   #decrement the number of available slots
   end
   def unpark
     puts "Unpark your car"
     @spotsAvailable = @spotsAvailable + 1   #increment the available slots
   end
 end

Here, initialize is a special method in Ruby. When you call BookInStock.new to create a new BookInStock object, Ruby creates an uninitialized object and then calls that object's initialize method, passing in any parameters that were passed to new. This gives you a chance to write code that sets up your object's state. This is very similar to constructors in Java. Then there is self.book which is class method which is very similar to static methods in Java. The variables starting with @ are instance variables meaning that they are associated with the instances. There is another example of class ParkingLot which has two methods defined, park and unpark. It maintains the number of available spots in the ParkingLot. In the next few sections we will take a look at the attributes, access control mechanisms and inheritance features provided in the class.

Attributes

An object's instance variables are its attributes, the things that distinguish it from other objects of the same class. It is important to be able to write and read these attributes; doing so requires methods called attribute accessors. Ruby defines three accessor methods attr_reader, attr_writer and attr_accessor<ref>Accessors</ref>.

Attr_reader: Creates instance variables and corresponding methods that return the value of each instance variable.

Attr_writer: Creates an accessor method to allow assignment to the attribute

Attr_accessor: Creates a reader and a writer method for each symbol passed as an argument.

These methods provide access to the underlying instance variables of the name (with a leading @sign). The below example illustrates the use of these methods.

 class BookInStock
   attr_reader :title       #getter method
   attr_writer :price       #setter method
   attr_accessor : isbn     #getter and setter methods 
 end 

These methods will have the same effect as shown in the below table.

 Shortcut	         Effect
 attr_reader :title	 def title; @title; end
 attr_writer :price	 def price=(price); @price=price; end
 attr_accessor :isbn	 attr_reader :isbn; attr_writer :isbn

Inheritance

Inheritance is one of the pillars of object-oriented programming. Inheritance allows you to create a class that is a refinement or specialization of another class. This class is called a subclass of the original, and the original is a superclass of the subclass.

For example here Ale is-a subclass of Beer and Beer is-a superclass of Ale. Inheritance allows a subclass to have all the methods defined in the superclass and hence promotes code reuse. In addition subclass can override the methods which are much more specialized than superclass. Ale class now gets the intake method defined in the Beer and also it has another method defined taste which varies from one subclass to another. Also it’s important to note that all classes in Ruby inherit from the Object class and hence all the methods declared in the Object class are now implicitly part of any class we declare.

 class Beer  
   def intake  
     puts "blurp blurp"  
   end  
 end  

 class Ale < Beer  
   def taste 
     puts "Sweet, tasty tasty"  
   end  
 end  

 beverage = Ale.new  
 beverage.taste  #outputs blurp, blurp
 beverage.intake #outputs Sweet, tasty tasty

Access control

Ruby allows having control over the methods defined in the class. There are three levels of protection provided by Ruby,

1. Public methods can be called by any class. No access control is enforced. In Ruby, methods are public by default ( except initialize, which is private). 2. Protected methods can be used by the class which defines them and the subclass which inherit these classes. 3. Private methods can be called only in the context of the current object. You can’t invoke other objects private methods.

The below example shows some details about access control. One can just choose any relevant access specifier before each method is defined as shown here.

 class AccessControlExample
  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

Abstract methods

Ruby doesn’t support the concept of abstract methods. This is because Ruby is dynamically typed and you can add methods on the fly. If you want to simulate the notion of abstract method, you can do the following,

 class Animal
   def talk	
      raise “NotImplementedError.new("Method not implemented") #raise indicates exception
   end 
 end
 class Dinosaur < Animal
   def talk
      puts “Roar! Roar”
   end
 end

Here Dinosaur class implements talk method. If you don’t implement the talk method in Dinosaur, an attempt to instantiate the class throws an error. However as mentioned earlier Ruby programmers use abstract methods very rarely.

Duck typing (Unbounded Polymorphism)

In Ruby, the class is never the type. Instead, the type of an object is defined more by what that object can do which is determined by the methods and properties rather than inheritance from particular class. In Ruby, we call this duck typing<ref>Duck Typing</ref>. If an object walks like a duck and talks like a duck, then the interpreter is happy to treat it as if it were a duck.

Lets look at the example below.

There are 4 classes defined which are not related in any way. The classes Frog, Dinosaur, Table have a talk method defined while the class Bird has chirp method defined. Since the Table class has a talk method we can say it has animal even though by reality it does not belong which illustrates Duck typingin Ruby. Since Bird class does not implement the talk method it is not classified as of type animal.


 class Frog
   def talk
        puts "ribbit, ribbit"
   end
 end
 class Dinosaur
   def talk
        puts "Roar"
   end
 end
 class Table
   def talk
        puts "chss, chss"
   end
 end
 class Bird
   def chirp
     puts “kuckoo”
   end
 end
 class Test
   def test_animal(an)
        an.talk
   end
 end
 t = Test.new
 t.test_animal(Frog.new) # Frog is an animal
 t.test_animal(Dinosaur.new) # Dinosaur is an animal
 t.test_animal(Table.new) # Table is an animal!!
 t.test_animal(Bird.new) # Bird is not an animal

The downside of duck typing is that, the absence of the static type checking may increase the execution time. This is also dynamic typing because type information is determined at run-time. Other well-known languages which heavily use dynamic typing are Python, PHP, Perl, Objective-C, etc.

Conclusion

We started by explaining what closures are and in Ruby how this can be achieved using block and procs. Then we explained classes which are basic elements in object-oriented programming and duck typing which is very popular in Ruby and used most often. Closures allow a programmer to write shorter code, while classes in Ruby provide data encapsulation, code reuse via inheritance and polymorphism. Ultimately we believe that the developer has to weigh the options available according to the requirements of the application and use the Ruby features accordingly.

References

<references/>

External links

Further reading

  • Programming Ruby, The Pragmatic Programmer's Guide by Dave Thomas
  • Design Java by Dale Skrien