----- Original Message -----
From: Ben Giddings <ben / thingmagic.com>
Date: Friday, August 8, 2003 3:14 pm
Subject: Re: Ducktype, right?

> On Fri August 8 2003 2:36 pm, Chris Morris wrote:
> > Ben Giddings wrote:
> > I agree with Ryan's reply to you. If I only need the one method 
> on the 
> > object I'm interacting with -- should that one call not be 
> allowed 
> > simply because all of the other methods that I don't need aren't 
> > consistent with what it *should* be?
> 
> Ouch, this is one of the most difficult-to-understand sentences 
> I've seen in a 
> long time.
> 
> I'll pretend I understand it and give an example:
> 
> 
> #
> # Get the contents of the object, if it acts like a string, 
> # simply return it, if it acts like a file, return its contents, 
> # if it acts like a frobnitz, foozle it.
> #
> def getContents(obj)
>  if obj.respond_to? :read
>    return obj.read
>  elsif obj.respond_to? :to_str
>    return obj.to_str
>  elsif obj.respond_to? :foozle
>    return obj.foozle
>  end
> end
> 
> class EmailMessageBody < String
> 
>  attr_accessor :read 
> 
>  def initialize
>    @read = false  # By default, mark the message body as unread
>  end
> end
> 
> Imagine that the "getContents" method is buried deeply within the 
> source code 
> to a library somewhere.  The programmer would never see that 
> method, even if 
> he/she did see the commenting of the method.  Now what happens 
> when I pass an 
> EmailMessageBody into getContents?  It will see that it responds 
> to "read" 
> and return a boolean.  That's not what you want.  That's not what 
> the 
> documentation says, but that's what it will do.  This could be a 
> really hard 
> bug to track down.
> 
> Testing to see if the "read" method is supported is not really 
> "duck typing", 
> in the sense that you're not really checking to see if it's a 
> duck.  Just 
> because the object implements a certain method doesn't mean that 
> that method 
> does what you expect.
> 
> In certain cases, checking to see if a method exists is enough.  
> The method 
> "to_str" is a prime example.  If someone implements that method in 
> a way that 
> doesn't return a string, they deserve whatever problems they get.  
> There are 
> plenty of examples of how to_str is used everywhere else, and the 
> function 
> name is pretty descriptive.  Other functions are less obvious.
> 
> Somebody else gave this example (though I added the "implements 
> Duck" that I 
> think was just accidentally left out):
> 
> interface Duck
> {
>   void quack();
> }
> 
> class MyDuck implements Duck
> {
>   void quack()
>   {
>      System.exit();
>   }
> }
> 
> Here the "implements Duck" is essentially an agreement saying "I 
> want other 
> peole to see this as a Duck".  You're free to break that 
> agreement, but if 
> you do, you can expect trouble.
> 
> On the other hand:
> 
> class Psychiatrist
> {
>  void quack()
>  {
>    quack_references++;
>  }
>  void shrink()
>  {
>    shrink_references++;
>  }
> }
> 
> Just because someone has a "quack" function/method, doesn't mean 
> they want 
> their object to be treated as a Duck.

But if you have a Psychiatrist, why would you pass
it to a method that expects a Duck? The method
should be documented as expecting a duck, so passing
a Psychiatrist is wrong. The only difference
between Java and Ruby in this respect is that in
Ruby, you'd find out about it at runtime, which is
the best you can do anyway, since implements_interface?
is a method call anyway (granted, it's easy to figure
out than having quack() do something wrong).

>  When you create a class and 
> add 
> methods to it, should you always have to say: "Hmm, what's the 
> most popular 
> way _____ is used", and make sure yours acts the same way?

I think that generally you design a class for a
specific purpose, and only pass it to methods that
expect it to act in the way it was designed.

> My understanding was always that "implements Foo" in Java meant: 
> "my class 
> will define the following methods, in the way that Foo describes, 
> so you can 
> treat my class as a Foo".
> 
> As for the subject of why it is useful to implement a set of 
> methods rather 
> than just the one you care about, consider a function like:
> 
> def dumpContents(obj)
>  if obj.respond_to? :write
>    obj.write(data)
>    obj.flush if obj.respond_to? :flush
>    obj.close if obj.respond_to? :close
>  elsif ...
>  end
> end
> 
> I think it would be much more clear if you could simply say:
> 
> def dumpContents(obj)
>  if obj.implements_interface? :WriteableIO
>    obj.write(data)
>    obj.flush
>    obj.close
>  elsif ...
>  end
> end

Ruby is certainly more verbose in its checking of
type, when looked at this way.

Notice in your above example, the second version
isn't the same as the first. The first version
accepts something that allows writing, but not
necessarily flush and close, while the second
version only accepts something that does all three.
What if you want to instead output to a string?
Flush and close don't make sense in that case. So
you'd end up with an interface for each, which
would look like:

def dumpContent(obj)
   if obj.implements_interface? :Writable
      obj.write(data)
      if obj.implements_interface? :Flushable
         obj.flush
      end
      if obj.implements_interface? :Closable
         obj.close
      end
   end
end

Which could be shortened, but it's almost no better
than duck typing. The only thing that you gain is
that implementing Flushable implies that flush
has certain semantic meaning, which needn't be the
case, and in any case can be solved by documenting
obj.

If you don't want to do it that way, you'd need to
have separate cases for WritableIO and StringIO
which involves code duplication, so it's not ideal
either.

Furthermore, what if you want a one-shot object?
Take the idea of anonymous inner classes in Java.
You don't want to define a whole listener class
just to handle System.exit on window closing,
so you define a one-shot anonymous class like this:

addWindowListener(new WindowAdapter() {
   public void windowClosed(WindowEvent e)
   {
      System.exit(0);
   }
});

You could do this in ruby as follows:

obj = Object.new
def obj.windowClosed(e)
   exit(0) # I'm not sure if this is the right method, but you get the idea
end
addWindowListener(obj)

Now, there's no way to make obj extend a particular
interface other than using WindowListener.new as the
base instantiation. However, consider that you could
have something like:

exitObj = Object.new

def exitObj.windowClosed(e)
   exit(0)
end
def exitObj.actionPerformed(e)
   exit(0)
end
...
addWindowListener(exitObj)
closeButton.addActionListener(exitObj)

(this is all very Swing/AWT style)
So you could use the same object for all your close
operations (or all your open file operations, etc.).
However, if you used interfaces, exitObj would have
to implement both WindowListener and ActionListener,
and there's no way to make an anonymous inner class
that does that (I think), so you'd need to define
a real class. In ruby it's worse, because it would
require multiple inheritance, since it'd have to
extend both WindowListener and ActionListener, as
there are no interfaces, per-se in Ruby.

If I may comment on your getContents example, please take no offense, it seems a bit too general an
example for me to see it as a real problem. It
tests several dubiously related methods and and
returns their results. It doesn't seem like something
that would be in real code.

It reminds me of a discussion I read on here from,
say, a year ago. Someone was talking about how in
ruby, because you can dynamically change methods,
you can never know whether your code will work or
not. Their point was that someone could re-define
how a method works and break a bunch of your code.
The question is, why would someone do that? And
even if they did, it would likely happen in testing,
and would produce errors. Unless you're reading in
user input and eval()ing it, you won't have this
problem, as long as you document and test.

I can't fully answer your getContents method
because it lacks context. Something above getContents
would have to specify how objects passed in should
behave in documentation, and if someone doesn't
follow that, then it breaks.  Java interfaces
recommend how the implementation of their methods
behave.  That needs to be recommended in Ruby
somehow, whether in documentation or in some other
way. read() could have two different meanings in
two different modules, so you shouldn't pass an
object from the first to a function from the second.


Anyhow, I'm rambling on too much, so I'll stop here
and possibly resume later.

- Dan