Elliott Hughes (ehughes / bluearc.com) wrote:
> 
> i don't know, maybe i should give in and learn to love dynamic typing and
> instance-specific behavior. but it gives me great difficulty when there are
> perfectly adequate traditional solutions to the same problem.
>

Let me first outline a specific advantage to the duck-typed singleton
solution.  I think it's important to understand this advantage before
moving on to your very good complaint.

Yes, the common solution is to pass blocks into methods.  A la:

  uri.add_progress_bar do |percent_done|
    puts "Yeah, we're at #{percent_done}% now!"
  end

I use this quite a bit in YAML.rb.  And, truth told, it is one of the
most frustrating parts of the library to maintain.  Albeit, for the end
user it is quite simple.

It is frustrating because a block is a dead end.  It is wonderful for
iteration.  It is great for short handlers.  But if I need to extend
what the block does, I often will need to remove the block and find
another solution.  If you like, I can go into specifics as to why this 
has been problematic in YAML.rb.  (See lib/yaml/rubytypes.rb)

Duck-typed singletons are another solution.  I'm not suggesting they
take us by storm, forcefully erasing blocks from our memory.  With
duck-typed singletons, I can move the blocks into Modules.

  module SpecialProgressBar
    def progress_bar( percent_done )
      puts "Yeah, we're at #{ percent_done}% now!"
    end
  end

Then, the user merely needs mixin the module into their singleton:

  require 'uri'
  uri = URI.parse( "http://whytheluckystiff.net/why.yml" )
  uri.extend SpecialProgressBar

If I was a library author who wants to use this technique (and I am!),
then I would write a wrapper for 'extend', which specifically checks the
incoming module for the proper interface.

  class URI
    def extend_progress_bar( progress_bar_module )
      extend progress_bar_module
      unless respond_to? :progress_bar
        raise TypeError, 
          "no progress_bar method in #{progress_bar_module}"
      end
    end  
  end

Then for the user:

  uri.extend_progress_bar SpecialProgressBar

Now, this doesn't solve the problem you've presented of the mistyped
singleton method.  But it does show some advantage?  And it does safely
solve a problem, no?  I'm not cutting any corners.  Just exploring Ruby
as a medium.

> to take whytheluckystiff's example, if i accidentally write
> 
>   def uri.progress_Bar( percent_done )
>     puts "Yeah, we're at " + percent_done + "% now!"
>   end
> 
> instead of
> 
>   def uri.progress_bar( percent_done )
>     puts "Yeah, we're at " + percent_done + "% now!"
>   end
> 
> the only indication i'll have of this is that my program will silently do
> the wrong thing at run-time. [this problem occurs with overriding of
> non-abstract methods in most languages, too.]

This is a very good argument.  I can't throw an exception just because
the developer has named a single method.  And I can't throw an exception
just because no progress_bar method was found.  So throwing exceptions
at any point is out of the question.

My first thought is to provide a method which activates the progress
bar functionality and audits the singleton for the proper methods.  I
think the advantage of being able to encapsulate all these block
listeners into classes is worth the payment of such a method.

> i also can't see how you could extend this to support a multiple listeners
> instead of just one. it's more like set_progress_monitor than
> add_progress_monitor, which may be fine in this instance, but the general
> point still stands.

Well, you could easily manage these added methods in an 'extend' wrapper
as shown above.  Rather than mixing in the module directly, you could
store the methods in an Array and call them orderly as events fire.  Not
bad.

But, no, you can't have multiple such methods in a singleton.  I imagine
you could use 'method_added' to route the singleton method straight into
the Array mentioned in the last paragraph.

I'm rambling way too much, so I'll scale back.  Thanks for the very
stirring questions, I admit that there's still much to think out.
I definitely have plans to use this in some very specific libraries.
I'm sure actual field testing will yield the validity of these ideas.

(Hope I haven't cluttered this list.  This feels more appropriate for
-talk than -core.)

_why

[http://whytheluckystiff.net/arch/2003/11/19/1069264728]