import Our::Code::*;

Ovid on 2007-08-07T08:34:52

A long time ago, Matt Trout mentioned that he's started using aliased a bit and wanted a feature like this:

use aliased 'Our::Code::*';

That would be conceptually equivalent to the following Java code:

import Our.Code.*;

Now imagine you have a directory structure like this:

lib/Our/Code/Customer.pm
lib/Our/Code/Datacenter.pm
lib/Our/Code/Item.pm
lib/Our/Code/Office.pm
lib/Our/Code/Order.pm
lib/Our/Code/Server.pm
lib/Our/Code/Server/Dedicated.pm

The above 'aliased' line would give you aliases like this:

use aliased 'Our::Code::*';

my $cust       = Customer->new;
my $datacenter = Datacenter->new;
my $item       = Item->new;
my $office     = Office->new;
my $order      = Order->new;
my $server     = Server->new;
my $ded_server = ServerDedicated->new;   # unsure of this

Traditionally, that would be something like this:

use Our::Code::Customer;
use Our::Code::Datacenter;
use Our::Code::Item;
use Our::Code::Office;
use Our::Code::Order;
use Our::Code::Server;
use Our::Code::Server::Dedicated;

my $cust       = Our::Code::Customer->new;
my $datacenter = Our::Code::Datacenter->new;
my $item       = Our::Code::Item->new;
my $office     = Our::Code::Office->new;
my $order      = Our::Code::Order->new;
my $server     = Our::Code::Server->new;
my $ded_server = Our::Code::Server::Dedicated->new;

Needless to say, you can see which one folks might like.

Of course, the devil's in the details and I think a module other than aliased would be called for with this experiment. There would also have to be customization options. For example, I think by default it shouldn't use those modules until the alias is actually invoked. Also, by default, we'd want to ensure that only one directory structure is searched and classes loaded from that directory. We might want descending into subdirectories to be optional. One also might want to be able to load and alias all classes in the format Our::Code::O*.

The biggest objection I expect to hear from folks is "mysterious action at a distance is bad!" I can understand that point and I would have raised it myself a long time ago, but I've been around the block long enough to realize why this works for Java so well: after you've been working on your business code long enough, you know what's in lib/Our/Code/*. Of course, it will have issues when you try to do this with lib/Our/Code/DateTime.pm in there. I think a CHECK block might help, not to mention invoking the alias should check if there's a competing entry in %INC. For example, with Our::Code::Order:

sub Order {
  my $class = 'Our::Customer::Order';
  if ( exists $INC{'lib/Order.pm'} ) {
    # die and noisily complain about a conflict
  }
  # load the class if not loaded
  return $class;
}

Unfortunately, class method calls sometimes dispatch to the namespace and sometimes would dispatch to the alias. It all depends on how the code is organized. This could be the biggest problem.

I have enough sympathy for the 'action at a distance' argument and a firm enough belief that this is different enough from aliased that the experiment definitely deserves to be in a different namespace. Suggestions? (Other than "please don't". If you don't like zucchini, don't eat it.)

It's also worth nothing that there's a broken import module on the CPAN and it hasn't been updated in 8 years.


Module::Pluggable?

thinc on 2007-08-07T10:57:05

You might be able to use Module::Pluggable http://search.cpan.org/dist/Module-Pluggable/

import-on-demand is not a best practice

ferreira on 2007-08-07T11:12:19

The so called import-on-demand feature of Java:

import foo.bar.*;
is generally considered bad practice, good only at quick-and-dirty code. Tools like Eclipse help expand these evil statements into qualified imports after compiling and resolving the class names in the current source file.

Some of the problems are mentioned at http://javadude.com/tools/importifier/

  • If you use a name that exists in more than one package, the Java compiler cannot determine which fully-qualified name to use. You need to specify the full qualification yourself.
  • If a name is currently unambiguous (exists in only one package that you specify via import-on-demand), someone could add the same name to another package and your program will no longer compile.
  • If someone is reading your code, they need to check in each specified package to see which contains a referenced class Foo.

For Perl, at least the last problem remains the same: where that Foo came from? Add two or three such statements "aliased 'Foo::*';" and "aliased 'Bar::*';" and the actual (qualified) dependencies are hidden from a cursory glance at the code. Obviously, it may occasionally look right and clever for the ones used to the magical powers of AUTOLOAD or even subtler things. On the long run, we can regret that cleverness, as Damian says on PBP.

On the other hand, almost the same rate of elegance and conciseness can be obtained with a little change in the object architecture, leading to code like this:

use Our::Code;

my $god = Our::Code->new;
my $cust       = $god->new_customer;
my $datacenter = $god->new_datacenter;
my $item       = $god->new_item;
my $office     = $god->new_office;
my $order      = $god->new_order;
my $server     = $god->new_server;
my $ded_server = $god->new_dedicated_server;   # unsure of this
The cleverness moves from the user's code into the library (which can rely on magical powers to load modules and do the right thing appear to the user). You can tell "but now I have to change my library code?" Yep. With a bunch of related classes and no code to integrate them, that seems like a good idea.

For example, that's what I actually did at the Business-BR-Ids distribution which implements code for testing various Brazilian id numbers. Instead of saying:

use Business::BR::CPF qw(test_cpf); # natural person's id
use Business::BR::CNPJ qw(test_cnpj); # company'is id
use Business::BR::CPF qw(test_pis); # natural person's health id

test_cpf($cpf);
test_cnpj($cnpj);
test_pis($pis);
I use

use Business::BR::Ids qw(test_id);

test_id('cpf', $cpf);
test_id('cnpj', $cnpj);
test_id('pis', $pis);
and let Business::BR::Ids load Business::BR::* modules when necessary and with appropriate error messages in the case of failure.

Re:import-on-demand is not a best practice

Ovid on 2007-08-07T11:17:04

You know, I think you make a compelling argument. I'll have to consider this and I might just drop the idea.

Re:import-on-demand is not a best practice

sigzero on 2007-08-07T12:04:58

It used to be pretty standard in Python to import the whole shebang as well (ie from foo import *) but that is frowned upon today as well. Mainly because of namespace pollution.

You really should only call what you need and if you do somthing like Our::Code::*; You may be surprised by what you are importing (whether you did it intentionally or not).

Shortcutting

phaylon on 2007-08-07T15:15:56

For me, the beauty of 'aliased' lies mostly in its shortcutting of names. If I'd want to make an API to declare multiple of these in one, I would probably do...

use aliased::all 'Foo::Bar' => qw( Baz Quux Fnord );

my $baz   = Baz->new;
my $quux  = Quux->new;
my $fnord = Fnord->new;

With this, it is IMO short enough, explicit enough, and contains no surprises.

Poor Maintenance Programmers

chromatic on 2007-08-07T17:29:14

... after you've been working on your business code long enough, you know what's in lib/Our/Code/*.

Java's an unreadable language because novices can't look at that program and instantly know where exactly every import came from. (Hey, this argument is fun to make!)