CSC/ECE 517 Fall 2007/wiki1 5 ap: Difference between revisions
(6 intermediate revisions by the same user not shown) | |||
Line 9: | Line 9: | ||
Consider that we have two objects: Circle and Square. | Consider that we have two objects: Circle and Square. | ||
These objects are clearly different from each other and clearly do not belong to the same class. Assume that they have some common methods like Area() and Perimeter(). | These objects are clearly different from each other and clearly do not belong to the same class. | ||
Assume that they have some common methods like Area() and Perimeter(). | |||
Although these methods have the same names, their implementations are completely different. Now assume that we have a method DisplayDetails() which calls the methods Area() and Perimeter(). | Although these methods have the same names, their implementations are completely different. Now | ||
assume that we have a method DisplayDetails() which calls the methods Area() and Perimeter(). | |||
If we explicitly specify a type for the argument of DisplayDetails(), we will need two separate functions to display the | If we explicitly specify a type for the argument of DisplayDetails(), we will need two separate | ||
functions to display the areas and perimeters of both the Circle and the Square. | |||
But by using duck typing, we take off the argument type of DisplayDetails(), thereby enabling us to pass objects of either type and have their methods invoked.<br> | But by using duck typing, we take off the argument type of DisplayDetails(), thereby enabling | ||
us to pass objects of either type and have their methods invoked. | |||
</pre> | |||
<br> | |||
<br> | <br> | ||
Let's see a small example now of how duck typing is implemented in Ruby. | Let's see a small example now of how duck typing is implemented in Ruby. | ||
Line 33: | Line 39: | ||
''' | ''' | ||
== Duck typing v/s Interfaces == | == Duck typing v/s Inheritance and Interfaces == | ||
''' | ''' | ||
=== Advantages of Duck typing over Interfaces:=== | === Advantages of Duck typing over Inheritance & Interfaces:=== | ||
Duck typing is one of the important features of dynamic languages in which an object's interface and attributes determine valid semantics, rather than having the language enforce rules regarding inheritance or require type casting. This allows an object to be interchangeable with any other object so long as they both implement sufficiently compatible interfaces, regardless of whether the objects have a related inheritance hierarchy. <br> | |||
Java uses inheritance as the mechanism for defining the type of an object. An object "is a" X if it implements X. While Ruby also supports inheritance, establishing "is a" relationships is not its main purpose. In Ruby an object is of a certain type if it behaves as that type. It must be noted, however, that while Ruby has classes, objects are not explicitly declared of to have a certain type. | |||
Conceptually, an object can be of N! types, where N is the number of methods exposed by the object. | |||
<br><br> | |||
Consider the following example, in which the salary of an employee is calculated depending on his post and the number of days he has worked in the month. We will contrast the approaches of duck typing (Ruby) and interface (Java) one at a time.<br> | Consider the following example, in which the salary of an employee is calculated depending on his post and the number of days he has worked in the month. We will contrast the approaches of duck typing (Ruby) and interface (Java) one at a time.<br> | ||
Line 131: | Line 143: | ||
effort on the part of the caller. | effort on the part of the caller. | ||
• | • Convenience [1]: Since the types of arguments of methods are not typed, it eliminates the need to | ||
write separate caller functions based on the type of arguments to be passed. | write separate caller functions based on the type of arguments to be passed. | ||
</pre> | </pre> | ||
Line 140: | Line 152: | ||
<pre> | <pre> | ||
• The first drawback of duck-typing is the lack of compiler safety net. If enough care is not taken while writing a piece of | • The first drawback of duck-typing is the lack of compiler safety net. If enough care is not taken | ||
while writing a piece of code, there is little guarantee that it will run successfully. The | |||
problem isn't with duck typing, but in testing the 'duckness' of an object. Duck typing takes | |||
away a lot of error checking performed by the compiler and replaces it with unit testing to be | |||
performed by the programmer. However, in most cases programmers prefer the compiler to find and | |||
help avoid as many errors as possible before running their own unit test cases, which can | |||
themselves be buggy and incomplete. | |||
• The second drawback is the lack of a precise contract between the calling & the called methods. The only way to find it out | • The second drawback is the lack of a precise contract between the calling & the called methods. | ||
The only way to find it out is to analyze the body of the called method, which can be impractical | |||
at times, especially when one has to deal with a third party API. The most common approach to | |||
mitigate the issues caused by this is to provide documentation. But even that is not really | |||
advisable, as the documentation can get too verbose and less direct (informative). Also, there | |||
is a serious likelihood that the documentation may not be in sync with the code, as the code | |||
evolves with time. | |||
• The third drawback is the difficulty in maintaining duck typed code. Although duck typing is beneficial while writing small | • The third drawback is the difficulty in maintaining duck typed code. Although duck typing is | ||
beneficial while writing small applications as it facilitates fast coding, speed is of little | |||
significance in large and complex applications if one ends up writing fragile code which is hard | |||
to maintain. Since a major bulk of the cost of such codes over their lifetime is spent on their | |||
maintenance, anything that compromises the maintainability of code is highly suspect. Static | |||
typing is more beneficial in such cases, as it catches many errors that may not be apparent at | |||
first, but might surface over the lifetime of the application. | |||
</pre> | </pre> | ||
Latest revision as of 21:30, 19 September 2007
What is duck typing?
"If it walks like a duck and quacks like a duck, it must be a duck " [3] is the philosophy of duck typing.
In terms of Ruby, duck typing refers to the tendency to be less concerned with the class of an object and more concerned with what methods can be called on it and what operations can be performed on it. Duck typing allows an object to be passed in to a method that expects a certain type even if it doesn't inherit from that type. All it has to do is support the methods and properties of the expected type in use by the method.
Consider that we have two objects: Circle and Square. These objects are clearly different from each other and clearly do not belong to the same class. Assume that they have some common methods like Area() and Perimeter(). Although these methods have the same names, their implementations are completely different. Now assume that we have a method DisplayDetails() which calls the methods Area() and Perimeter(). If we explicitly specify a type for the argument of DisplayDetails(), we will need two separate functions to display the areas and perimeters of both the Circle and the Square. But by using duck typing, we take off the argument type of DisplayDetails(), thereby enabling us to pass objects of either type and have their methods invoked.
Let's see a small example now of how duck typing is implemented in Ruby.
def get_successor(s) raise ArgumentError, 'No successor method!' unless s.respond_to? :succ return "Successor of #{s} is #{s.succ}" end puts get_successor('a') => Successor of a is b puts get_successor(4) => Successor of 4 is 5
In this example, we define a method 'get_successor' which returns the successor of the object passed to it. All the objects on which the 'succ' method is defined can be passed as an argument to the 'get_successor' method irrespective of their class.
Duck typing does not rely on inheritance and polymorphism. Every single object is accepted in every single method. The only thing that matters is the capability of that object to do whatever the method is trying to do. If the object is unable to do that, the interpreter happily raises an exception.
Duck typing v/s Inheritance and Interfaces
Advantages of Duck typing over Inheritance & Interfaces:
Duck typing is one of the important features of dynamic languages in which an object's interface and attributes determine valid semantics, rather than having the language enforce rules regarding inheritance or require type casting. This allows an object to be interchangeable with any other object so long as they both implement sufficiently compatible interfaces, regardless of whether the objects have a related inheritance hierarchy.
Java uses inheritance as the mechanism for defining the type of an object. An object "is a" X if it implements X. While Ruby also supports inheritance, establishing "is a" relationships is not its main purpose. In Ruby an object is of a certain type if it behaves as that type. It must be noted, however, that while Ruby has classes, objects are not explicitly declared of to have a certain type.
Conceptually, an object can be of N! types, where N is the number of methods exposed by the object.
Consider the following example, in which the salary of an employee is calculated depending on his post and the number of days he has worked in the month. We will contrast the approaches of duck typing (Ruby) and interface (Java) one at a time.
Java Code:
package duck_typing; interface getSalary { public void compute(int days); } class Manager implements getSalary { public void compute(int days) { System.out.print("This manager has worked " + days + " days this month, "); System.out.println("so his salary is $" + (double) days*150); } } class Clerk implements getSalary { public void compute(int days) { System.out.print("This clerk has worked " + days + " days this month, "); System.out.println("so his salary is $" + (double) days*55); } } public class Calculate { public static void main(String[] args) { getSalary[] emp = {new Manager(), new Clerk()}; for (int i = 0; i < emp.length; i++) emp[i].compute(20); } }
Output:
This manager has worked 20 days this month, so his salary is $3000
This clerk has worked 20 days this month, so his salary is $1100
In the above code, we have created an interface 'getSalary' which has the abstract method 'compute'. The two classes Manager and Clerk need to implement this interface and define the method 'compute', to calculate the salaries of its objects. As can be seen from the code, we can compute the salaries of both the manager as well as the clerk, even though they belong to different classes.
Now let's see how the same can be implemented in Ruby using duck typing.
Ruby code:
class Manager def compute(days) print "This manager has worked #{days} days this month, " puts "so his salary is $#{days * 150}" end end class Clerk def compute(days) print "This clerk has worked #{days} days this month, " puts "so his salary is $#{days * 55}" end end emp = [Manager.new(), Clerk.new()] emp.each {|em| puts em.compute(20) }
Output:
This manager has worked 20 days this month, so his salary is $3000
This clerk has worked 20 days this month, so his salary is $1100
It can clearly be seen that implementing the above code is much simpler and less verbose as compared to java. In Ruby, we just created two classes, Manager and Clerk which implemented the method 'compute'. We were able to invoke the 'compute' method with the objects of different classes.
Thus it can be said that duck typing is a big time saver when we write codes, as the verification that the object does respond to different methods is not made when the object is passed as an argument to the method, but is made on the invocation of the said methods. And this is the reason why arguments to methods are usually not typed all.
We can sum up the advantages of duck typing as follows: • Granularity [1]: Duck-typed functions are the least dependent on the types of the arguments passed.On the contrary, interfaces may carry extra requirements, like some methods that are not required for the function in question. Although we can split the interfaces by keeping only a certain number of required functions in them, the resulting increase in the number of interfaces can cause confusion. • Adaptability [1]: Thanks to the independence of duck-typed functions from the type of arguments passed, a duck-typed function can be adapted to contexts that it was not built for initially, with a minimal effort on the part of the caller. • Convenience [1]: Since the types of arguments of methods are not typed, it eliminates the need to write separate caller functions based on the type of arguments to be passed.
Disadvantages of Duck typing over interfaces:
• The first drawback of duck-typing is the lack of compiler safety net. If enough care is not taken while writing a piece of code, there is little guarantee that it will run successfully. The problem isn't with duck typing, but in testing the 'duckness' of an object. Duck typing takes away a lot of error checking performed by the compiler and replaces it with unit testing to be performed by the programmer. However, in most cases programmers prefer the compiler to find and help avoid as many errors as possible before running their own unit test cases, which can themselves be buggy and incomplete. • The second drawback is the lack of a precise contract between the calling & the called methods. The only way to find it out is to analyze the body of the called method, which can be impractical at times, especially when one has to deal with a third party API. The most common approach to mitigate the issues caused by this is to provide documentation. But even that is not really advisable, as the documentation can get too verbose and less direct (informative). Also, there is a serious likelihood that the documentation may not be in sync with the code, as the code evolves with time. • The third drawback is the difficulty in maintaining duck typed code. Although duck typing is beneficial while writing small applications as it facilitates fast coding, speed is of little significance in large and complex applications if one ends up writing fragile code which is hard to maintain. Since a major bulk of the cost of such codes over their lifetime is spent on their maintenance, anything that compromises the maintainability of code is highly suspect. Static typing is more beneficial in such cases, as it catches many errors that may not be apparent at first, but might surface over the lifetime of the application.
See also
[1] http://cdsmith.twu.net/types.html
[2] http://coderoshi.blogspot.com/2007/09/5-reasons-static-typing-sucks.html
[3] http://www.oopcenter.com/article/ruby-on-rails/ruby-classes-and-objects.html
References
[1] http://www.alittlemadness.com/?p=28
[2] http://www.akropolix.net/rik0/blogs/category/programming/ruby/
[3] http://boo.codehaus.org/Duck+Typing
[4] http://def-end.blogspot.com/2006/11/lame-duck-typing.html
[5] http://blog.newatlanta.com/index.cfm?mode=entry&entry=116C5A99-A4A6-12B9-3D6013713A815D9C