CSC/ECE 517 Fall 2012/ch2b 1w59 nm
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.
Scope
The scope of this article is limited to the scope covered in video lecture. The lecture starts with an introduction of duck typing where each object takes care of the methods it can handle and modules which facilitate inheritance in Ruby classes. The video lecture illustrates how different classes respond to instance methods by including modules.
Hence for the purpose of this article it is assumed that the reader has already read the relevant material and watched the video lecture.
Duck Typing[2]
What is Duck Typing
“If a object walks like a duck, quacks like a duck then treat it as a duck”
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
- [1] http://www.youtube.com/watch?v=Q1h-W0sWVoM
- [2] http://en.wikipedia.org/wiki/Duck_typing
- [3] http://www.codecapers.com/post/Duck-Typing.aspx
- [4] http://en.wikipedia.org/wiki/Programming_language#Type_system
- [5] http://bigdingus.com/2007/12/08/just-what-is-this-javascript-object-you-handed-me/
- [6] http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
- [7] http://juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/
Additional Reading
- Sam Ruby, Dave Thomas, David Heinemeier Hansson.agile web development with rails (fourth edition)
- Perils of Duck Typing
- What is Duck Typing : Stack Overflow
- What actually do you mean by 'Type'