Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

$foo = "Foo::Bar"; $foo->new() works?

by mikfire (Deacon)
on Jan 23, 2005 at 03:55 UTC ( [id://424328]=perlquestion: print w/replies, xml ) Need Help??

mikfire has asked for the wisdom of the Perl Monks concerning the following question:

I am working on a relatively complex series of related modules. I was trying to create a "dispatcher" to make things easier -- so you could type "Foo::Bar->new('Baz')" instead of saying "Foo::Bar::Baz->new()".

After running through several attempts involving eval and other weirdness, I found this works:

$huh = "Foo::Bar::Baz"; $huh->new();
even under "use strict". I would have thought I needed either an eval or a "no strict 'refs'" to make this work.

I am not complaining - this is great DWIMery. But why does it work?

Replies are listed 'Best First'.
Re: $foo = "Foo::Bar"; $foo->new() works?
by saintmike (Vicar) on Jan 23, 2005 at 04:03 UTC
    From perlobj:

    Instead of a class name or an object reference, you can also use any expression that returns either of those on the left side of the arrow.

    So the following statement is valid:

    Critter->find("Fred")->display("Height", "Weight");
    and so is the following:
    my $fred = (reverse "rettirC")->find(reverse "derF");
Re: $foo = "Foo::Bar"; $foo->new() works?
by dws (Chancellor) on Jan 23, 2005 at 04:18 UTC

    One reason that it is a Very Good Thing that it works this way is that it allows you to write things like subclassable factories, where the class of the thing being constructed is determined by the factory subclass.

    package Factory; sub makeThing { my ($class, @args) = @_; my $thingClass = $class->thingClass; my $thing = $thingClass->new(@args); $class->magicStuff($thing); return $thing; } sub thingClass { 'Thing' } sub magicStuff { my ($class, $thing) = @_; # pretend there's generic magic stuff here }

    Now, to change the class of the thing being constructed, you simply subclass the factory class and override one method.

    package SpecialFactory; use base qw(Factory); sub thingClass { 'SpecialThing' }

    Now, Factory->makeThing gives you a Thing, and SpecialFactory->makeThing gives you a SpecialThing.

      or you could even

      sub thingClass { ref $_[0] }

      in the base class, and forget about having to create overrides.

Re: $foo = "Foo::Bar"; $foo->new() works?
by Zaxo (Archbishop) on Jan 23, 2005 at 04:16 UTC

    When you get down to it, a perl namespace is just a string. It gets mapped into the symbol table by simple trickery involving string operations.

    A class constructor method new() usually starts with,

    sub new { my $class = shift;
    then goes on to generate some suitable $self, and ends with,
    bless $self, $class; }
    which is typically all that's done with $class.

    If you look at the docs for bless, you'll see that all it needs is a string in the second argument. There is nothing to prevent you from blessing a reference into any string you like:

    perl -Mstrict -we'my $vanilla = bless {}, "Im a Pudding"; print ref $v +anilla, $/' Im a Pudding
    Of course that will fail to find any methods for $vanilla if you try to use them.

    So you're doing the right thing. There are no reference or alias issues, and strict doesn't raise a peep.

    After Compline,
    Zaxo

Re: $foo = "Foo::Bar"; $foo->new() works? (factories++)
by tye (Sage) on Jan 23, 2005 at 11:40 UTC

    A symbolic reference to a package is all there is so we can't disallow it. A symbolic reference to a variable is what use strict 'refs' disallows.

    Now, I don't like symbolic references to packages much. I'd require (well, encourage anyway) that you mention the package once to load it and that process would give you a handle to the package that you use instead of repeating the package name all over the place.

    Package names should be world-wide unique, which means they can get rather long. So I'd like to start my code more like:

    use strict; my $Heap= require Data::Heap; my $Parser= require XML::Twig; my $q= require Net::CGI; my $Bank= require Finance::Banking::English::USA::CreditUnion::CA::Poi +ntMagu; ...

    If such became well supported, then you could define a "package reference" like we defined "scalar reference", "hash reference", and the rest. Then require could default to returning this new type of non-symbolic reference and you could make using a symbolic reference optionally cause an error just like other symbolic references.

    This would also solve a lot of complex problems. You could have multiple versions of the same package loaded at the same time quite easily. Modules wouldn't have to break if they were installed in the wrong place (a user could pick a non-default place to install a module and give the designation for that location to require and it would find the module file and load it and things would just work without having to change the module name declared inside the module etc.).

    Well, solving those things becomes easy if you disallow symbolic references to packages like Perl disallows symbolic references to lexical variables. Rather, if you make it so that require is the only way to get a reference to a package.

    This actually creates a few namespaces that are quite natural. Modules get the namespace by which they are normally referred such as "Algorithm::Diff" (no change from how things work today). When a module is loaded, it gets named by the path to the file that it was loaded from (this way you can have two different versions of one module that even have the same version number and yet they both can work fine in a single instance of the Perl interpreter at the same time). require takes "Algorithm::Diff" and uses the current scheme to transform that into a path name. But when it loads the code in that file, it gets loaded into a package specific to that path string not named by some 'package' statement in the file.

    And the module code only states the base module name once for documentation purposes and the user of the module also only types in this name once.

    I think that would be cool. (But having require return the factory for the module, even w/o all of the other features, would still be nice.)

    - tye        

Re: $foo = "Foo::Bar"; $foo->new() works?
by BUU (Prior) on Jan 23, 2005 at 03:59 UTC
    Er, the exact same reason:
    Foo::Bar::Baz->new()
    works. Perl just needs a package name as a string to look up the appropiate method, it doesn't care if the string is in the form of a bareword or contained in a scalar.
Re: $foo = "Foo::Bar"; $foo->new() works?
by ikegami (Patriarch) on Jan 23, 2005 at 06:35 UTC

    Something else you can do is:

    sub Baz () { 'Foo::Bar::Baz' } $baz = Baz->new();
Re: $foo = "Foo::Bar"; $foo->new() works?
by gaal (Parson) on Jan 23, 2005 at 06:12 UTC
Re: $foo = "Foo::Bar"; $foo->new() works?
by Aristotle (Chancellor) on Jan 23, 2005 at 16:00 UTC
    $ perl -MO=Deparse,-x7 -e'Foo::Bar->new()' 'Foo::Bar'->new; -e syntax OK

    Pay attention to the quotes: as far as Perl is concerned, class methods are always invoked on a string containing the package name. Whether that's a literal string or one stored in a variable matters naught.

    Makeshifts last the longest.

      It even turns out that there is no speed difference. (I expected *some* advantage to the inline string, because there'd be an extra indirection. But it's lost to the method call overhead.)

      use Benchmark; package Bar; sub new {}; package main; my $obj; my $class = "Bar"; # "our" *is* slower, though. timethese -1, { bareword => sub { $x = Bar->new() }, var => sub { $x = $class->new() }, }; ######################################### Benchmark: running bareword, var for at least 1 CPU seconds... bareword: 1 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 59 +0160.38/s (n=625570) var: 1 wallclock secs ( 1.06 usr + 0.00 sys = 1.06 CPU) @ 59 +0160.38/s (n=625570)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://424328]
Approved by Zaxo
Front-paged by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (8)
As of 2024-04-18 06:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found