Search code examples
perloophashconstructorhashref

Unless constructor argument passed is a hash type, croak on invalid arguments?


I am vaguely confused a bit on different methods of passing certain arguments to the constructor type. I want to only pass a hash reference \%hash, or a list foo => 1, bar => 1 but not both and croak if anything else is passed i.e ( single elements, array reference ).

For example, I pass my reference or list.. (This works for the way I do this)

my $obj = foo->new;
my $data = $obj->dump( \%hash );
my $data = $obj->dump( foo => 1, bar => 1 );

or

my $obj = foo->dump( \%hash );
my $obj = foo->dump( foo => 1, bar => 1 );

Package module:

package foo;

use strict;
use Carp;

use Scalar::Util qw/reftype/;

sub new { return bless {}, shift }

sub dump {
   my $class = shift;
   my $self  = shift;
   unless ( reftype( $self ) eq reftype {} ) {
         croak("Constructor method not a hash type!");
   }
}

1;

I've also thought about using the conditional operator ? : here, but I can't get it to error properly.

my $self  = reftype($_[0]) eq reftype {} ? shift : {@_};

Is there a better preferred way to do this?


Solution

  • We can look at the various ways your dump method can be called.

    If we pass a "hash list", the number of elements is even (@_ % 2 == 0). Also, if at least one key-value pair is present, the first argument (a key) is a string, so not defined reftype $_[0] holds.

    If we pass a hash reference, then the argument list should only hold this reference, and no other values: @_ == 1. The first argument will be a hash: reftype($_[0]) eq 'HASH'.

    So to put the arguments in a hash reference, one could do something like:

    sub dump {
      my $invocant = shift;
      my $hashref;
      if (@_ == 1 and reftype $_[0] eq 'HASH') {
        $hashref = $_[0];
      } elsif (@_ % 2 == 0 and (@_ == 0 or not defined reftype $_[0])) {
        $hashref = +{ @_ };
      } else {
        croak "Unknown argument format: either pass a hashref, or an even-valued list";
      }
      ...; # do something with $hashref
    }
    

    To find out if the $invocant is the class name or an object, just ask it if it is blessed:

    if (defined Scalar::Util::blessed $invocant) {
      say "Yep, it is an object";
    } else {
      say "Nope, it is a package name";
    }