CSC/ECE 517 Fall 2012/ch2b 1w59 nm

From Expertiza_Wiki
Revision as of 21:31, 18 November 2012 by Nmsarda (talk | contribs)
Jump to navigation Jump to search

SaaS - 3.7. Mixins and Duck Typing

This wiki is aimed at acting as a complementary read to the SaaS - 3.7. Mixins and Duck Typing lecture[1]. This lecture describes three of the most important fundamentals of the Ruby language: Duck-Typing, Modules and Mixins. 

Introduction

  • Duck-typing is one of the main features of many object-oriented languages. Duck-Typing describes how a language like Ruby, treats its objects as compared to a language like JAVA. We describe the beautiful features of Duck-typing and also its negative side-effects.
  • Modules define a namespace. It provides a mechanism to club several things together which naturally do-not form a common class. With modules, programmer does not have to worry of possible name-clashes of method name's with other methods written by different programmers. Modules handles this by making use of namespace.
  • Mixins extend from modules. modules are not classes and hence we cannot instantiate them. However, when modules are 'include'ed in a class, all its methods are available to that class. We say modules are 'mixed in' to the class or in other words 'mixins'. Modules which are mixed in behave as superclass and provide multiple inheritance.

Duck Typing[2]

What is Duck Typing

                    “If a object walks like a duck, quacks like a duck then treat it as a duck”
A cat or a duck or a cat walking like a duck?[3]






This is the ‘mantra’ of Ruby language and the most pleasing aspect of the language. What it means is that, whether an object responds to a method?, is only important and not the type of the object. If a dragon responds to methods like swim and quacking, ruby is happy to treat dragon as a duck without having a concern of whether it actually is a duck or not. This gives powerful tools for a ruby programmer.

This is possible in Ruby because we don’t declare the 'Type[4] of variables or methods like in JAVA; in Ruby, everything is an object and each object can be individually modified, irrespective of its implementation in the class to which it belongs.





Consider the following example taken from the SAAS lecture:

my_list.sort

[5, 4, 3].sort
["dog", "cat", "rat"].sort
[:a, :b, :c].sort

Here, the same function, sort, is responding to an array of integers, strings and literals. This is classic Ruby duck-typing at work. How sort works is that it compares the elements which are needed to be sorted. So as long as the elements can be compared, sort function works. In the above example, the array of integers, strings and literals implement the comparison operator and hence the same sort function works for all of them.

Why in Ruby and not in JAVA?

The reason for duck-typing to be easily possible and implemented in language like Ruby and not in language like JAVA is that these 2 languages treat ‘type’ differently. In JAVA or C++, type means class. When someone asks the type of “abc”, we say it is String. So, such language allows String objects to refer to only other String objects. String a; a will never be allowed to refer to anything other than a string, like a=5.

While in Ruby, type refers to how the object reacts to a method or in other words does the object have a particular capability. Type refers to this capability. While a class to which the object belongs provides the initial capabilities, from that point on, the object is free to extend its capabilities independently of its pre-defined class. So,

a= ”abc”
a= 5 are two perfectly acceptable statements in Ruby.

This is duck-typing. A classic example[5] of how a scripting language, JavaScript, not implementing duck-typing can lead to some bad code.

Duck-Typing in action

class American
  def football
    puts "We play it with hands"
  end

end

class European
  def football
    puts "We play it with legs"
  end
end

class Martian
end

footballManiac = [American.new,European.new]

footballManiac.each {|game| game.football}

Output of this program is We play it with hands(next line) We play it with legs

This is because of duck-typing. footballManiac first acted as an American class object and since that class responded to the football method, it executed it. Then it acted as an European class object and again that class responded to the method, it executed it. In static-typed languages, we would have to type cast each object and even then we might get compile time or run time error. Nothing of that sorts in Ruby. Now let us also create a Martian class object.

 
footballManiac = [American.new,European.new,Martian.new]

footballManiac.each {|game| game.football}

On executing it, we get the following error

undefined method `football' for #<Martian:0x3425358> (NoMethodError)

This is obvious because martians do not play football. But the point is, Ruby still checked the martian class and when it found that the object does not respond to the method, it complained.

The other side to Duck-Typing

As with everything in life, there is always 2 perspectives about its usefulness. Following are some of the criticism against duck-typing :

Lack of compiler security

There is no help from compiler for a programmer error like assigning a string to an integer and so on. Only when you run the code, the output might be funny and that’s when you realize a probable mistake in assignment. In a static-type language, there is always a compiler check and hence the problem does not arise. Obviously this problem can be alleviated by proper and exhaustive testing. But as we all know, no one can guarantee 100% code tests.

Contract between caller and the callee

In a static type language, the caller knows the exact ‘type’ of parameters required by the callee and also the return-type from the callee. In dynamic type languages, no type is provided. So to understand what the callee expects, one has to look at the code and understand. This is difficult in most of the cases and mainly for third-party code. Again this problem can be solved by providing proper documentation of each API and the pre-requirements for execution. But who can guarantee that the documents correctly map the requirements?

Modules and Mixins

What are modules and mixins?

In Ruby language modules facilitate grouping of classes, methods and constants. A module has been defined as collection of classes and instance methods but they are not a class so they can’t be instantiated. However, modules can be mixed-in a class. The implementation of module is intertwined with that of a class thus providing the functionality of a module to given class. Modules provide a namespace which prevents name clashes and implementation of mixin facility.

The namespace facility is used when a class requires two methods from different classes having the same name. The class does not have the ability to recognize the methods independently which leads to name clashes. Consider the following example where Math module defines sin method and Action module defines sin method as well.

module Math
PI = 3.141592654
     def Math.sin(x)
     #.....
     end
end

module Action
BAD = -1
     def Action.sin(badness)
     #.....
     end
end

By implementing sin methods in different modules eliminates name clashes since the methods will now be referenced in a class which includes these modules. The modules can be included in a class using the require statement, this is shown as follows:

require Math
require Action

sinValue = Math.sin(Math::PI/2)
bad_action = Action.sin(Action::BAD)

Module methods[6] are invoked in the same manner as class methods by preceding its name with the module's name and a period, and by referencing a constant using the module name and two colons.

Types of mixins[7]

The following example demonstrates two types of mixins. Consider the following example of a module,

module Example

def example
     if @variable > 0
          return 1
     else
          return 0
     end 

end

The Example module makes use of @variable instance variable. The class which mixes this module has to define the @variable since the Example module makes use of @variable but does not define it. A module can make use of methods not defined within the module but the class in which it has been mixed in. Thus, this module exhibits dependency on the implementation of the class it is mixed with.

The following example demonstrates an independent module ,

module AddSub

#Class method
def add(var_one, var_two)
     SampleClass.new(var_one + var_two)
end

end

The Addition module defines a class method as well as an instance method. Class methods are also known as static methods. The add method in the AddSub module defines accepts two integers and returns an instance of SampleClass. The mixin SampleClass class is defined as follows:

# Base Integer class
Class Integer
  def value
    @variable
  end
end
 
# SampleClass extends Integer
class SampleClass < Integer
 
 
  # Add instance methods from Example
  include Stringify
 
  # Add class methods from AddSub
  extend AddSub
 
  # Add a constructor with one parameter
  def initialize(value)
    @variable = value
  end
end

The module methods have been mixed into the class using the include and extend methods respectively. The extend method will mix the methods as class/static methods.

#Call class method from AddSub module

object1 = SampleClass.add(2,3)
 puts object1.value	# -> 5

The include method will mix the methods defined in the module as instance methods.

#Call instance method from Example module

puts object1.example	# -> 1

However, care must be taken that including an instance method does not copy the module's instance methods into the class. A reference is generated from the class to the included module. This applies to all the classes which have included the module. If any change is made in the instance method it will be reflected in the behavior of all the classes that include the module. The extend method comes with an additional feature. An object instance can be enhanced at run time by mixing it in a module at run time.

module  ScaleValue
     def multiply
          “#{@variable}*100”
     end
end

An object instance can be extended with a module using the following commands:

#Adding module methods to this object instance only!

object1.extend ScaleValue
puts object1.multiply # -> 500

The extend method includes the module methods only on object1, the remaining objects will not this functionality.

puts object2.multiple # generate error

Mix-in is a contract

The Enumerable module provides various methods as a mixin to a given class. However, the module assumes that all the objects of a given class respond to each method. Apart from this, Enumerable provides methods which can be used to query an object whether it responds to a given method or not. Given the ability of an object to respond to each method which iterates over the collection of elements one can make use of following methods to find out the properties of an object. Some of the methods Enumerable provides are as follows:

• all?, any?, collect, find, include?, inject, map, and partition

In Java one can find the declaration “Array implements Enumerable”, no such declaration is found in Ruby. The reason being the methods in Enumerable module assume that the target objects are capable of responding to the methods invoked on them.

Comparable is another module which assumes that objects of a given class respond to < = > (spaceship) operator. Similar to the Enumerable module, Comparable provides various methods for comparing the objects and some of them are mentioned below:

• <, <=, >, >=, ==, and between?

Given a class which implements spaceship operator and each method, the objects can make use of sort method which requires implementation of spaceship operator so that the objects are capable to comparing with each other and the each method in order to retrieve the objects one at a time. By mixing modules within a class, an object can perform various actions such as map, reduce, and many more. The classes do not matter unless they respond to certain methods. Consider an example of constructing a data structure, by implementing each method the data structure can implement various methods such as sort, map, reduce etc in its own way. Thus, mixing modules facilitate behavior reuse which is an added advantage.

The video lecture presents an example of Account class which implements spaceship operator. The spaceship operator is used to compare the balance of two different objects.

class Account
   include Comparable
        def <=>(other)
             self.balance <=> other.balance
        end
end

On defining the spaceship operator the objects of Account class can define of all the other methods Comparable module provides. In this manner the Account class takes advantage of behavior reuse.


References

Additional Reading