Object-Oriented Perl constructor syntax and named

2020-02-02 06:20发布

问题:

I'm a little confused about what is going on in Perl constructors. I found these two examples perldoc perlbot.

package Foo;

#In Perl, the constructor is just a subroutine called new.
sub new {
  #I don't get what this line does at all, but I always see it. Do I need it?
  my $type = shift;

  #I'm turning the array of inputs into a hash, called parameters.
  my %params = @_;

  #I'm making a new hash called $self to store my instance variables?
  my $self = {};

  #I'm adding two values to the instance variables called "High" and "Low".
  #But I'm not sure how $params{'High'} has any meaning, since it was an
  #array, and I turned it into a hash.
  $self->{'High'} = $params{'High'};
  $self->{'Low'} = $params{'Low'};

  #Even though I read the page on [bless][2], I still don't get what it does.
  bless $self, $type;
}

And another example is:

package Bar;

sub new {
  my $type = shift;

  #I still don't see how I can just turn an array into a hash and expect things
  #to work out for me.
  my %params = @_;
  my $self = [];

  #Exactly where did params{'Left'} and params{'Right'} come from?
  $self->[0] = $params{'Left'};
  $self->[1] = $params{'Right'};

  #and again with the bless.
  bless $self, $type;
}

And here is the script that uses these objects:

package main;

$a = Foo->new( 'High' => 42, 'Low' => 11 );
print "High=$a->{'High'}\n";
print "Low=$a->{'Low'}\n";

$b = Bar->new( 'Left' => 78, 'Right' => 40 );
print "Left=$b->[0]\n";
print "Right=$b->[1]\n";

I've injected the questions/confusion that I've been having into the code as comments.

回答1:

To answer the main thrust of your question, since a hash can be initialized as a list of key => value pairs, you can send such a list to a function and then assign @_ to a hash. This is the standard way of doing named parameters in Perl.

For example,

sub foo { 
    my %stuff = @_;
    ...
}

foo( beer => 'good', vodka => 'great' );

This will result in %stuff in subroutine foo having a hash with two keys, beer and vodka, and the corresponding values.

Now, in OO Perl, there's some additional wrinkles. Whenever you use the arrow (->) operator to call a method, whatever was on the left side of the arrow is stuck onto the beginning of the @_ array.

So if you say Foo->new( 1, 2, 3 );

Then inside your constructor, @_ will look like this: ( 'Foo', 1, 2, 3 ).

So we use shift, which without an argument operates on @_ implicitly, to get that first item out of @_, and assign it to $type. After that, @_ has just our name/value pairs left, and we can assign it directly to a hash for convenience.

We then use that $type value for bless. All bless does is take a reference (in your first example a hash ref) and say "this reference is associated with a particular package." Alakazzam, you have an object.

Remember that $type contains the string 'Foo', which is the name of our package. If you don't specify a second argument to bless, it will use the name of the current package, which will also work in this example but will not work for inherited constructors.



回答2:

.1. In Perl, the constructor is just a subroutine called new.

Yes, by convention new is a constructor. It may also perform initialization or not. new should return an object on success or throw an exception (die/croak) if an error has occurred that prevents object creation.

You can name your constructor anything you like, have as many constructors as you like, and even build bless objects into any name space you desire (not that this is a good idea).

.2. I don't get what my $type = shift; does at all, but I always see it. Do I need it?

shift with no arguments takes an argument off the head of @_ and assigns it to $type. The -> operator passes the invocant (left hand side) as the first argument to the subroutine. So this line gets the class name from the argument list. And, yes, you do need it.

.3. How does an array of inputs become the %params hash? my %params = @_;

Assignment into a hash is done in list context, with pairs of list items being grouped into as key/value pairs. So %foo = 1, 2, 3, 4;, creates a hash such that $foo{1} == 2 and $foo{3} == 4. This is typically done to create named parameters for a subroutine. If the sub is passed an odd number of arguments, an warning will be generated if warnings are enabled.

.4. What does 'my $self = {};` do?

This line creates an anonymous hash reference and assigns it to the lexically scoped variable $self. The hash reference will store the data for the object. Typically, the keys in the hash have a one-to-one mapping to the object attributes. So if class Foo has attributes 'size' and 'color', if you inspect the contents of a Foo object, you will see something like $foo = { size => 'm', color => 'black' };.

.5. Given $self->{'High'} = $params{'High'}; where does $params{'High'} come from?

This code relies on the arguments passed to new. If new was called like Foo->new( High => 46 ), then the hash created as per question 3 will have a value for the key High (46). In this case it is equivalent to saying $self->{High} = 46. But if the method is called like Foo->new() then no value will be available, and we have $self->{High} = undef.

.6. What does bless do?

bless takes a reference and associates with a particular package, so that you can use it to make method calls. With one argument, the reference is assoicated with the current package. With two arguments, the second argument specifies the package to associate the reference with. It is best to always use the two argument form, so that your constructors can be inherited by a sub class and still function properly.

Finally, I'll rewrite your hash based object accessor as I would write it using classical OO Perl.

package Foo;

use strict;
use warnings;
use Carp qw(croak);

sub new {
    my $class = shift;

    croak "Illegal parameter list has odd number of values" 
        if @_ % 2;

    my %params = @_;

    my $self = {};
    bless $self, $class;

    # This could be abstracted out into a method call if you 
    # expect to need to override this check.
    for my $required (qw{ name rank serial_number  });
        croak "Required parameter '$required' not passed to '$class' constructor"
            unless exists $params{$required};  
    }

    # initialize all attributes by passing arguments to accessor methods.
    for my $attrib ( keys %params ) {

        croak "Invalid parameter '$attrib' passed to '$class' constructor"
            unless $self->can( $attrib );

        $self->$attrib( $params{$attrib} );
    }

    return $self;
}


回答3:

Your question is not about OO Perl. You are confused about data structures.

A hash can be initialized using a list or array:

my @x = ('High' => 42, 'Low' => 11);
my %h = @x;

use Data::Dumper;
print Dumper \%h;
$VAR1 = {
          'Low' => 11,
          'High' => 42
        };

When you invoke a method on a blessed reference, the reference is prepended to the argument list the method receives:

#!/usr/bin/perl

package My::Mod;

use strict;
use warnings;

use Data::Dumper;
$Data::Dumper::Indent = 0;

sub new { bless [] => shift }

sub frobnicate { Dumper(\@_) }

package main;

use strict;
use warnings;

my $x = My::Mod->new;

# invoke instance method
print $x->frobnicate('High' => 42, 'Low' => 11);

# invoke class method
print My::Mod->frobnicate('High' => 42, 'Low' => 11);

# call sub frobnicate in package My::Mod
print My::Mod::frobnicate('High' => 42, 'Low' => 11);

Output:

$VAR1 = [bless( [], 'My::Mod' ),'High',42,'Low',11];
$VAR1 = ['My::Mod','High',42,'Low',11];
$VAR1 = ['High',42,'Low',11];


回答4:

Some points that haven't been dealt with yet:

In Perl, the constructor is just a subroutine called new.

Not quite. Calling the constructor new is just a convention. You can call it anything you like. There is nothing special about that name from perl's point of view.

bless $self, $type;

Both of your examples don't return the result of bless explicitly. I hope that you know that they do so implicitly anyway.



回答5:

If you assign an array to a hash, perl treats alternating elements in the array as keys and values. Your array is look at like

my @array = (key1, val1, key2, val2, key3, val3, ...);

When you assign that to %hash, you get

my %hash = @array;
# %hash = ( key1 => val1, key2 => val2, key3 => val3, ...);

Which is another way of saying that in perl list/hash construction syntax, "," and "=>" mean the same thing.



回答6:

In Perl, all arguments to subroutines are passed via the predefined array @_.

The shift removes and returns the first item from the @_ array. In Perl OO, this is the method invocant -- typically a class name for constructors and an object for other methods.

Hashes flatten to and can be initialized by lists. It's a common trick to emulate named arguments to subroutines. e.g.

Employee->new(name => 'Fred Flintstone', occupation => 'quarry worker');

Ignoring the class name (which is shifted off) the odd elements become hash keys and the even elements become the corresponding values.

The my $self = {} creates a new hash reference to hold the instance data. The bless function is what turns the normal hash reference $self into an object. All it does is add some metadata that identifies the reference as belonging to the class.



回答7:

Yes, I know that I'm being a bit of a necromancer here, but...

While all of these answers are excellent, I thought I'd mention Moose. Moose makes constructors easy (package Foo;use Moose; automatically provides a constructor called new (although the name "new" can be overridden if you'd like)) but doesn't take away any configurability if you need it.

Once I looked through the documentation for Moose (which is pretty good overall, and there are a lot more tutorial snippets around if you google appropriately), I never looked back.