CSC/ECE 517 Fall 2011/ch4 4a ga

From Expertiza_Wiki
Jump to navigation Jump to search

Introduction

This article discusses the extend concept as illustrated by Ruby's extend method. We provide a definition of extend, compare it to the include method, and go over the differences between extending a class and extending an object. We then consider two practical examples of Ruby's extend method.

From there, we go on to discuss how other languages provide functionality similar to that of Ruby's extend method. We also compare these implementations to the Ruby implementation as to whether they are more or less powerful than Ruby's. Finally, we also consider extend-like functionality in prototype-based languages such as Self, which don't even provide classes.

Extend in Ruby

What is Extend?

Definition

The Ruby doc definition of the extend method is that it, “Adds to obj the instance methods from each module given as a parameter.”<ref>http://www.ruby-doc.org/core-1.8.7/Object.html#M000007</ref> An example shows how this plays out:

module Mod
  def hello
    "Hello from Mod.\n"
  end
end

class Klass
  def hello
    "Hello from Klass.\n"
  end
end

k = Klass.new
k.hello         #=> "Hello from Klass.\n"
k.extend(Mod)   #=> #<Klass:0x401b3bc8>
k.hello         #=> "Hello from Mod.\n"

From this example we can see several interesting things about extend. 1. It can be applied dynamically to an object after that object has been created. 2. Methods introduced by extend will override any existing methods in that object.

Extend vs. Include

Extend is also often defined by comparing it to the include method:

module Foo
  def foo
    puts 'heyyyyoooo!'
  end
end

class Bar
  include Foo
end

Bar.new.foo # heyyyyoooo!
Bar.foo # NoMethodError: undefined method ‘foo’ for Bar:Class

class Baz
  extend Foo
end

Baz.foo # heyyyyoooo!
Baz.new.foo # NoMethodError: undefined method ‘foo’ for #<Baz:0x1e708>

In the previous example, extend was applied dynamically to an object after it had been created. In this example, both extend and include are applied inside the class.

This example also shows an important difference between include and exclude, while also making it clearer what exactly extend does.

When include is used on a class, the methods from the module are applied to instances of a class rather than to the class itself. The extend method, on the other hand, applies methods from the module to the class itself, rather than to instances of the class.<ref>http://railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/</ref>

Extending classes vs. Extending objects

We’ve seen examples of extend being applied both to classes and to objects (instances of classes). The syntax, however, was significantly different between the two examples. Let’s look at another example where the same syntax is used:

class C
end

module M
   def talk
     puts "hi"
   end
end

C.talk # NoMethodError: undefined method `talk' for C:Class
C.extend M
C.talk #hi

c = C.new
c.talk # NoMethodError: undefined method `talk' for #<C:0x297c918>
c.extend M
c.talk    # hi

This syntax is a variation of the syntax used the in the first example; the parentheses are optional. Notice that extend can be applied using this syntax to both classes and class instances. Note also that extending a class doesn’t extend any instances of the class. The instance was only extended after we explicated extended the instance in addition to the class. If we created a new instance of the class, we’d also have to extend that instance before we’d be able to use any methods from the module.<ref>http://www.ruby-forum.com/topic/164443</ref>

Practical Examples

It’s easy to find trivial examples of using Ruby’s extend method, especially when applying it to objects:

# Format a numeric value as a currency
module CurrencyFormatter
  def format
    "$#{@value}"
  end
end

# Add the module methods to
# this object instance, only!
bigint2.extend CurrencyFormatter
puts bigint2.format   # --> '$2'
puts bigint1.format   # will generate an error

While this is an interesting example, it seems inefficient to extend a single object in order to provide a particular format for its value. It would make much more sense to use include instead so that the method could be applied to all instances of the class for that object. Because of this, all of the practical examples that follow, and likely any practical examples one could find, will be cases where extend is used within a class rather than on an instance of a class.<ref>http://juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/</ref>

As we’ll see even in these examples, however, rather than extend being a way to accomplish something that couldn’t be accomplished easily any other way, extend is most often used as a convenience method for accomplishing a frequently needed function in an more manageable way.

Twitter Module

There is a twitter module that makes use of the extend method in an interesting way:

module Twitter
  class OAuth
    extend Forwardable
    def_delegators :access_token, :get, :post
    
    # etc

    def access_token
      @access_token ||= ::OAuth::AccessToken.new(consumer, @atoken, @asecret)
    end

    #etc
    
  end
end

Forwardable is used here as a convenient way to delegate responsibility. Rather than having to define get and post within the OAuth class, those calls are passed off to access_token, which has methods with those same names.<ref>https://github.com/jnunemaker/twitter/blob/9b880c4be0a43bf3ea6fd4f5ec1d6c120e6c2b11/lib/twitter/oauth.rb</ref>

While this is a fairly practical example of using extend, in some ways it is more of a convenience than a necessity. One could just as easily create wrappers for “get” and “post” that pass the calls off to access_token. It does, however, result in cleaner and more readable code, so that is a plus.<ref>http://railsmagazine.com/articles/4</ref>

Active::Concern

Consider this snippet from The RubyGem Devise:

lib/devise/controllers/helpers.rb
    module Helpers
      extend ActiveSupport::Concern
</pref><ref>http://stackoverflow.com/questions/6787876/why-do-people-use-extend-in-a-module-definition</ref>

Devise is actually one of many ruby gems and modules that extend Active::Concern. To understand what this does, it helps to take a few steps back and look at one of the most common 3rd-party gems used in Rails, ActiveRecord::Base. There’s a particular idiom that’s used for ActiveRecord::Base, which the following module illustrates:
<pre>
module TagLib
 
  module ClassMethods
    def find_by_tags()
      # ...
    end
  end
 
  module InstanceMethods
    def tags()
      # ...
    end
  end
 
  def self.included(base)
    base.send :include, InstanceMethods
    base.send :extend, ClassMethods
  end
 
end
 
class ActiveRecord::Base
  include TagLib
end

Without getting into too much detail about what this does, the main point of this is that there is a pattern of code that is being repeated again and again every time someone wants to make use of the functionality of this gem. It’s also not entirely clear what benefits are gained through this code or what it’s doing, unless you’re familiar with the idiom. This same functionality, however, can be obtained with ActiveSupport:Concern through something like the following:

module TagLib
  extend ActiveSupport::Concern
 
  module ClassMethods
    def find_by_tags()
      # ...
    end
  end
 
  module InstanceMethods
    def tags()
      # ...
    end
  end 
end
 
class ActiveRecord::Base
  include TagLib
end

Again this accomplishes exactly the same thing as before, but with much less code and all wrapped up in a nice package that people can take a look at and dive into if they want additional details about how everything works.<ref>http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active-support-concern/</ref>

Singleton Pattern

Another common convenience that extend provides is the ability to convert all instance methods into class methods:

module Rake
  include Test::Unit::Assertions

  def run_tests # etc.
  end

  extend self
end

<ref> http://stackoverflow.com/questions/1733124/ruby-extend-self</ref>

Without extend, in order to make use of this module, one would have to include it in a class, create an instance of that class, and then call methods from that instance. With the extend, however, it’s perfectly legal to make calls like this:

Rake.run_test

In particular, using extend in this way is a more convenient way to create Singleton objects in Ruby. Observe how a Singleton object would traditionally be created:

require 'net/http'

# first you setup your singleton
class Cheat
  include Singleton
  
  def initialize
    @host = 'http://cheat.errtheblog.com/'
    @http = Net::HTTP.start(URI.parse(@host).host)
  end
  
  def sheet(name)
    @http.get("/s/#{name}").body
  end
end

# then you use it
Cheat.instance.sheet 'migrations'
Cheat.instance.sheet 'yahoo_ceo'

Including the Singleton module provides adds the “instance” method to the class Cheat, which allows it to be used as a singleton rather than having to create instances of Cheat. Using extend, however, we can execute the singleton pattern in a much tighter fashion.

require 'net/http'

# here's how we roll
module Cheat
  extend self

  def host
    @host ||= 'http://cheat.errtheblog.com/'
  end

  def http
    @http ||= Net::HTTP.start(URI.parse(host).host)
  end

  def sheet(name)
    http.get("/s/#{name}").body
  end
end

# then you use it
Cheat.sheet 'migrations'
Cheat.sheet 'singletons'

Notice how the awkward “instance” is no longer necessary, nor even is the Singleton module itself. Instead, method calls can be made directly to the Cheat module in a very readable fashion. As a side benefit, it’s still possible to include the module in a class if necessary and create instances just as you would normally. In typical Ruby fashion, you aren’t restricted from creating instances, as you might be if you implemented the singleton pattern in a language like Java, but the intention to create a Singleton is very clear and easy to use.<ref>http://ozmm.org/posts/singin_singletons.html</ref>

Extend in other languages

Extend in Java

Java, like other object-orientated languages, supports class inheritance. Inheritance allows one class to "inherit" the properties of another class. Inheritance offers a radically different outlook on the re-use of code. Extend is a functionality that provides a way to inherit the fields and methods of a class by other class. This is done by extending the the class or implementing interfaces. This concept of extending the functionality is called Inheritance. In Java, when we wish to extend the usefulness of a class, we can create a new class that inherits the attributes and methods of another. We don't need a copy of the original source code to extend the usefulness of a library. We simply need a compiled '.class' file, from which we can create a new enhancement. Furthermore, instead of re-writing a class to provide a new function, we can simply declare we wish to extend a class, and add a few lines of code to get an enhancement. It makes the process of re-using code much simpler, and code easier to read. When we extend an existing class, we 'inherit' all of the attributes, and methods, of the parent class which are not static, final or private. This makes programming much easier, and simpler, as we don't re-invent the wheel each time we extend other classes.

For example, all Java objects are inherited from the java.lang.Object class. This means that we can call the toString() method inherited from java.lang.Object, and get a string representation of any java object, such as an Integer, a Float, a Double, etc. Take a look at the following example, which demonstrates the use of the 'extends' keyword.

public class A extends java.lang.Object {
        public int number;

        public String toString() {
                return new String("Value : " + number);
        }
}

In this example, we implicitly state that we extend java.lang.Object, and override the functionality of the toString() method (present in java.lang.Object class) by providing our own function. Note that all classes, whether they state so or not, will be inherit from java.lang.Object. Our next example shows a more common use of the keyword 'extends'. In this example, we inherit from class A, which means that B will also contain a field called number, and a function called toString().

public class B extends A {
        public void increment() {
                number++;
        }
}

public class ABDemo {

        public static void main (String args[]) {
                // Create an instance of B
                B counter = new B();

                // Increment B
                counter.increment();

                // Call toString() method
                System.out.println ( counter.toString() );
        }
}

Even though we never defined a toString() method, or added a number field to the B class, it has inherited this information from A. This is a powerful feature of object-orientated programming, and can save significant time when developing classes.

In the above case, we can say that Object is base class and A is derived class or A is base class for B and B is derived class. Derived class always extends base class.An interface can also extend another interface. Extending a class is closely related to implementing an interface. A new class can extend at most one superclass, but it may implement several interfaces. Extending means adding new method definitions. Implementing means satisfying the existing interface contract by writing the proscribed method bodies. When a class or abstract class implements or extends, those methods are are available in all descendants. All descendant classes automatically implement that interface too.

The below program explains the ability of derived class to extend the fields and methods of base class.

class Box {

        double width;
        double height;
        double depth;
        Box() {
        }
        Box(double w, double h, double d) {
                width = w;
                height = h;
                depth = d;
        }
        void getVolume() {
                System.out.println("Volume is : " + width * height * depth);
        }
}

public class MatchBox extends Box {

        double weight;
        MatchBox() {
        }
        MatchBox(double w, double h, double d, double m) {
                super(w, h, d);

                weight = m;
        }
        public static void main(String args[]) {
                MatchBox mb1 = new MatchBox(10, 10, 10, 10);
                mb1.getVolume();
                System.out.println("width of MatchBox 1 is " + mb1.width);
                System.out.println("height of MatchBox 1 is " + mb1.height);
                System.out.println("depth of MatchBox 1 is " + mb1.depth);
                System.out.println("weight of MatchBox 1 is " + mb1.weight);
        }
}

Output

Volume is : 1000.0
width of MatchBox 1 is 10.0
height of MatchBox 1 is 10.0
depth of MatchBox 1 is 10.0
weight of MatchBox 1 is 10.0

Extend in Prototype Languages

Self

Self is a prototype based dynamic object-oriented language. It is centred around principles of simplicity, uniformity and concreteness with the help of its environment and virtual machine.

The language Self was designed in 1986 by David Ungar and Randall B. Smith at Xerox PARC. The programming environment was built at Stanford University and the project continued at Sun Microsystems. The project had many releases latest being Release 4.0 in July 2010. The latest release contains entirely new user interface and programming environment, enabling programmers to create and modify objects withing environment and facility of saving the objects in files for distribution purposes.Environment also includes a graphical debugger, and tools for navigation through the system.

Self consists of collection of objects built for writing self programs. It provides a way to present the objects to programmer and user in direct and physical way. The objects are built using prototype based style.It is difficult to predict what qualities a set of objects and classes will have in the distant future. So, one cannot design a class hierarchy properly.All too often the program would eventually need added behaviours, and sections of the system would need to be re-designed (or refactored) to break out the objects in a different way.Systems would tend to grow to a point and then become very rigid, as the basic classes deep below the programmer's code grew to be simply "wrong".Dynamic languages such as Smalltalk allowed for this sort of change via well-known methods in the classes; by changing the class, the objects based on it would change their behaviour.In languages like C++, where subclasses can be compiled separately from superclasses, a change to a superclass can actually break precompiled subclass methods.In Self, and other prototype-based languages, the duality between classes and object instances is eliminated.Instead of having an "instance" of an object that is based on some "class", in Self one makes a copy of an existing object, and changes it.Basic objects that are used primarily to make copies are known as prototypes. This technique is claimed to greatly simplify dynamism. If an existing object (or set of objects) proves to be an inadequate model, a programmer may simply create a modified object with the correct behavior, and use that instead. Code which uses the existing objects is not changed.

Self inspired many programming languages. The common being JavaScript language which is used primarily for dynamic web pages in all modern browsers.

It would be better if we explain extend in Self programming language using JavaScript as an example.

JavaScript

In Javascript, objects can have properties added to them dynamically.Javascript is evaluated at run-time, and because all Objects are implemented as hash tables. It also makes it possible to refer to the same property either directly or as a string.

Example:

var spacing = myTable.cellSpacing;
var spacing = myTable['cellSpacing']; //equally as valid

JS Interpreter creates the properties for an object instance on the fly.

Example: You want to keep track of how many times the user changes the value of a certain text input (say, the quantity of items ordered). With a traditional compiled OOP language you'd need to subclass the input object and create a custom flavor that allows a timesChanged property. With JavaScript, you simply write:

if (myInput.timesChanged==null) myInput.timesChanged=1;
else myInput.timesChanged+=1;

Extending an object by adding a custom method can be quite convenient, but it only applies to that particular object's instance. In case we want to modify the entire existing class to add a new functionality, we need to use "prototype" property. To add a property or method to an entire class of objects, the prototype property of the object class must be modified.The intrinsic object classes in JavaScript which have a prototype property are:

Object.prototype — Modifies both objects declared through the explicit new Object(...) contructor and the implicit object {...} syntax. Additionally, all other intrinsic and user-defined objects inherit from Object, so properties/methods added/modified in Object.prototype will affect all other intrinsic and user-defined objects.

Array.prototype — modifies arrays created using either the explicit new Array(...) constructor or the implicit [...] array syntax.

String.prototype — modifies strings created using either the explicit new String(...) constructor or the implicit "..." string literal syntax.

Number.prototype — modifies numbers created using either the explicit new Number(...) constructor or with inline digits.

Date.prototype — modifies date objects created with either the new Date(...) contructor.

Function.prototype — modifies functions created using either the explicit new Function(...) constructor or defined inline with function(...){...}.

RegExp.prototype — modifies regular expression objects created using either the explicit new RegExp(...) constructor or the inline /.../ syntax.

Boolean.prototype — applies to boolean objects created using the explicit new Boolean(...) constructor or those created using inline true|false keywords or assigned as the results of a logical operator.

Adding properties or methods to the prototype property of an object class makes those items immediately available to all objects of that class, even if those objects were created before the prototype property was modified. The below example explains the use of "prototype" keyword much better:

Person.prototype.populationCount=0;
function Person(name,sex){ 
   Person.prototype.populationCount++;
   this.getName=function(){ return name } 
   this.getSex=function(){ return sex } 
   this.setSex=function(newSex){ if (confirm('Really change the sex of "'+name+'" to '+newSex+'?')) sex=newSex; } 
   this.kill=function(){ Person.prototype.populationCount-- } 
} 
var gk = new Person('Gavin','male');
var lrk = new Person('Lisa','female');

//Following yields "There are 2 people in my world." 
alert("There are "+gk.populationCount+" people in my world.");

//Following creates a new public property of 'gk' and sets it to 102 
gk.populationCount+=100;

var geo = new Person('George','male');
alert('GK thinks there are '+gk.populationCount+' people, but everyone else knows there are '+lrk.populationCount+' people.');
//Above yields "GK thinks there are 102 people, but everyone else knows there are 3 people."

The below example explains the use of prototyping by implementing slice() method to Array class in Javascript:

The slice() method of an array object returns a subsection of the array. While quite useful, this method was not part of the original ECMAScript specification, and not supported by all JavaScript interpretters. When writing code which may be run on older browsers where you'd like to slice some arrays, you can either write code which doesn't use this convenient method (an annoying approach) or you can roll your own implementation of slice().

To do this propertly, we need to know exactly what the slice() method does. From MSDN:

arrayObj.slice(start,[end]);

"The slice method copies up to, but not including, the element indicated by end. If start is negative, it is treated as length + start where length is the length of the array. If end is negative, it is treated as length + end. If end is omitted, extraction continues to the end of arrayObj. If end occurs before start, no elements are copied to the new array."

Thus armed, following is a custom implementation of the slice() method. By using Array.prototype this method is made available to all array objects. Note that even if the following isn't the most efficient code possible, it will only be used on those few browsers where the slice() method isn't available. (As it turns out, the implementation below is almost identical in speed to the built-in method.)

//Only add this implementation if one does not already exist. 
if (Array.prototype.slice==null) Array.prototype.slice=function(start,end){ 
   if (start<0) start=this.length+start; //'this' refers to the object to which the prototype is applied 
   if (end==null) end=this.length;
   else if (end<0) end=this.length+end;
   var newArray=[];
   for (var ct=0,i=start;i<end;i++) newArray[ct++]=this[i];
   return newArray;
}

As you can see, the first three lines change the parameters passed in to values useful for the for loop, and then this method simply creates a new array, copies the desired items over one at a time, and then returns that new array as the result of the method. (Like most methods in JScript, the original object is not modified.)

Boolean XOR Operation in Boolean class

JavaScript provides a boolean AND operator (&&), a boolean OR operator (||), and a boolean NOT operator (!). But it is missing a boolean XOR operation. (In English, XOR can be stated as "If A is true or B is true, but not if both are true.") The following simple code adds an XOR() method to the Boolean object.

Boolean.prototype.XOR=function(bool2){ 
   var bool1=this.valueOf();
   return (bool1==true && bool2==false) || (bool2==true && bool1==false);
   //return (bool1 && !bool2) || (bool2 && !bool1); 
} 
true.XOR(false); //returns a value of true 

(The above method requires the passed value to be an actual boolean value to succeed. The second option, commented out, will attempt to cast bool2 to a boolean value for the comparison. If that line were used instead, values of 0, null, and undefined would be interpretted as false, and other non-empty values such as 1 or "foo" will be interpretted as a value of true.)

References

<references></references>

Additional Resources