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 share some of David's concerns, but would like to point out that not only
> does Class name checking not ensure needed behavior (which is true, but a
> small danger in my mind), it overly constrains the software to reject
> perfectly good solutions.
> 
> Consider the following function.
> 
>   def read(io_object)
>     fail "Not an IO Object" unless IO === io_object
>     io_object.read
>   end
> 
> This seems like a perfectly reasonable function and we feel safe because
> carefully check the "type" (actually Class ancestry) of our parameter.  
> 
> Now consider the following usage:
> 
>   io = StringIO.new("HI")
>   read(io)                # => RuntimeError: Not an IO object
> 
> This perfectly reasonable use of read will fail because StringIO does not
> enherit from IO, even though it implements IO-like methods.  This is a
> shame.  Our "type-checking" has needlessly constrainted our solution.  

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:

# an IO object fulfils the following contract:
#  ... details here ...
module IO; end   # note: no implementation!!!

class StringIO
  # declare that this class fulfils the IO contract
  include IO 
  ...
end

class File
  # declare that this class fulfils the IO contract
  include IO 
  ...
end

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?

Simon