develooper Front page | perl.perl5.porters | Postings from November 2003

Re: [5.12.0] Proposal for changing UNIVERSAL semantics

Thread Previous | Thread Next
Michael G Schwern
November 4, 2003 15:13
Re: [5.12.0] Proposal for changing UNIVERSAL semantics
Message ID:
On Tue, Nov 04, 2003 at 06:39:06AM -0800, Michael Jacob wrote:
> >This is directly contrary to how OO inheritence works in Perl.
> >Anytime you inherit from a class in Perl you inherit *everything*.
> >This is a consequence of not having real method privacy, but that's a
> >Perl design decision.  We deal.  UNIVERSAL is simply a special case of this.
> >You can just as easily accidentally pick up an AUTOLOAD from Exporter
> >as from UNIVERSAL.  Should can() stop looking at Exporter?
> There's small difference. When I pick up an AUTOLOAD from Exporter, the _I_ picked it up and _my_ test cases will fail. If if picked it up via UNIVERSAL, that someone other broke my code by remote action and the programs that use my code will fail.

Consider the following.

$ perl -MDBI -wle 'package Foo;  @ISA = qw(DBI);  bar()'
Use of inherited AUTOLOAD for non-method Foo::bar() is deprecated at -e line 1.
Can't locate auto/Foo/ in @INC (@INC contains: /tmp/lib /sw/lib/perl5/5.6.0/darwin /sw/lib/perl5/5.6.0 /sw/lib/perl5/darwin /sw/lib/perl5 /System/Library/Perl/darwin /System/Library/Perl /Library/Perl/darwin /Library/Perl /Library/Perl /Network/Library/Perl/darwin /Network/Library/Perl /Network/Library/Perl .) at -e line 1

Whoops!  Must have picked up an AUTOLOAD from DBI.  But wait... is it
really from DBI?

$ perl -MDBI -wle 'package Foo;  @ISA = qw(DBI);  use Devel::Peek;  print Dump(Foo->can("AUTOLOAD"))'
SV = RV(0x14fbc) at 0xb976c
    COMP_STASH = 0x21210        "AutoLoader"
    START = 0x36730 ===> 1271
    ROOT = 0x3a700
    XSUB = 0x0
    XSUBANY = 0
    GVGV::GV = 0x21240  "AutoLoader" :: "AUTOLOAD"
    FILE = "/System/Library/Perl/"

Oh hey, its from AutoLoader!  Where did that come from?  DBI doesn't inherit
from AutoLoader...

$ perl -MDBI -wle 'package Foo;  @ISA = qw(DBI);  use Class::ISA;  print join ", ", Class::ISA::self_and_super_path("Foo")'
Foo, DBI, Exporter, DynaLoader

Gosh, where's that AUTOLOAD coming from?

Turns out its from DynaLoader which does this:

    require AutoLoader;
    *AUTOLOAD = \&AutoLoader::AUTOLOAD;

Same trouble UNIVERSAL::import had.

So, basically, you pick up an AUTOLOAD by inheriting from any module that
uses XS.  Feeling paranoid yet? :)  Should we change can() to ignore
methods inherited from DynaLoader now?

With they way Perl's inheritence works, you can inherit tons of crap you
never know about.  UNIVERSAL is just one small mechanism, the DynaLoader
hole is much, much, much bigger since it routinely occurs every time you
inherit from a module involving XS somewhere in its chain.  "Fixing" 
UNIVERSAL::can() is just sticking your finger in the dyke that was never 

> >You seem to consider that anything which is inherited from UNIVERSAL is a
> >mistake.  While its true that its all too easy for a lot of accidental
> No, I consider can() reporting it a mistake. That's a difference.

I guess the discussion here is over.  UNIVERSAL::can() is defined to check 
if the object/class has a method.  There's no exception for UNIVERSAL.  Going
through a multi-year deprecation cycle to change that is fairly pointless.  
End of story.

> >#1 implies a legit use of UNIVERSAL and shouldn't be papered over.  2 and 3
> >are poor design decisions and should be fixed.  Unfortunately, we can't
> >programmaticly tell the difference between them.
> Do you really think a UNIVERSAL::AUTOLOAD installed by Alice could autoload a missing method in Bobs code?

Why yes, since you asked, yes it can.

$ perl -wle 'use Symbol::Approx::Sub (xform => "Text::Metaphone");  sub phone { 42 }  print fon()'

Symbol::Approx::Sub uses a normal autoloader, not a universal one, but I
think it demonstrates that Alice can autoload Bob's code. :)

But UNIVERSAL::AUTOLOAD is more typically used to alter the way Perl's
method dispatching occurs.  Or to effect the error message for unfound
methods (what Attribute::Handlers is trying to do).

Am I saying its a good idea?  For the purposes of this conversation I
refrain judgement.  I'm just showing that its possible and been done.

> >UNIVERSAL is an exception *only that it sits at the top of the class
> >hierarchy* otherwise its a perfectly normal class.  Methods in it should
> >be picked up by can() same as any other.
> No, it does NOT sit at the base. It is searched just before giving up, in addition to first searching the full inheritance tree.

I think those two statements are equivalent.

> Consider this:
> sub UNIVERSAL::go { print "U" }
> sub b::go { print "A" }
> @c::ISA = qw( a b );
> c->go();
> would print "A" because UNIVERSAL is not the base class of a. Add @a::ISA=qw(UNIVERSAL) and it will print "U".
> It is an exeption.

Regardless of how it fits into the inheritence structure, UNIVERSAL::go is
still there and callable.  c->can('go') returns true as long as either
UNIVERSAL::go or b::go exist.  This is *encapsulation*.  c inherits go()
from one of its parents and it shouldn't matter to a user of c what
parent it gets it from.  UNIVERSAL is, ultimately, a parent of c.

> >The whole point of OO is that subclasses act like their parents.  UNIVERSAL
> >is the parent of everything.  Again, you might not like it but that's the
> >way it is.
> And another point is, that changing of a superclass will usually break the subclass. With UNIVERSAL we allow this at runtime. Whow.

You can change any class at runtime.  UNIVERSAL just happens to be more
promiscuous which is the purpose it was designed for.

Folks doing Naughty Things to UNIVERSAL is dealt with the same way folks
doing Naughty Things to any other class is.  Perl does absolutely nothing.
We have no class protections.  Its a social issue.  Trying to reverse this
major design decision in one little spot ain't gonna help.

> >> $class->foo if $class->can('foo') and $class->can('foo') ne UNIVERSAL->can('foo');
> >
> >Again, you have this idea that anything inherited from UNIVERSAL is garbage.
> >This is wrong.
> As I said before, why do you expect that UNIVERSAL's method can do the job? I wouldn't trust in that idea unless I had it designed into these classes myself.

Then you miss the whole point of modules. :(

> >Now you're saying that if you put something on CPAN it'll be unused?  Grand!
> No, I'm saying that anything on CPAN that implements something every programmer will do on its own will be unused.
> Nobody will ever install a module that just implements a max() function. 

Lots of people install and use CLASS.

> Nobody will ever have the idea to look for it on CPAN, because everyone has his own sub max{$_[0]>$_[1]?$_[0]:$_[1]} writen out in 10 seconds.
> People will not look for modules that solve problems they don't have. They will just write "package UNIVERSAL; sub AUTOLOAD {..." and never think about another module doing the same.

Fortunately, only the authors of modules which use UNIVERSAL::AUTOLOAD
have to.  And that's a damned small number.  They have their modules use
it, declare a dependency on it, and TADA!  Done.  And no user has to
even know it exists.  And no one has to wait for 5.10.

> >Placing something in the core doesn't guarantee anyone will use it either.
> That's true. People also write "system("rm $a || echo 'Error in rm'")"...
> >Let's see... File::stat, Time::Localtime, SelectSaver, FileCache.  Great
> >ideas, but for whatever reason nobody uses them.
> File::stat is not used, because the perlfunc/stat POD has the stat() example so handy. Time::Localtime is used heavily. SelectSaver, FileCache---never heard about them. What must I do to find them? ls -lR?

perldoc.  I rest my case. :)

> >Always remember this:  Once something is in the core *it cannot be removed*.
> It can. But it takes two major versions with "deprecated" warnings.

Do you realize that's FOUR YEARS?!

> >> Let's express it this way: UNIVERSAL::AUTOLOAD is a limited resource inside core Perl. So a solution for coordinated use should exist in the core, too.
> >
> >Prove the concept on CPAN first.  There's no need to special case your
> >solution by putting it into the core.  Find the users of UNIVERSAL::AUTOLOAD
> >and submit them patches for your new module.  I'm sure they'll be
> >more than happy that their modules don't conflict with anyone else.
> Ok, I'll try to find some time somewhere...

I know where you can find lots of time.  Ending this argument. ;)

> >Fortunately, only one person has to have that knowledge and its fixed for
> >everyone:  The person who fixes Attribute::Handlers and/or DBI.
> And what when Alice::A and Bob::B clash? and 2 years later with Carrol::C and Martha::M?

Then they use the UNIVERSAL::AUTOLOAD module you're going to write, you
generous person you. :)

> >> >I didn't hear anything about Net::SSH.
> >> 
> >> It used use() parameters in a cosmetic way for it's submodules and was no Exporter. Broke on UNIVERSAL::import...
> >
> >I don't see how that could be, it inherits from Exporter and has no
> >custom import.  Are you sure you mean Net::SSH and not something else?
> It did NOT inherit from Exporter itself. It was coded with that fact in mind, so it broke when it inherited from Exporter in my system via UNIVERSAL...

I don't know what version of Net::SSH you've got, but here's 0.07 from

package Net::SSH;

use strict;
use vars qw($VERSION @ISA @EXPORT_OK $ssh $equalspace $DEBUG @ssh_options);
use Exporter;
use IO::File;
use IPC::Open2;
use IPC::Open3;

@ISA = qw(Exporter);
@EXPORT_OK = qw( ssh issh ssh_cmd sshopen2 sshopen3 );
$VERSION = '0.07';

> >I can tell you right now what's wrong with the logic in XML::SAX::Base.  
> >Forget UNIVERSAL::AUTOLOAD, the idea that "its got an AUTOLOAD so it must 
> >be able to do what I want" isn't gonna fly.  You don't know what an AUTOLOAD
> >might do or what its there for.  *Especially* in a base class like
> >XML::SAX::Base where you don't know who's going to extend it or how.
> And you don't know what the exeption text might look like that you'll catch below.

Of course I do.  Same as the normal exception for when a method isn't
loaded.  If the AUTOLOAD produces otherwise then they're written a crappy
autoloader (yes, I list AutoLoader's in that category).  AUTOLOAD should be 
as transparent as possible.

> >        elsif( $obj->can('AUTOLOAD') ) {
> >	    # hmm, its got an AUTOLOADer, lets see if it does what I need
> >	    eval { $obj->some_method };
> >	    if( $@ ) {
> >                if( $@ =~ /^Can't locate object method "some_method"/ ) {
> >		    # guess it doesn't have it, move on.
> >		}
> Oh? What's that? "The handler we should use has a bug? Whatever; ignore it and go on."

You just try another handler, however that's done.  Forgive me for not
spelling it all out or having a complete understanding of XML::SAX::Base.

Presumably the whole point of all that XML::SAX::Base code is to discover
the capabilibies of its handlers and what events each handler wants to see.
It should do the same thing in the situation above as it should for when it 
can't find the method and there's no autoloader.

> No, the original code is completely right. The handler implements AUTOLOAD, so it must be able to handle all methods in the interface.

That seems directly contrary to the purpose XML::SAX::Base or any event
dispatch system.  Handlers aren't required to handle every event.  Having
an AUTOLOAD doesn't imply you can handle everything thrown at you.

Michael G Schwern
If it's stupid, but it works, it isn't stupid.

Thread Previous | Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About