UNIVERSAL::require must die!

schwern on 2005-10-02T08:04:36

Here I am preparing a new release of UNIVERSAL::require and I just can't document that the right thing to do is:

$module->require or die $UNIVERSAL::require::ERROR;

Yuck. So early in the module's development and already the interface is fucked. Not only is that a lot to type, a lot of people don't realize UNIVERSAL::require does not die. And why should they? require() dies. UNIVERSAL::require should die.

I'd rather fix it now than never be able to. Here's the plan.

-1) UNIVERSAL::require is getting its own distribution separate from UNIVERSAL::exports both to highlight the module better and to avoid any upgrade pain around UNIVERSAL::require from effecting UNIVERSAL::exports.

0) I've looked through the modules using UNIVERSAL::require (cpansearch++) and noticed only a few cases where the code wasn't dying on failure. So the potential impact of changing require() to die is small.

1) Add use() which will be just like require() except... * It calls import() * It dies on failure to load.

2) Add require_or_die() which is just like require() except... * It dies on failure to load.

3) Email all known users of UNIVERSAL::require (CPANTS++, cpansearch++) and telling them to switch to require_or_die() or use() and bump up their required version to 0.04.

4) Declare that the behavior of require() will change in the future to match require_on_die() unless there's a storm of protest.

That gives folks an upgrade path and enough lead time to adjust their code to the change.


*cry*

jesse on 2005-10-02T09:27:10

So this: ../JFDI/lib/JFDI/Web.pm: unless ($class->require) {

will stop working in the future?

About 40% of our internal code that uses UNIVERSAL::require is dying on failure. But that leaves 60% that isn't. And having something so simple change semantics out from under us is going to lead to pain.

That makes me sad. Please do note that there's a lot of code out there that uses CPAN modules but isn't on CPAN itself.

Maybe someday we'll get a sane module versioning system ;)

Re:*cry*

schwern on 2005-10-03T05:08:28

unless ($class->require) { will stop working in the future?
That's the plan.
Please do note that there's a lot of code out there that uses CPAN modules but isn't on CPAN itself.
If you look at the Makefile.PL for UNIVERSAL::require you'll notice logic in there which checks if the currently installed version of the module is older than the last API change. If so, it warns about the API change and sleeps for a moment so there's a hope of it being seen. Additionally, I'll put this warning in the version *before* I make the change to require() so there's plenty of lead time.

Re:*cry*

jesse on 2005-10-03T06:08:42

But that still breaks existing code. What about having the "...and die" behaviour require a different "use UNIVERSAL::require" line?

Re:*cry*

schwern on 2005-10-03T20:19:20

Already thought about this.
package A;
 
use UNIVERSAL::require;
 
package B;
 
use UNIVERSAL::require qw(die);
 
package C;
 
Some::Module::Which::Does::Not::Exist->require;
What happens? Thus is the curse of UNIVERSAL. I also can't use a different package name, such as UNIVERSAL::use or UNIVERSAL::load, if its going to have a require() method because then UNIVERSAL::require and UNIVERSAL::use could not be used together in the same package.

Re:*cry*

schwern on 2005-10-03T20:20:35

because then UNIVERSAL::require and UNIVERSAL::use could not be used together in the same package.
s/package/program/

storm of protest

davebaird on 2005-10-02T13:41:41

Not a fan of this change. What's wrong with saying $foo->require or die $@; That's not too much to type. It breaks this kind of useful idiom: $this->require or $that->require or die "No alternatives found: $@"; and it breaks this kind of portability even more

if ( $better->require )
{
   $better->frobnicate;
}
else
{
    $default->frobnicate;
}
It also makes it difficult to write a custom error message - I have to wrap the call in an eval, and so we're back where we started before UNIVERSAL::require.

require() has 2 modes - success or failure. It's only trying to do one thing - load code. It seems reasonable to differentiate these cases by returning true or false, rather than returning true or die. Might as well not return the true value if the failure mode will die anyway.

You'll save a few people who are not in the habit of checking return values from tripping over their feet. The rest of us will have to start wrapping these calls in evals if we want to take alternative action when a particular module doesn't load.

d.

Re:storm of protest

rjbs on 2005-10-02T14:21:26

I have to wrap the call in an eval, and so we're back where we started before UNIVERSAL::require.

No we're not. Now you're wrapping it in eval{}, not in eval "". That's a big win.

Re:storm of protest

kane on 2005-10-02T14:29:02

Coincidentaly, this is what Module::Load does -- analogous to 'require', but not doing the silly bareword vs string differentiation:



use Module::Load;



my $module = 'Data:Dumper';
load Data::Dumper; # loads that module
load 'Data::Dumper'; # ditto
load $module # tritto






You can read the docs here

Re:storm of protest

davebaird on 2005-10-02T14:59:46

I'd argue that's a small win. We're talking about a single call, like eval { $fooo->require }. The only win here is if we declared $foo, not $fooo. eval { $foo->reqiure } is still a runtime error.

Re:storm of protest

schwern on 2005-10-03T05:28:39

What's wrong with saying $foo->require or die $@; That's not too much to type.
Its not about the amount of typing (well... part of it is). Its about the expectation that require() should die. Even *I'm* surprised to find out that $foo->require doesn't die and I WROTE THE MODULE!

Error checking should be something you turn off, not something you turn on. Especially when it comes to dealing with the World Outside Your Program. Files, networks, etc... things not totally under your program's control and thus likely to do unexpected things.

I will say using $@ to return the error is a lot better than $UNIVERSAL::require::ERROR, and it seems to work... which is an accident, but a happy one. It certainly makes me gag a lot less. However I'm not happy about the mixing of idioms, using $@ for an error even though there's no (visible) eval.
It breaks this kind of useful idiom: $this->require or $that->require or die "No alternatives found: $@";
I have no matching idiom for that and that bothers me. Though it bothers me less than thinking about having to append "or die $@" on the end of most of my require calls much in the same way it bugs me about open() or any other "outside world" call. I tend to use Fatal a lot.
Might as well not return the true value if the failure mode will die anyway.
That makes eval { $foo->require } or do_bleh a little difficult.

Its a little known, and little used, fact that you can return pretty much any scalar from require including references.

Er... all of them?

cog on 2005-10-03T13:54:36

all known users of UNIVERSAL::require (CPANTS++, cpansearch++)

Er... are you assuming that all users of UNIVERSAL::require have their code on CPAN? :-)

Re:Er... all of them?

Louis_Wu on 2005-10-03T19:04:48

Er... are you assuming that all users of UNIVERSAL::require have their code on CPAN? :-)
Yet Another Reason to Release to CPAN. ( YARRC?) You get early notice when a module you rely upon is changed.

Re:Er... all of them?

cog on 2005-10-04T08:42:01

True, but sometimes... you just can't :-)

Re:Er... all of them?

Alias on 2005-10-08T12:50:32

Are you kidding?

You want us to drop every silly web application we've ever written for every client into CPAN?

Stuff that contains business logic that only applies to a guava farmer working in the north-western tasmania local government area of Tasman, who uses Quickbooks and has Internet Explorer specific functionality?

Re:Er... all of them?

schwern on 2005-10-03T20:22:44

Er... are you assuming that all users of UNIVERSAL::require have their code on CPAN? :-)
No, just all known users. Look inside the Makefile.PL for how everyone else is informed.

Re:Er... all of them?

cog on 2005-10-04T08:41:18

Hum, that is a good idea. However, shouldn't it ask "Do you want to install this thing, because it might screw things up?" instead of just saying "Hey, watch out, because you might just have screwed things up..." (I'm thinking automated instalations here)

I might be getting something wrong, though, and I'm not sure whether you'd be able to prevent the instalation from within Makefile.PL. Probably.

Re:Er... all of them?

schwern on 2005-10-09T20:19:35

You're right, I should ask the user using prompt(). I'd probably have it default to "Yes" most of the time as it would be far too obnoxious for someone to fire off an automated install and then have it fail halfway through because I made some trivial backwards incompatibility. Though if I was to have made the require change I'd have it default to "no".
I might be getting something wrong, though, and I'm not sure whether you'd be able to prevent the instalation from within Makefile.PL. Probably.
There is the simple expedient of just not producing a Makefile.

Re:Er... all of them?

cog on 2005-10-10T08:45:20

There's another issue... you're checking whether the module is already available and deciding whether the guy is using it depending on it being instaled before... what if he used it before, but is now installing things on a brand new machine? :-\

There is the simple expedient of just not producing a Makefile.

Indeed :-)

Re:Er... all of them?

schwern on 2005-10-10T11:22:02

There's another issue... you're checking whether the module is already available and deciding whether the guy is using it depending on it being instaled before... what if he used it before, but is now installing things on a brand new machine?
In that case then its being installed as a dependency of something which itself has not yet been installed. When the dependent thing gets installed its own tests will run and detect if the incompatibility caused a problem.

Oh... you do have tests, right?

Re:Er... all of them?

cog on 2005-10-10T13:00:19

Right, but maybe printing out a notice explaining what's happening would help, in that case (telling the user that maybe installing a previous version would solve the problem).

That, however, poses a problem, as the module was already installed... but maybe you can somehow detect, er... something else O:-)