From: "T. Onoma" Date: 2003-11-22T19:54:34+09:00 Subject: InterfaceContract distilled :: how to implement (mostly) Distilled: 1. An InterfaceContract is a guarentee that a class provides required "respond_to" methods. Ruby can verify this on either defining or constructor events. So it is a strong promise with reasonably low overhead. 2. An InterfaceContract may also specify the types of method parameters. But this requires extra syntax, and Ruby can't verify these at constructor or defining moments. So it is only a weak promise, anyway. 3. To overcome the later a runtime TypeChecker is required. This provides strong promise, but has higher overhead. To improve performance this could be an option that can be switched on or off either on the command line or inline. 4. InterfaceContracts will require type definitions to specify the base Interfaces. And it has been proposed that standard built in Ruby classes be used as that basis. 6. Using these base classes amounts to providing a set of correspoding hollow, immutable replicas of the base classes. Classes that Implement these are checked to verify that they fulfill the role of a complete superclass of the implemented immutables. Thoughts: 1. If there is too much coding overhaed for defining InterfaceContracts it will get little use. This would make it rather obscure and thus perhaps not worth the effort. 2. But this can be addressed by not having special Interface types. We can just use existing classes. If we say: implement Klass (in contrast to mixins) then we are saying that our new class must fullfill the role of a superclass of Klass, prior to the instantiation of our new class, or it is not valid. 3. When #implements is invoked a new frozen subclass of Klass is created, the Interface, and a mixin is inserted in our new class. So when our new class is substantiated, the use of super calls the initialize method of the mixin module which compares the Interface to self. If it doesn't check-out an ImplementationError is thrown. 4. This design is not as light weight as the one Sean has put forth because it checks the interface everytime a new object, that implements an interface, is created. But the advantage is that no special hooks are required for dealing with the dynamic adding or removing of methods to our classes working to continue to ensure the contract is being kept. The dynamic nature of Ruby remains intact. Also I think that if we subsequently freeze our class, we could have this mixin removed, as it would no longer be needed. in an enviornemt where InterfaceCnntracts are bing used freezing calss definition make a lot of sense, and could be common place. Although this feature would have to be added to core ruby itself. 5. This design dosen't account for parameter types b/c they would have only a weak promise any way. So this is best reserved for a Euphoria like type checker as I have descibed elsewhere, or :pre and :post assertion methods as descirbed by James. Each has advantages and disadvantges. The Euphoric system has one major advantage in that it can be integrated with InterfaceContracts such that the types defined could double for Interface classes, which seems useful considering the next point. 6. One problem becomes quite appearent when using this tool. That of Granularity. Ruby's built in classes have too many methods for this to be very useful. This may indicate that Ruby's classes, somehow, need to be broken into smaller meaningful chunks, even if it is merely a matter of namespace dividers inside the class that have no effect on the class other then to group related methods, and, of course, a means to reference them from the outside. Implementation: class ImplementationError < StandardError end module InterfaceContracts end class Module def implements(*klasses) klasses.each do |klass| InterfaceContracts.class_eval %Q{ unless #{klass} === Class class #{klass} < #{klass}; end #{klass}.freeze end } end ifc_str = '[' + klasses.collect {|k| "InterfaceContracts::#{k.name}" }.join(',')+']' ifm = Module.new ifm.module_eval %Q{ def initialize(*args) #{ifc_str}.each do |k| umeths = k.public_instance_methods(true) - self.public_methods if umeths != [] raise ImplementationError, self.class.name + " as " + k.name + "\n" + umeths.sort.join(', ') end end super end } class_eval { include ifm } end end Example 1: # this may seem trival but it is a guaranteed super class of String. class MyClass < String implements String def initialize super p "I promise to quack like a String." end def extra; p "extra"; end end m1 = MyClass.new # => "I promise to quack like a String." m2 = MyClass.new # => "I promise to quack like a String." Example 2: # show that the methods of Integer have not been implemented. class MyClass < String implements String, Integer def initialize super p "I promise to quack like a String and Integer." end end m1 = MyClass.new # => ImplementationError (see below) MyClass as InterfaceContracts::Integer (ImplementationError) +@, -@, abs, ceil, chr, coerce, div, divmod, downto, floor, integer?, modulo, nonzero?, prec, prec_f, prec_i, quo, remainder, round, step, times, to_int, truncate, zero? # notice that the unimplemented methods are listed. -t0