perl5i Makes More Simple Things Simple

| 5 Comments

Suppose that you want to load a module dynamically (you have the name in a scalar), then alias a function from that module to a new name in another class. In other words, you want a renaming import. How do you do that in Perl 5?

{
    no strict 'refs';
    eval qq{require $class} or die $@;
    *{$other_class."::".$alias} = $class->can($func);
}

There's a lot of magic going on there. Aliasing requires using symbolic refs which means turning off strict. Because you want strict off in as small a hunk of code as possible you have to enclose it in braces. Then, require Class and require $class work differently, so you have to trick require into seeing a bareword by evaling it. Don't forget to catch and rethrow the error! Finally, to do the aliasing you need to get a code ref with can() and assign it to the symbol table via the magic of typeglobs.

Guh. There's an idea in interface design called The Gulf of Execution which measures the distance between the user's goal and the actions she must take to achieve that goal. The goals here are to:

  1. Load a class from a variable.
  2. Alias a function in that class.

The actions are:

  1. Enclose the code in a block.
  2. Turn off strict.
  3. require $class in an eval block to turn it into a bareword.
  4. Catch and rethrow any error which might result.
  5. Use can() to get a reference to the function.
  6. Construct a fully qualified name for the alias.
  7. Turn that into a typeglob.
  8. Assign the code ref to the typeglob.
  9. Drink.

Try explaining that to a non-Perl guru.

Now consider the perl5i (specifically perl5i::2) way:

$class->require
      ->can($func)
      ->alias($other_class, $alias);

Release the breath you've been holding in for the last 15 years of Perl 5.

Through the magic of autoboxing, perl5i lets you call methods on unblessed scalars, hashes, arrays, regexes, references... anything. It also implements some handy methods. Some, like require(), are core functions redone as methods. Others, like alias(), should be core functions never made it in for whatever reason. autoboxing gives perl5i the freedom to add handy features without polluting the global function/method namespace with new keywords.

Recall the goals:

  1. Load a class from a variable.
  2. Alias a function in that class.

... and consider perl5i's actions:

  1. Call require() to load the class.
  2. Call can() to get a reference to the function.
  3. Call alias() on that reference to alias it to the other class.

The gulf has narrowed to a stream you can hop over while hardly getting your feet wet.

The goal of perl5i is to bring modern conveniences back to Perl 5. In the 15 years since the release of Perl 5, we've learned a lot. Our views of good practices have changed. 15 years ago, aliasing a function was black magic available only to the wildest of gurus. Now it's a technique many module authors take advantage of. Why should it remain complicated and error prone?

Autoboxing is a big part of perl5i, allowing it to add convenience methods without having to add new keywords. Adding new keywords--which contracts the function names available to programmers--is a big thing holding Perl 5 back! Every potential new keyword is a debate over compatibility. Autoboxing eliminates that debate. It takes off the brakes.

Some other examples: how do I check if a scalar contains a number, an integer, or a float? The Perl FAQ entry on the subject is two pages long offering five different possibilities, two of which require pasting code. Code in FAQs tends to rot, and perlfaq is no exception; without testing nobody noticed that those venerable regexes fail to catch "+1.23". How does perl5i do it?

say "It's a number"   if $thing->is_number;
say "It's an integer" if $thing->is_integer;
say "It's a decimal"  if $thing->is_decimal;

It's clear, simple, fast, and tested. TMTOWTDI is great and all, but FAQs are about getting things done, not about writing dissertations on the subject. perl5i picks one way that's pretty good and makes it available with no fuss.

The bar for what is "simple" has moved since Perl 5 first came out. perl5i takes the goal of "simple things should be simple" and helps us all catch up.

5 Comments

But why are 1. and 1.0 both integer, rather than decimal?

Is it bad form to comment on your own article? Well, I figure I'll get the ball rolling. (BTW It takes OpenID so you don't really need your own account to comment if that's holding you back)

This article has obviously cherry-picked a set of features that's been implemented well, but this combination isn't something that was planned. I was writing a test and needed to do just the above, dynamically load a class and alias a function from it. The idea of having require() return the class loaded, rather than the cutesy but impractical last evaluated expression, was suggested by Matt Trout to allow chaining. I'm not as gaga about having methods return themselves to allow chaining as some others are, but its quite tidy.

I hope more folks pile on and make simple things simpler. An especially fertile ground is the perlfaqs. Take any answer that involves cutting and pasting a bunch of code or loading a module and turn it into a method.

I think you're old enough now that your mother and I can talk to you about "bugs". See, sometimes software has "bugs", especially young software that is carefree about its features. That means it won't act the way you expect. Its because of the bug.

When you see a bug, tell someone about the bug. Replying to a blog post, even if it is by the author, doesn't really count. You have to report the bug to the proper authorities. http://github.com/schwern/perl5i/issues

(The answer to your question is "because my implementation is entirely too clever")

But everything about perl5i is entirely too clever! When I encounter unexpected behavior with entirely too clever things, I'm not at all confident that it's a bug. Maybe I'm just not clever enough to understand it. When I run

$ ruby -e 'puts 0 ? "true" : "false"'
true

I think, "Huh? That can't be right." But it turns out it is right. The Ruby people have some reason for wanting 0 to be true. I don't understand it. I'm not clever enough for Ruby.

Similarly, when I run

$ perl5i -e 'say "1.0"->is_integer ? "true" : "false"'
true

I think, "Huh? That can't be right." But I don't run off the bugs page. Maybe Schwern has some reason for wanting decimal whole number strings to be integers. It says right there it's tested. Surely, this would be one of the first five or ten tests written. I'm probably just not clever enough for perl5i.

So I asked.

Thank you for asking. I'm projecting my own frustration with users not wanting to report their maybe-not-a-bug on you. Apologies.

I know some developers are asshats and treat an invalid bug report like a personal insult to their mother, and then proceed to insult yours. For example. Who would ever report another bug after being treated like this?

I promise you, perl5i wants to hear from you. Its not a "bug tracker", its not even really an "issue tracker". Let's call it "negative feedback". Even if it was clearly documented and tested that 1.0->is_integer is true, perl5i would still want to hear your "WTF?!" report.

Apply some hubris and tell us how perl5i sucks! You say its entirely too clever, then tell us! In painful detail! We won't yell at you, I promise. Its the only way to get things fixed. Otherwise silence is interpreted as acceptance and you accept crappy software.

Visit the home of the Perl programming language: Perl.org

Sponsored by

Monthly Archives

Powered by Movable Type 5.13-en