From: Geoff Youngs Date: 2003-11-19T07:31:48+09:00 Subject: Re: "stereotyping" (was: Re: Strong Typing (Re: Managing metadata about attribute types) ) --n8g4imXOkfNTN/H1 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Wed, Nov 19, 2003 at 07:06:24AM +0900, Simon Kitching wrote: > On Wed, 2003-11-19 at 10:30, Weirich, James wrote: > > David Black (dblack@wobblini.net) wrote: > > > Class name checking doesn't ensure needed behavior. Actually, let > > > me > I think this is a flaw in the current ruby libraries, not a flaw in > the concept of typechecking. > There can be an interface defining what it means to "be an IO". Any > object which does in fact behave exactly in accordance with this > contract should mark itself as being an implementation of that > interface. > I suggest this approach: [interfaces] > Now someobj.is_a?(IO) will return true for all those objects which > fulfil the IO contract. Note that the IO module does *not* have any > implementation; it is purely an abstract concept and therefore can be > mixed in to any existing class. When mixed in, it functions as a > promise of behaviour which can be checked for at runtime. > I expect that some people will put forward the idea that some class > might be invented that happens to also fulfil the IO contract without > being explicitly marked so, and that checking for the "IO" marker > would disallow passing this class. Well I think the chances of a class > happening to exactly fulfil a contract without that contract ever have > being taken into consideration is smaller than the chance of us all > being wiped out by an asteroid. > Note that checking for *concrete* classes using is_a is a totally > different issue. That does present problems regarding the inheritance > tree, because you can't just mix in such a type to any other type. I > think the arguments against this pattern are on much stronger ground. > Comments? I've been playing with a system like this - it allows the creation of specialised modules which define a relatively strict contract which is enforced at the time they are mixed in to a class. e.g. Interface.define("Enumerable") module Interface::Enumerable needs :each optional :min, :max include ::Enumerable end An interface can define prerequisite methods (both instance and singleton methods) as well as optional methods. It is then mixed into a class as normal, but only after the required methods have been defined. It checks that the required functions have been defined, but also checks for optional functions and preserves them if they already exist. So, for the above example, using Interface::Enumerable could allow an array class to use the brute force Enumerable#max, but wouldn't prevent the Range class from implementing a much simpler and faster Range#max. Commments? Geoff. --n8g4imXOkfNTN/H1 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="interface.rb" =begin Interface.rb - Basic Java-style interface implementation Permits the creation of specialised modules which provide functionality == Usage To create an interface, call Interface.define == License Same as Ruby. == Example require 'interface' Interface.define("StreamRead") module Interface::StreamRead needs :getc optional :read def read(noBytes=nil) buf = "" if noBytes.kind_of?(Numeric) until noBytes <= 0 or (c = getc).nil? buf << c noBytes -= 1 end else buf << c until (c = getc).nil? end buf end end class FileStreamReader def initialize(fpOrName) if fpOrName.respond_to?(:to_str) @fp = File::open(fpOrName.to_str,"r") elsif fpOrName.kind_of?(IO) @fp = fpOrName end end def getc @fp.getc end include Interface::StreamRead end == Limitations Limited to the Interface namespace Interface.define is required before normal declaration =end class Interface < Module class << self def new(name) raise "Interface #{name} is already defined" if const_defined?(name) const_set(name, super()) module_eval <<-EOB module #{name} class << self def symbol(id) id.kind_of?(Symbol) && id || id.intern end def needs(*ids) @__if_rs ||= [] ids.each { |id| @__if_rs << symbol(id) unless @__if_rs.include?(symbol(id)) } @__if_rs end def class_needs(*ids) @__if_crs ||= [] ids.each { |id| @__if_crs << symbol(id) unless @__if_crs.include?(symbol(id)) } @__if_crs end def optional(*ids) @__if_ops ||= [] ids.each { |id| @__if_ops << symbol(id) unless @__if_ops.include?(symbol(id))} @__if_ops end def needs?(id) @__if_rs.include?(symbol(id)) end private def append_features(aClass) imethods, smethods, missing = aClass.instance_methods(true), aClass.singleton_methods(true), [] needs.each { |func| missing << func unless imethods.include?(func.to_s) } class_needs.each { |func| missing << "class."+func unless smethods.include?(func.to_s) } raise NoMethodError, sprintf("Method(s) %s not implemented by %s",missing.collect{|i| i.to_s }.join(", "),aClass.inspect) unless missing.empty? preserve, imethods = {}, aClass.instance_methods(false) optional.collect{ |i| i.to_s }.each do |func| aClass.module_eval("alias_method(:"+(preserve[func] = (func + "__" + func.hash.to_s))+", :"+func+")")if imethods.include?(func.to_s) end super preserve.each { |orig,new| aClass.module_eval(sprintf("remove_method(:%s);alias_method(:%s, :%s);remove_method(:%s)", orig, orig, new, new)) } end end end EOB const_get(name) end alias :define :new end end class Object def implements?(aInterface) self.class.include?(aInterface) end end --n8g4imXOkfNTN/H1--