Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Getting rid of "new"

by xdg (Monsignor)
on Jul 07, 2006 at 06:00 UTC ( [id://559716]=perlmeditation: print w/replies, xml ) Need Help??

I saw TheDamian's Sufficiently Advanced Technologies presentation tonight in New York, which emphasizes minimizing interfaces. It got me thinking about one of the little annoyances I type over and over again: new. Maybe it would be nice to have an implicit constructor that just relies on the class name itself.

What I do now:

use Some::Class; my $obj = Some::Class->new( \%options );

What I want

use Some::Other::Class; my $obj = Some::Other::Class( \%options );

I've got a thrown-together version of this working and I'm looking for suggestions on a distribution name for CPAN as well as feedback as to whether this seems useful or is too off-the-wall to bother with at all.

The module is tentatively titled Classname::Constructor. It's used like this:

package Some::Other::Class; use Classname::Constructor; sub new { # whatever }

In this case, behind the scenes during import, it just installs a closure function Class() to the Some::Other namespace like this:

sub import { my $caller = caller(0); $caller = "::$caller" if $caller !~ m{::}; no strict 'refs'; *{$caller} = sub { unshift @_, $caller; goto &{"$caller\::new"} }; }

As a feature, if the class name is just a single word, it installs that as a function to the main package, and it's called like this:

package Wherever; use OneWordClass; my $obj = ::OneWordClass( \%options );

Obviously, this approach has issues if the last part of the package name collides with a function name in the parent package namespace, but that may be a limitation people are willing to accept or work around.

So what do you think? Useful? Nuts? Do you have name suggestions? (Acme?)

Your thoughts are greatly appreciated.

Update:

Ouch! As grinder points out, meryln has been there before and the sticky point is that this breaks class methods without some complicated workarounds. (Mental note: reread perlop on precedence of ->.) The version I show above also happens to break inheritance, but that's easy to fix.

Perhaps an alternative might be to export a function that resembles the class name, replacing "::" with "_". That avoids all but the strangest potential collison, and doesn't interfere with normal class methods.

use Some::Other::Class; my $obj = Some_Other_Class( \%options );

For those who prefer a constructor other than new, that could be supported with a parameter during "use".

use Classname::Constructor 'create';

-xdg

Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Replies are listed 'Best First'.
Re: Getting rid of "new"
by grinder (Bishop) on Jul 07, 2006 at 08:56 UTC

    You might be interested to know that merlyn posted a snippet to do this a couple of years ago at Create a constructor named the same as your package name!. I think his solution is cleaner, but there is some debate as to whether it's a good idea in the first place :)

    I think a better argument to make is whether new is always the wisest name for a constructor. The canonical example is DBI, where the constructor is named connect. I think that design decision was a very good one.

    • another intruder with the mooring in the heart of the Perl

Re: Getting rid of "new" (wrong target; perl6)
by tye (Sage) on Jul 07, 2006 at 14:16 UTC

    Gah! You want to keep repeating "Some::Other::Class" and get rid of "new" ?? Of the two, the former is much more annoying than the latter. Module names need to be world-wide unique so they need to be fairly long and nearly self-explanatory. Which makes them lousy things to be repeating all over the place in your code. Especially since it is not uncommon for me to switch module names (either because a module got renamed or because I'm using a different module that provides similar functionality).

    I like having names for constructors. new() is often a fine name but I often use other names. For example, Win32::TieRegistry didn't even have a 'new' method at first (it had 'Open' and 'Connect'). I also prefer (in most cases) to allow $obj->new( ... ) so that I don't have to keep specifying the configuration parameters that most classes end up having that boil down to module-user preference (and so should only have to be specified once by each module user).

    So I much prefer the model of 'use' module, specifying configuration parameters, and get a factory object that you store in a variable having a nice name that use strict will tell you about mispellings of at compile time (just like use tells you about module name mispellings at compile time and unlike how Class::Name->new( ... ) doesn't tell you about mispellings at compile time).

    This is especially nice since Perl's OO implementation usually makes it trivial to provide such factories by just making a global incomplete object for the class that contains the default configuration parameters and then allowing $obj->new( ... ). Then you just make Class::Name->new( ... ) turn into $globalDefaultObject->new( ... ) and you don't have to write special code for initializing default values, just simple code to copy (the configuration) parameters from one object to another.

    Even nicer would be giving Perl the ability to have real references to modules (a.k.a. packages) and letting people tell strict.pm that symbolic references to a module/package should be a fatal error. This would have the same types of benefits that many already tout for avoiding symbolic references to variables. But even better is that it would allow multiple versions of the same module to be used by the same script (it would even allow custom implementations of the same module with the same version number, which is more than the complex Perl6 scheme would allow, last I looked) and would simplify custom installation of modules, which also makes development of new modules less frustrating for 'newbies'. I outlined this further at Re: $foo = "Foo::Bar"; $foo->new() works? (factories++).

    One inconvenience is that Perl 5 doesn't make it easy for require and use to return these factory objects. You can almost get away with my $q= require Net::CGI; but the default return value is "1" not "Net::CGI" and even if Net::CGI is improved to return a factory, it will only do so the first time it is required. But even better than require is use, because it allows you to specify your desired configuration. A chatterbox conversation with Larry lead me to believe that Perl 6 will support my $Factory= use Class::Name( ... ), and I'm even optimistic that such could be done for Perl 5. But, for now, a good approach is something like:

    use Data::Heap::Whatever 1.1 ( \my $Heap, KeepTies => 1, CompactDump = +> 1 ); # ^^^^^^^^^ Factory object gets put into +this lexical variable my $bestScores= $Heap->new( '>', -max => 10 ); my $worstScores= $Heap->new( '<', -max => 10 );

    This allows you to get rid of all class methods, which surely makes the interface thinner.

    - tye        

      Or you could just use aliased:

      use aliased 'Long::Class::Name::For::Customer'; my $customer = Customer->new; use aliased 'Worker::Class::With::A::Long::Name' => 'Worker'; my $worker = Worker->new; use aliased 'Class::Name::With::Imports::For::Munger', Munger => @impo +rt_list; my $munger = Munger->new;

      Or if you prefer lexicals:

      use aliased; my $Customer = alias 'Long::Class::Name::For::Customer'; my $customer = $Customer->new;

      This works well because the module author doesn't need to provide support for it. aliased pretty much works for any OO module.

      Cheers,
      Ovid

      New address of my CGI Course.

        I was addressing module authors more than module users. That looks like a nice module for when using modules that don't already support factories [well, the last example -- the prior ones would need to be written as Munger()->new( ... ) to meet my best practices but still wouldn't meet my preference]. Note that your module wouldn't work for a module that actually did get rid of all class methods. (:

        Oh, and that last example would need to be use aliased 'alias'; to meet my best practices. I suspect your module doesn't support that, so I doubt I'll be using it until that gets fixed. :)

        - tye        

      and I'm even optimistic that such could be done for Perl 5. But, for now, a good approach is something like:

      use Data::Heap::Whatever 1.1 ( \my $Heap

      That reference constructor there is a nit for me. Why should the user be required to provide it? There is no functional reason that it is needed. The import routine should get an alias to the whatever was passed in so I cant really see a reason for it.

      Sorry, but I've encountered this design choice a lot (DBI does it somewhere iirc) and it always kind of irritates me as it seems to me to be so unperlish.

      ---
      $world=~s/war/peace/g

        An alternative:

        use Data::Heap::Whatever 1.1 ( Factory => my $Heap, ...

        But the passing of a reference has two purposes:

        1. It lets us reliably distinguish between an option name and a variable being passed in to receive a factory, thus allowing the more stream-lined usage
        2. It makes the fact that $Heap will be modified explicit, serving as a form of documentation

        I don't have a strong preference (other than wishing use could return a factory), though.

        - tye        

      use Data::Heap::Whatever 1.1 ( \my $Heap, KeepTies => 1, CompactDump = +> 1 ); # ^^^^^^^^^ Factory object gets put into +this lexical variable my $bestScores= $Heap->new( '>', -max => 10 ); my $worstScores= $Heap->new( '<', -max => 10 );
      $Heap is scoped to the implicit BEGIN block.

        Thanks. Drat, I thought I'd used that before. So you need to declare the variable outside of the use statement (which is clearer and how I'd normally write such code anyway).

        - tye        

Re: Getting rid of "new"
by Corion (Patriarch) on Jul 07, 2006 at 08:59 UTC

    One thing why I see no great benefit in this is that I often use other constructors instead of ->new, for example constructors named ->parse, ->load or ->create - removing that information from the constructor either makes an additional parameter necessary or removes the "gain".

    Also, I often create objects through strings/factories, which is unwieldly/impossible with this approach as well:

    my $class = "My::App::Plugin::$action"; my $handler = $class->new(...);

    So the slight gain of eliminating ->new isn't worth it to me.

Re: Getting rid of "new"
by adrianh (Chancellor) on Jul 07, 2006 at 08:53 UTC
    Obviously, this approach has issues if the last part of the package name collides with a function name in the parent package namespace, but that may be a limitation people are willing to accept or work around. So what do you think? Useful? Nuts?

    I'd put it in the "nuts" category I'm afraid because of the name collision problem you pointed out :-)

    <update>oops - hit submit too early</update>

    Another technique for removing new would be to stop doing any initialisation of the object in the constructor - then you can do something like File::Find::Rule where you can do:

    File::Find::Rule->directory->in( $directory ); # rather than File::Find::Rule->new->directory->in( $directory );
Re: Getting rid of "new"
by Zaxo (Archbishop) on Jul 07, 2006 at 20:17 UTC

    I proposed another solution to get a "silent" constructor in my Dog $spot;. It involves merely naming the constructor MODIFY_SCALAR_ATTRIBUTES(). It almost works, but some dummy attribute must be cited for the constructor to be called,

    use Some::Other::Class; my Some::Other::Class $obj :dummy;
    MODIFY_SCALAR_ATTRIBUTES() could be written to take the constructor arguments, %options, from the attribute list, but that feels kind of dirty to me.

    After Compline,
    Zaxo

Re: Getting rid of "new", Implied actions are bad
by cerelib (Novice) on Jul 07, 2006 at 21:41 UTC
    The "new" convention seems logical and the same goes for "spawn" or "create". The problem with your idea is that it lacks a verb. I will grant you that "new" it not an English verb, but it is a Perl verb. Without a verb, your statement "Some::Other::Class( \%options )" has no visible action although one is being performed. For that reason alone it goes against many design decisions of Perl itself. In Perl there are visual cues to help you understand what you are seeing. A scalar looks like a scalar '$', an array looks like an array '@', and so on. To this extent an action looks like an action in the direct form 'Class->new', the indirect 'new Class', or with the implied subject 'new' (this is the most deceptive of the three). That last one may look like it justifies your idea, but what you want is an explicit subject with an implied action. So, I think the implied constructor is a bad decision. It will lead to more confusing code. If you really hate "new", get with the people responsible for Class::Struct and talk to them about changing naming conventions.
      Without a verb, your statement "Some::Other::Class( \%options )" has no visible action although one is being performed

      And heaven help the poor fool who has to maintain it when it becomes this:

      my $foo = Some::Other::Class( code => sub { DoThingsWith( $_[0] ) } );
      In the previously mentioned absence of a verb, someone is definitely going to think the action comes from DoThingsWith()....

      But I'd be the last person in the world to discourage a Very Cool, if Quite Tricky Idea. It's cleverness personified, and I don't care what The Damian says...cleverness has a place in the world!

      Put it in Acme please, so nobody I code with uses it :)

      ~Jeff

Re: Getting rid of "new"
by davidrw (Prior) on Jul 07, 2006 at 14:00 UTC
    hmm.. maybe a (insert standard caveats here) source filter to replace Some::Foo::Class( ... ) w/Some::Foo::Class->new( ... ) ? One immediate problem is My::Class::a_function( ... ) -- it would have to be table to be able to distinguish that case ..
    This approach probably wouldn't be a production-worthy endeavor, but possibly an interesting/fun excercise..
Re: Getting rid of "new"
by Khen1950fx (Canon) on Jul 07, 2006 at 09:41 UTC
    Hey, I like it! Useful? Yes. Nuts? Of course! But History is littered with great men who happened to be nuts:-) I'd make it an Acme.
Re: Getting rid of "new"
by spiritway (Vicar) on Jul 10, 2006 at 01:19 UTC

    Maybe I'm still stuck in C++ mode, but I like 'new' - it lets me know what's happening, kind of sticks out a bit. True, it can be avoided - but is that a Good Thing? Sometimes I like a bit of redundancy, if it makes clear what's going on. Typing a few extra characters is a small price to pay for clarity, IMNSHO.

Re: Getting rid of "new"
by ruoso (Curate) on Jul 10, 2006 at 17:35 UTC

    If we're talking about nuts, well... here's a weird faulty working version... :)

    package a::b; use strict; sub a::b { my @l = caller(0); open my $f, "<", $l[1]; my $line; for (0..$l[2] - 1) { $line = <$f>; } my $pack = $l[3]; if (!@_ && $line =~ /$pack\->/) { return $pack; } else { return $pack->new(@_); } } sub new { print "new!!! ".join(", ",@_)."\n"; } sub c { print "a::b->c()!!!! ".join(", ",@_)."\n"; } package main; a::b; a::b('a'); a::b->c; a::b->c('a');

    UPDATE: use of __DATA__ substituted by opening the file itself...

    daniel

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (2)
As of 2024-04-20 05:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found