What's all the fuss about base.pm?

schwern on 2007-07-31T11:10:44

This post on p5p is worth repeating:

Johan Vromans wrote: >>From Ricardo SIGNES > Perl-Critic-Tics-0.002 > > Perl::Critic::Policy::Tics::ProhibitUseBase : > > "use base qw(Baseclass); > > You've seen that a hundred times, right? That doesn't mean that > it's a good idea. It screws with $VERSION, it (temporarily) > clobbers your $SIG{__DIE__}, it doesn't let you call the base > class's import method, it pushes to @INC rather than replacing it, > and it devotes much of its code to handling the nearly totally > unused fields pragma -- but the multiple inheritence that pushing > to @INC sets up is not supported by fields." > > Is this still true? I thought most of these issues were dealt with.

If they aren't, nobody's reported them.

* The $SIG{__DIE__} issue is fixed. * I don't know what people are talking about screwing with $VERSION. * You shouldn't be importing functions from a superclass and nobody's stopping you from adding a "use Baseclass" if you really want to. * I've yet to see a practical reason why pushing to @INC is wrong. If it replaced @INC the same people would be complaining I'm sure. * Who cares how much code it devotes to fields? * Who cares if fields doesn't support MI? Does anyone use fields anymore?

Most folks miss the important points: * It happens at compile time. * It has error checking.

That its at compile time avoids some nasty circular dependency issues such as...

# File A.pm package A;

use B; @ISA = qw(B);

use C;

1;

# File B.pm package B;

sub foo { 42 }

1;

# File C.pm package C;

print A->isa("B") ? "Yes\n" : "No\n"; print A->can("foo") ? "Yes\n" : "No\n";

1;


Use base instead and that all works. Yes, C should be using A but I've read a lot of commercial code that just assumes other modules are loaded and its a horrible thing to debug.


Importing

Ovid on 2007-07-31T11:26:02

* You shouldn't be importing functions from a superclass and nobody's stopping you from adding a "use Baseclass" if you really want to.

True, but it's frustrating to inherit from a base class which might need arguments passed to its import method. There was some discussion about this on P5P and I recall someone suggesting something like this:

use base
  'SomeClass',
  'SomeOtherClass' => { import => [ qw/import args/ ] };

That would be backwards compatible and get around that limitation.

Re:Importing

drhyde on 2007-08-01T12:16:33

Also import() isn't just used to, umm, import. It's quite a common idiom to use it to pass global settings to a class, such as telling it to put temporary files in a particular place, or changing its default timezone. Those still have their place in OO programming.

Even so, I don't consider this to be a problem with base.pm. If I really need to do things like that, the work-around is trivial.

Re:Importing

schwern on 2007-08-01T21:58:19

Here's what I expressed on p5p: 80% of the code and syntax will be devoted to a feature that you use 1% of the time and has a perfectly reasonable work around that everyone understands. Just not worth the extra work/complexity/documentation/bugs.


        use base qw(Foo);
        use Foo qw(some args);


And isn't it "let's add just one more feature" that got base.pm in this situation in the first place?

The $VERSION "issue"

Adrian on 2007-07-31T11:48:37

* I don't know what people are talking about screwing with $VERSION.

Not that I think it's a problem - but I think folk are referring to base setting VERSION to '-1, set by base.pm' if it's undef.

Adrian (thoroughly in the "liking base" camp)

Re: base.pm

rjbs on 2007-07-31T13:05:59

The policy in question comes from Perl-Critic-Tics, which describes itself as: The Perl-Critic-Tics distribution includes extra policies for Perl::Critic to address a fairly random assortment of things that make me (rjbs) wince.

In other words: it's things that I don't want to see in my code. If you want to be like me, swell.

I don't think any of the things I listed are *bugs*, they're just things that *I don't like*.

"You shouldn't be importing functions from a superclass." Wishing doesn't make it so. In the cases where this is practical, I will end up saying "use Baseclass (...); BEGIN{@ISA = 'Baseclass';}" Now maybe I am going to use base elsewhere. Did I use it first? Well, now I clobbered ISA. So I need to remember to use base second or, if I need to use things first, I need to push to @ISA. Or I could just not ever use base, and then I will not have to worry about this.

To enforce this, maybe I'll write a Perl::Critic policy and put it in my private collection of them. Not only will I help myself write better code, I'll get to start a lively discussion about how I'm a bitcher and not a do-er!

I agree that people would complain if base replaced ISA. Its current behavior is well documented and well established. That's why I don't want to use it: changing it would probably be more annoying to everyone involved than adding some goofy method to say "set, don't push."

By not using base.pm, I minimize the ways in which I am likely to confuse myself.

I admit that my comment about fields is a red herring. What I don't like is the fact that it deals with fields at all, not that it has X amount of code. I don't like it because it has to be documented. If a novice programmer is looking at my code and sees that it uses "base," he may not know what base is and will consult the documentation. The documentation will talk about fields, which sound like some sort of basic and important thing for classes, but are not. Despite this, neither base nor fields says, "please don't use me, because we are from another planet." Is this a nit pick? Sure. It's just one more little reason that I don't see much value in using base in my code.

It's nice that base.pm easily moves ISA-setting and module requirement into one statement at compile time. I am aware of the need to do this, though, and I can put it in a BEGIN block. I do not worry about remembering this, because I'm familiar with the need to do so. I'm less likely to remember that I made a decision to stop using base.pm, which is why I wrote a policy for that, and not a policy to make sure that @ISA is only set during compile time.

As per your request, I have filed tickets. I only thing one of them is much of a bug. It's the one you seem to know nothing about, so I am sorry that I didn't report it the first time it annoyed me.

http://rt.cpan.org/Ticket/Display.html?id=28579

Bogus argument

Corion on 2007-07-31T16:41:03

Yes, C should be using A but I've read a lot of commercial code that just assumes other modules are loaded and its a horrible thing to debug.

What a bogus argument. I would accept this if you had said "I have written a lot of commercial code that just assumes ...", but arguing a (unremovable, by now) misfeature of your code as something actually beneficial is quite a stretch. I hadn't looked at the source code of base.pm before people argued against it, but for my taste it is far too close to the code of UNIVERSAL::require which also silently gobbles up errors happening during requireing a file. The custom $SIG{__DIE__} handler also is an unneccessary complication that stems from the same idea that errors should not be visible upwards.

Now, neither of these two points are what constitutes a "bug", but for some code that should basically be (less the error checking for recursive inheritance)

package base;
use strict;

sub import {
    my ($class,$base) = @_;
    my $target = caller();
    (my $file = $base) =~ s!::|'!/!g;
    $file .= ".pm";
    require $file;
    {
        no strict 'refs';
        push @{ "$target\::ISA" }, $base;
    };
};

1

base.pm has far too many lines and does far too many contortions that I do not want to track down just in case I have a weirdo error that base.pm chooses to suppress. Also, no bad action-at-a-distance occurs by messing with $VERSION and all errors point directly to the source like Can't locate Foo.pm in @INC (... instead of Base class package "Foo" is empty. - in 95% of the cases, that's exactly what's wanted, because somebody misspelled Foo.

(Yes, I'm aware that my code forces every base class to be in a file with a name that matches that base class. If you don't want that, write your own @INC hook or fiddle with %INC.)

Anyway - I started out liking base.pm, but after looking at the source code and having been bitten by UNIVERSAL::require gobbling my (syntax) errors far too many times, I don't want to become one of the people with anecdotal evidence that base.pm has bugs.

Re:Bogus argument

schwern on 2007-08-01T22:02:38

I think we're having a violent agreement. My example about the commercial code was illustrating the importance of inheriting at compile time vs setting @ISA at run time. All the other details of "use base" are orthogonal.

Some people remember to put a BEGIN around setting @ISA (and @EXPORT) but most don't either because they forget or aren't aware.