On Mon, Jun 11, 2001 at 09:18:14PM -0500, David L. Nicol wrote:
> But I'm digressing.  What I want to talk about is overloaded builtins.
> 
> I recently suggested that C<close> be overloaded to make its argument,
> when its argument is not a filehandle, become read-only.  An objection
> was made to this, on the grounds that homonymous operators are confusing,
> with eval block and eval string being given as an example of something
> that is confusing.
> 
> This post is intended to be a response to that objection.
> 
> Natural language contains many homonyms.  This is rarely, outside of
> dramatic plots, a problem, as there are other indicators, up to and 
> including requests for clarification, but generally "context," to
> guide us human beings as we apply our common sense to the great
> questions of What Is Fair and What Is Right without taking wrong turns
> or digressing into discussions of propagandistic trends in modern
> journalism while dividing chocolate cake evenly among party guests.
> 
> Okay?
> 
> C<eval> (string/block) is but one.

I'd hold this up as a failure of polymorphic functions.  eval STRING
and eval BLOCK do completely different things and people associate
their behaviors, merits and flaws together because they have the same
name.


> C<do> might be considered another.

Another fine counter-example.  Who understands all the different
things that do() does??  Its particularly bad because of the name.
"do" is about as generic as "foo".  It could mean anything.


> overloading C<length(@)> to do what beginners expect.

This is an example of polymorphism done for good.  Why?  First,
there's a relatively obvious expectation of what length() will do with
a string and an array (a hash might be a little less obvious).
Second, and much more subtle, is that while the object of the function
changes, its basic functionality does not.

Its very important.  Think of it as a verb (function) and object of
that verb (arguments).  length() is the verb, a string or array is the
object.  When acting on a string, you get the number of characters in
the string.  When acting on an array, you get the number of elements
in the array.  Basically the same operation, adapted for the object.
This is polymorphism for Good.

Now look at eval.  When acting on a string, it compiles and runs it as
code.  When acting on a block, it traps any errors and prevents dying.
You may be able to come up with some weak analogies between the two,
but they're two different functionalities.

Different object, same action:       ok
Different object, different action:  not ok


>       close (IO)              # closes an open stream, returns 
>                               # true on success or false on failure,
>                               # sets  $! on failure
> 
>       close (\$)              # overwrites the ASSIGN method of the
                                <overly elaborate explaination snipped>

close.  Closing a filehandle means you're no longer going to read
and/or write to it (depending on how it was opened).  Closing a
variable means you're no longer going to write to it... but reading is
ok.  The analogy is there, but its not a particularly strong or
obvious one, and there's the jarring difference that you can no longer
read from a closed filehandle, but you can read from a closed variable.

You've got to be careful with polymorphism and ensure that you've got
strong analogous action in all cases.  Not just because its clever or
you like the name.  This is not to say we shouldn't have polymorphic
built-ins (we already have more than you might think) but they must be
chosen with extreme care.


Some more counter-examples... un/pack() on a filehandle could compress
and decompress the file!  It might be perfectly logical if un/pack
didn't already do something completely different.

Some good examples... delete/exists work on both arrays and hashes.
delete() completely removes an array/hash element, exists() checks to
see if an array/hash element has been populated (even if its
undefined).  The details might start to diverge a bit, but the basic
meaning doesn't change.


Homogeneous operators are potentially confusing (just because English
has homonyms doesn't mean its not confusing) but not all polymorphic
functions are homonyms.  Find the ones which retain their basic
meaning across all their objects, those are the good ones.


PS  delete() and exists() are also polymorphic, as is reverse(),
chomp(), chop(), goto() and die().  There are probably more, but I
can't think of them, probably because they mesh so well we don't even
think about it.  You could consider functions which have default
arguments as polymorphic, then there's lots and lots of polymorphism
in Perl.  But I digress.


-- 

Michael G. Schwern   <[EMAIL PROTECTED]>    http://www.pobox.com/~schwern/
Perl6 Quality Assurance     <[EMAIL PROTECTED]>       Kwalitee Is Job One
kiloconway: unit of extreme mind expansion.  Equal to 1024 conways,
  one kiloconway gives the sensation that all of the quantuum particles
  in your brain have been spontaneously converted into Mars bars (IN
  CONSTANT TIME!).  The presumed existance of kiloconway scale events
  has led many to believe that higher forms of intelligence do in
  fact exist, and are living quite happily in Rural Australia.
        -- Ziggy

Reply via email to