CSC/ECE 517 Fall 2011/ch4 4b ds
Wiki textbook chapter on OOLS Lecture 5. Covers the basic of closures and blocks, currying and OOP in Ruby. Since the lecture 5 material in the CSC517 covers object-oriented concepts specific to Ruby, thus we have restricted to the same.
Introduction
In the following wiki, we will be covering the basics of closures and currying. 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).
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} 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)
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.
Procs And Lambdas<ref>proc and Lamda</ref>
There are three ways to create a Proc object in Ruby.
Proc.new
Proc.new takes in a block and returns a Proc object which will run the code in the block when call method is invoked.
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
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
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. 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.
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>[[8] http://rubylearning.com/satishtalim/duck_typing.html></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 >ref>Duck typing</ref> in 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