CSC/ECE 517 Fall 2011/ch4 4b ds: Difference between revisions
No edit summary |
|||
Line 149: | Line 149: | ||
===Access control=== | ===Access control=== | ||
Ruby allows having control over the methods defined in the class. | 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. | |||
The below example shows some details about access control. | |||
class AccessControlExample | class AccessControlExample | ||
def method1 # default is “public” | def method1 # default is “public” | ||
Line 179: | Line 177: | ||
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, | 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 | class Animal | ||
def talk | def talk | ||
raise “NotImplementedError.new("Method not implemented") | |||
end | end | ||
end | end | ||
Line 189: | Line 186: | ||
class Dinosaur < Animal | class Dinosaur < Animal | ||
def talk | def talk | ||
puts “Roar! Roar” | |||
end | end | ||
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 | 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)== | ==Duck typing (Unbounded Polymorphism)== |
Revision as of 18:11, 20 October 2011
Wiki textbook chapter on OOLS Lecture 5. Covers the basic of closures and blocks, currying and OOP in Ruby.
Introduction
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 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
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 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] curried_add = add.curry add_to_ten = curried_add[10] add_to_twenty = curried_add[20] puts add_to_ten[5] puts add_to_twenty[5]
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 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 def initialize(isbn, price) @isbn = isbn @price = Float(price) end def self.book 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 methodsprovide 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 attr_writer :price attr_accessor : isbn 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") 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. 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 typing 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) t.test_animal(Dinosaur.new) t.test_animal(Table.new) t.test_animal(Bird.new)
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 and duck typing in Ruby. Closures allow a programmer to write shorter code, while classes in Ruby provide data encapsulation and extension of code . Ultimately it is the developer who chooses when to use closures and classes based on the needs of his application.
References
[1] http://en.wikipedia.org/wiki/Closure_(computer_science)
[2] http://www.skorks.com/2010/05/closures-a-simple-explanation-using-ruby/
[3] http://www.skorks.com/2010/05/ruby-procs-and-lambdas-and-the-difference-between-them/