CSC/ECE 517 Spring 2013/ch1b 1n jp

From Expertiza_Wiki
Revision as of 06:46, 25 February 2013 by Ragarwa2 (talk | contribs) (→‎Advantages of method_missing())
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Ruby's method_missing(): Advantages/Disadvantages and Alternatives

Introduction

One of the distinguishing features of the Ruby programming language is it's ability to easily extend classes in a dynamic fashion to perform a variety of different methods depending upon the needs of the current application. For example, should the developer want to extend a core language class, take String for example, they could easily re-open that class and add new methods dynamically to this existing class, rather than creating an entirely new class.

Even more powerful, Ruby allows for a construct called method_missing(). By overriding this method in your class, method_missing() will attempt to handle any method calls not currently defined by the given class (or class hierarchy). Typically, some sort of regular expression is used to determine if the method call can be redirected to an existing method based on it's naming convention (for example, a method call in a non-existent singular form could be redirected to an existing pluralized form). If the method_missing() definition cannot determine where to redirect the method call, the default is simply to call the parent class via super().

By having the ability to allow a class to dynamically extend itself even if a method definition never truly existed, Ruby allows for a far greater degree of flexibility than does a statically typed language such as Java, where much greater effort is involved in handing method calls that do not currently exist. With method_missing() defined, almost any sort of method call can be handled in the desired manner.

This article will explore the method_missing() construct in greater detail, giving both advantages and disadvantages of having this feature in the Ruby language. We will also examine how other programming languages attempt to handle the same problem.

Advantages of method_missing()

There are several advantages of implementing method_missing() in Ruby programs. The example below of find_by_* methods illustrates these advantages. <ref>http://www.trottercashion.com/2011/02/08/rubys-define_method-method_missing-and-instance_eval.html</ref>:

   Post.find_by_title("Awesomeness!")
   User.find_by_email("bob@example.com")
   User.find_by_email_and_login("bob@example.com", "bob")

These are methods called by the user where "title", "email", and "email_and_login" are made up by the user at runtime. Also, the user can specify one or two parameters based on the method signature.

   class ActiveRecord::Base
     def method_missing(meth, *args, &block)
       if meth.to_s =~ /^find_by_(.+)$/
         run_find_by_method($1, *args, &block)
       else
         super # You *must* call super if you don't handle the
               # method, otherwise you'll mess up Ruby's method
               # lookup.
       end
     end
     def run_find_by_method(attrs, *args, &block)
       # Make an array of attribute names
       attrs = attrs.split('_and_')
       # #transpose will zip the two arrays together like so:
       #   [[:a, :b, :c], [1, 2, 3]].transpose
       #   # => [[:a, 1], [:b, 2], [:c, 3]]
       attrs_with_args = [attrs, args].transpose
       # Hash[] will take the passed associative array and turn it
       # into a hash like so:
       #   Hash[[[:a, 2], [:b, 4]]] # => { :a => 2, :b => 4 }
       conditions = Hash[attrs_with_args]
       # #where and #all are new AREL goodness that will find all
       # records matching our conditions
       where(conditions).all
     end
   end

This is the ActiveRecord class that implements method_missing() to handle the find_by invocations that the user specifies.


In the above example, method_missing() is overridden to handle any method that has the prefix "find_by". Within the ActiveRecord class (a class that is used to access database data in an application. ActiveRecord is used for creating, restoring, updating or deleting records in a database. <ref>http://stackoverflow.com/questions/715490/method-missing-in-programming-ruby-over-my-head</ref><ref>http://en.wikipedia.org/wiki/Active_record_pattern</ref>), method_missing() is defined to allow any "find_by_*" methods to become class methods to find records by column attributes. When a method with "find_by" is called, the program invokes method_missing() because the method is not directly defined in the program. In the method_missing() definition here, the method run_find_by_methods() is called to specifically look for the all records under the specified column for the particular parameter indicated in the "find_by_*" method invocation. So, in the above example, Post.find_by_title("Awesomeness!") first calls method_missing() because it is not explicitly defined anywhere in the program. Method_missing() then takes the "title" part of the method name and the parameter "Awesomeness!" and calls run_find_by_method(). In run_find_by_method(), the column "title" in the database is searched for the parameter "Awesomeness!", and then all records with the title attribute "Awesomeness!" are returned.

Here there are many advantages that can be noted:
1. method_missing() allows for dynamically invoked methods. This means that a method on an object can be called without it being specifically defined under the called method name, in a class. Essentially is defined at runtime when it is called. When this method is called by the user, if its name is accounted for in method_missing(), it will have a generic definition in the class. In the example above, we see that any dynamic invocation of a "find_by" method is handled by the run_find_by_method(). By allowing for dynamic execution of a method, method_missing() contributes to the metaprogramming characteristics of Ruby <ref>http://wiki.expertiza.ncsu.edu/index.php/CSC/ECE_517_Fall_2007/wiki1b_2_22</ref>

2. method_missing() allows for the reduction of code in a program. In this example, the amount of code to write each possible method invocation for a "find_by" method is reduced to two methods handling any call of a "find_by" method thus reducing unecessary lines of code. This makes the code less complicated,easier to read, and avoids messiness in the code.

3. Another advantage is that method_missing() reduces redundancy (it basically follows the DRY or Don't Repeat Yourself principle). The programmer does not have to define several "find_by" methods that all have the same method signature, functionality, and return-type and only differ by column name. Rather, the programmer can define a generic method that is mutable to what the user specifies in the method invocation. In this case, the generic method is "find_by_*" that is based on the columns in the database. A user can then choose whichever attribute they want to find records on. In the below example, the redundancy of print statements needed for testing a program is reduced through method_missing():

class SimpleCallLogger
 def initialize(o)
   @obj = o
 end
 def method_missing(methodname, *args)
   puts "called: #{methodname}(#{args})"
   a = @obj.send(methodname, *args)
   puts "\t-> returned: #{a}"
   return a
 end
 end

"The above implementation of method_missing can be used to debug some code without littering it with print statements. This object "intercepts" all method calls made to it, prints out a message and forwards on the method call to an internal object using the 'send' method, without knowing anything about the object passed to it."<ref>http://wiki.expertiza.ncsu.edu/index.php/CSC/ECE_517_Fall_2007/wiki1b_2_22</ref> Essentially, through method_missing(), a trace of the methods that call the object and what those methods return can be printed out.

Disadvantages of method_missing()

A major disadvantage to method_missing() is that it can be slow. Since it is the last method that is called after the entire Ruby program is checked for the method that is "missing" and is then dynamically invoked, this causes a bit of overhead in the program because of the time to search the program. When method_missing is invoked, extra method calls occur. <ref>http://www.thirdbit.net/articles/2007/08/01/10-things-you-should-know-about-method_missing/</ref><ref>http://www.jroller.com/dscataglini/entry/speeding_up_method_missing</ref> In the example below:

   def method_missing(meth, *args, &block)
      if meth.to_s =~ /^find_by_(.+)$/
        run_find_by_method($1, *args, &block)
      else
        super # You *must* call super if you don't handle the
              # method, otherwise you'll mess up Ruby's method
              # lookup.
      end
    end

It can be noted that first method_missing() is called and then within the method, run_find_by_method() is called. This is more than if the method the user calls were pre-defined and one method call occurred. Both multiple method calls and searching cause low performance in the program. In general, this limits the use for method_missing to situations where there is a need for multiple methods with the same signature, functionality, and return type.

Alternatives to method_missing() in other programming languages

Smalltalk: doesNotUnderstand

In many ways the inspiration for Ruby, Smalltalk is often considered one of the original truly Object-Oriented languages. Smalltalk, much like Ruby with method_missing(), sends a message called #doesNotUnderstand<ref>http://c2.com/cgi/wiki?DoesNotUnderstand</ref> to handle methods that are unknown.

Essentially, when an object receives a method that it doesn't recognize, the Smalltalk runtime engine is then responsible for turning this message into an object with all necessary parameters and returning it to the sender as an exception. The sender is, at that point, responsible for handling the message and somehow dealing with the exception. Even though Smalltalk does consider this an exception, this construct is used quite frequently to form proxy objects and can handle a wide variety of different methods and arguments.

Consider the following code: <ref>http://www.iam.unibe.ch/~akuhn/blog/category/smalltalk/</ref>

   OrderedCollection subclass: #Group.
   Group >> eachRespondsTo: aSelector
       self allSatisfy: [ :each | each respondsTo: aSelector ]
   Group >> doesNotUnderstand: aMessage
       (self eachRespondsTo: aMessage selector)
         ifTrue: [ self collect: [ :each | aMessage sendTo: each ] ]
         ifFalse: [ super doesNotUnderstand: aMessage ]
   Group >> respondsTo: aSelector
     ^ (super respondsTo: aSelector) or: [ self eachRespondsTo: aSelector ]

In this code snippet, an OrderedCollection object is created. Should this collection class not understand any of the methods being sent to it, the doesUnderstand code will attempt to handle the message itself. Without overriding this method, the default is for Smalltalk to open a debugger to step through the exception (defining it overrides this behavior).

Java: DynamicProxy

In Java, the ability to handle undefined methods is a bit more cumbersome, but it can still be done successfully through the use of dynamic proxy classes. By creating this dynamic proxy, all calls will essentially be intercepted prior to invocation. An attempt will be made to invoke the object in question and, if unable to do so, an interface will need to be defined to handle the missing method scenarios. To give an example<ref>http://www.jroller.com/ie/entry/methodmissing_in_java</ref>:

 public class Dynamic implements java.lang.reflect.InvocationHandler  {
       Object delegate;
       public Dynamic(Object delegate){
               this.delegate=delegate;
       }        
       public Object invoke(Object proxy, Method method, Object[] args) 
                          throws Throwable {
               Object result = null;
               try {                  
                   result = method.invoke(delegate, args);
               } catch (InvocationTargetException e) {
                       result = 
                 ((MethodMissing)delegate).methodMissing(method,args);
               } catch (Exception e) {
                   result = 
            ((MethodMissing)delegate).methodMissing(method,args);
               } finally {
                   
               }
               if(result==delegate)
                       result = proxy;
               
               return result;
       }
       public static T newInstance(Object o,Class... interfaces) {
               return (T)java.lang.reflect.Proxy.newProxyInstance(
                   interfaces[0].getClassLoader(),
                   interfaces,
                   new Dynamic(o));
       }        
 }

Here, we define our Proxy class. Note, both an Object and Method are passed in as parameters. When attempting to invoke the method of the given Object, an exception is caught and delegated to a MethodMissing interface, which will also need to be defined.

 public interface MethodMissing {
       public T methodMissing(Method method,E... args);        
 }

Now, we define an interface whose methods will need to be implemented in order to handle the missing objects.

 public class Ruby implements MethodMissing{
       public Object methodMissing(Method method, Object... args) {
               System.out.println(method.getName());
               return this;
       }
       public static void main(String[] args){
               IDynamic dynamic = 
                  Dynamic.newInstance(new Ruby(), IDynamic.class);               
               dynamic.meow().woof();
               dynamic.woof();
               
       }               
 }
 public interface IDynamic extends MethodMissing{
       public IDynamic woof();
       public IDynamic meow();
 }

Lastly, we create a class that attempts to utilize the MethodMissing construct. As you can see, the Ruby class creates an implementation of the methodMissing() method, choosing to simply reflect the name of the method itself. When the IDynamic class is created, it attempts to run several methods which are never actually defined. Nevertheless, all of these method calls are actually intercepted by our proxy class and called as if there were no issue.

The output of running this class would be:

 meow
 woof
 woof

Compared to Ruby, of course, or even Smalltalk, this is a very difficult way to handle missing methods. Instead of having something built into the class itself, additional interfaces and wrappers are required to handle these scenarios, creating far more bulky and complex code. Unfortunately, this is probably the price for having a statically typed language.

Python: __getattr__

In Python, much like Ruby, there is also a built-in class mechanism for handling non-existent methods. Built into a class is __getattr__, which can be used to provide a default for all Python attributes that have not otherwise been defined. In the following example: <ref>http://farmdev.com/src/secrets/magicmethod/index.html#introducing-getattr</ref>

 class Test(object):
   def __init__(self):
       self.a = 'a'
       self.b = 'b'
   def __getattr__(self, name):
       return 123456
 t = Test()
 print 'object variables: %r' % t.__dict__.keys()
 print t.a
 print t.b
 print t.c
 print getattr(t, 'd')
 print hasattr(t, 'x')

Expected output:

 object variables: ['a', 'b']
 a
 b
 123456
 123456
 True

In the above code, the Test class has variable definitions for 'a' and 'b', but no others. When the print method tries to send in a 'c' or 'd' method call, __getattr__ catches this call and prints the generic '123456' value. Even the hasattr method recognizes non-existent attributes as being valid without there even being an actual method call.

If an attribute is found through a normal mechanism, this function is never actually called. In fact, one difference with Ruby is that Python executes this method in a static fashion, only checking on one run if attributes have been defined somewhere for a relevant class. This code may in fact not perform properly if classes are modified dynamically at runtime.

Perl: AUTOLOAD

Lastly, the Perl programming language provides the AUTOLOAD feature for handling this situation. Basically, if a class or any parent classes define an AUTOLOAD method, this method is called in the case where a direct method isn't found. <ref>http://perldoc.perl.org/perlobj.html#AUTOLOAD</ref> Anything that the AUTOLOAD method returns will be the last value returned to the caller (the caller never even needs to know the AUTOLOAD code exists).

The following is a code snippet that can be used to auto-create methods on the fly in Perl using AUTOLOAD: <ref>http://www.perlmonks.org/?node_id=8227</ref>

 #!/usr/bin/perl -w
 package myClass;
 use strict;
 use vars qw{$AUTOLOAD $Debug};
 $Debug = 1;
 sub new {
   return bless {
     thing	=> 1,
     thang	=> 2,
     thong	=> 3,
   }, shift;
 }
 sub AUTOLOAD {
   my $self = shift or return undef;
     # Get the called method name and trim off the fully-qualified part
     ( my $method = $AUTOLOAD ) =~ s{.*::}{};
       # If the data member being accessed exists, build an accessor for it
       if ( exists $self->{$method} ) {
         ### Create a closure that will become the new accessor method
           my $accessor = sub {
           my $closureSelf = shift;
           if ( @_ ) {
             return $closureSelf->{$method} = shift;
           }
             return $closureSelf->{$method}
           }
           # Assign the closure to the symbol table at the place where the real
           # method should be. We need to turn off strict refs, as we'll be mucking
           # with the symbol table.
           SYMBOL_TABLE_HACQUERY: {
             no strict qw{refs};
             *$AUTOLOAD = $accessor;
           }
           # Turn the call back into a method call by sticking the self-reference
           # back onto the arglist
           unshift @_, $self;
           # Jump to the newly-created method with magic goto
           goto &$AUTOLOAD;
       }
       ### Handle other autoloaded methods or errors
 }
 DESTROY {}
 ### Test program
 package main;
 my $a = new myClass;
 print $a->thing, $a->thang, $a->thong, "\n";

In the above example, new accessor methods are being created dynamically where closures are being added to the symbol table and then the relevant method is loaded for the caller.

References

<references />