cs/Interface

copyright (c) 2004 Sean O'Dell

Purpose

cs/Interface is a library which implements a form of type-checking which helps developers understand the purpose of Ruby objects encountered at run-time by tagging classes and objects with type IDs, and enforcing that required methods are present, and take the required number of parameters. Built-in interfaces for many existing Ruby classes and objects include: IString, IArray, IHash, IDate and ITime. More pre-defined interfaces will be added in time.

current version: 0.6.2
project homepage: http://rubyforge.org/projects/interface/
download url: csinterface-0.6.2.tar.gz
changes: CHANGELOG
license: GPL

Table of Contents
    Quick Lesson
    Developing Interfaces Tutorial
        Creating New Interfaces
        Applying Interfaces
        Checking Interface Compliance
        Applying Interfaces To Your Classes
        Interfaces and Modules
        Interfaces and Singletons
    How It Works
    Interface Class
        Interface.new
        interface#add_pattern
        interface#add_class_patterns
    Module Class
        Module.implement
    Object Class
        object#implement?
    Standard Interface Objects
        IArray
        IDate
        IHash
        IString
        ITime

I need your help! This interface library is very useful, but I think for most developers, they just want to check for interfaces on existing Ruby classes and objects, so I need to create as complete an interface library as possible. Please join the discussion here and help me out!

Installation

using Ruby Gems

# gem -Ri csinterface

from source

Download csinterface-0.6.2.tar.gz to a working directory, then issue the following commands:

$ tar -xzvf csinterface-0.6.2.tar.gz

$ ruby setup.rb config
$ ruby setup.rb setup
# ruby setup.rb install

Using cs/Interface

Quick Lesson

Ruby Gems

If you installed cs/Interface with gem, be sure to add the following code to the top of your scripts:

require "rubygems"
require_gem "csinterface"

Example

In the example below, you have a method which takes one parameter. Normally, you would duck-type or check for needed methods with respond_to?. In this case, however, neither of those two ways are reliable enough for your purposes. You need to be sure this object behaves like a Hash before you attempt to use the object. What do you do?

require "celsoft.com/interface/standard"

def func(obj)
  raise "not a hash" if (not obj.implement?(IHash))
end

func(Hash.new)
func(ENV)

Notice the method is called twice, passing both a Hash object and the ENV object. IHash is one of many pre-defined interfaces and is already applied to the Hash class and the ENV object.

Developing Interfaces Tutorial

Creating New Interfaces

You create new interfaces by creating an instance of the Interface class, then adding method patterns to the interface. You can pull method patterns en-masse from existing classes or modules, or add patterns individually.

require "celsoft.com/interface"

IHash = Interface.new
IHash.add_class_patterns(Hash, :[], :[]=)
IHash.add_pattern(:each_pair, proc{||})

Applying Interfaces

You can apply interfaces to classes, giving them the ability to claim that they implement the interface. Compliance will be validated with another call.

class Hash
  implement IHash
end

Checking Interface Compliance

Using the above example, it's easy to test objects to see if they implement the IHash interface.

h = Hash.new
p h.implement?(IHash) == true

Applying Interfaces To Your Classes

Now that IHash is defined above, you can declare that your own objects implement the IHash interface. You can do this by declaring the interface in your class or module definition. Remember to implement all the methods IHash requires.

class MyHashType
  def [](key)
    # ...
  end

  def []=(key, value)
    # ...
  end
  
  def each_pair()
    # ...
  end

  implement IHash
end

m = MyHashType.new
p m.implement?(IHash) == true

Interfaces and Modules

You can define interfaces in modules or classes, but you may only declare implementations in the class definitions.

module MyHashModule
  def [](key)
    # ...
  end

  def []=(key, value)
    # ...
  end
  
  def each_pair()
  end
end

IMyHash = Interface.new
IMyHash.add_class_patterns(MyHashModule, :[], :[]=, :each_pair)

class MyHash
  include MyHashModule
  implement IMyHash
end

m = MyHash.new
p m.implement?(IMyHash) == true

Interfaces and Singletons

Singletons may also be declared as implementing an interface by accessing/creating the object's singleton class. If an object's class becomes a singleton, any existing implementation declarations are copied to the new singleton class.

p ENV.implement?(IHash) == false # (before)
class <<ENV
  implement IHash
end
p ENV.implement?(IHash) == true # (after)

How It Works

Method patterns are stored with each Interface object and are used to check against the methods of any given object. The name and number of parameters are important. If an object, or any of its ancestors, never claims to implement an interface, it will always report that it does not implement a given interface, even if it may be capable of it. When an object,or any of its ancestors, claims to implement an interface, a thorough check is performed to verify that it actually does. This check is only performed when necessary for efficiency, when implement? is called. The result of the check is cached and returned quickly on subsequent calls to implement?. If either the interface definition changes, or methods are changed in the class of the object, the cache is dumped and the thorough check is performed the next time implement? is called, and the answer is then again cached.

Interface Class

require "celsoft.com/interface"

Interface.new

interface#add_pattern

interface#add_pattern(idMethod, &block)

Adds a single method pattern to the interface by analyzing the block given. Blocks can be proc objects or methods.

interface#add_class_patterns

interface#add_class_patterns(modObject [, idMethod1 [...]])

Adds a series of method patterns contained in the class or module modObject.

Module Class

require "celsoft.com/interface"

Applies to Modules and their derivatives, such as Class.

Module.implement

Module.implement(interface)

Declares the module (or class) as an implementation of interface.

Object Class

require "celsoft.com/interface"

object#implement?

object#implement?(interface)

Returns true if the class of the object, or any ancestor, claims the object is an implementation of interface, and if the object's class methods match the patterns of the interface.

Standard Interface Objects

require "celsoft.com/interface/standard"

Pre-defined interfaces for standard Ruby classes and objects.

IArray

For array-like classes, which contain items indexed by number. Pre-applied to Array.

IDate

For date-like classes.

IHash

For hash-like classes, which get and set values using keys. Pre-applied to Hash and ENV.

IString

For string-like classes which implement the to_str method. Pre-applied to String.

ITime

For time-like classes.