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

I notice that it's popular enough to pass parameters to a method as a hash which gets converted into the usual @_ but which then gets coerced back into a hash inside the method and this seems to be what perlbot is telling us to do.

But that doesn't work if the hash has to be more than one level deep. So if I want my module to be done the 'right way' (tm), is there any reason why I can't require non-oo users of the module to pass a hash reference instead of a hash?

  • Comment on enabling OO and non-OO access to the same module using a hash reference

Replies are listed 'Best First'.
Re: enabling OO and non-OO access to the same module using a hash reference
by liverpole (Monsignor) on Jan 15, 2007 at 17:10 UTC
    Hi Moron,

    Here's an interesting experiment (well, I think it's interesting, as I wasn't quite sure how it would turn out):

    #!/usr/bin/perl -w use strict; use warnings; use Data::Dumper; my %hash = ( 'a' => 'b', 'c' => 'd', 'e' => [ 'F', 'G', 'H' ], 'i' => { 'J' => 'K', 'L' => 'M', }, ); as_hash(%hash); as_array(%hash); sub as_hash { my (%hash) = @_; printf "Dump of hash as a hash => %s\n", Dumper(\%hash); my $hash_i_J = $hash{'i'}{'J'}; printf "hash{i}{J} = '%s'\n", $hash_i_J; } sub as_array { my (@array) = @_; print "\nEach element of \@array:\n"; map { print "- $_\n" } @array; printf "\nDump of hash as an array => %s\n", Dumper(\@array); as_hash(@array); }

    Which outputs:

    Dump of hash as a hash => $VAR1 = { 'e' => [ 'F', 'G', 'H' ], 'c' => 'd', 'a' => 'b', 'i' => { 'J' => 'K', 'L' => 'M' } }; hash{i}{J} = 'K' Each element of @array: - e - ARRAY(0x804c8d4) - c - d - a - b - i - HASH(0x806024c) Dump of hash as an array => $VAR1 = [ 'e', [ 'F', 'G', 'H' ], 'c', 'd', 'a', 'b', 'i', { 'J' => 'K', 'L' => 'M' } ]; Dump of hash as a hash => $VAR1 = { 'e' => [ 'F', 'G', 'H' ], 'c' => 'd', 'a' => 'b', 'i' => { 'J' => 'K', 'L' => 'M' } }; hash{i}{J} = 'K'

    I wanted to see what happened when the hash got interpreted as an array, and then back into a hash again.  No problem, you can treat it as an array, then treat that array as a hash, and it's all still there in the same original format (as witnessed both by dumping the structure with Data::Dumper, and by accessing $hash{i}{J}).

    So when you say that doesn't work if the hash has to be more than one level deep, what part do you think isn't working?


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

      Hi, I was not surprised:) perl passed a flat list of "aliases" so it does not matter if the list comes from hash or array. Still "my assignment" just creates a shallow copy, so one should be aware of it. In a context of options (which are treated read-only) this should not be problematic

      hth --stephan
Re: enabling OO and non-OO access to the same module using a hash reference
by Tanktalus (Canon) on Jan 15, 2007 at 17:04 UTC

    In what way does it not work for multi-layers?

    Module::foo( blah => 'zap', baz => [ 4, 12, 15, 'Fibonacci' ], froop => { thwap => 'funny' }, );
    should work just fine when coerced from @_ to %args. The only thing to watch out for is if the same sub can be used as an object method, then you'll want to check that first:
    my $self = undef; if (ref $_[0] and UNIVERSAL::isa($_[0], __PACKAGE__)) { $self = shift; } my %args = @_;
    (untested)

    Now, granted, a hashref can be faster than this, but that wasn't your question.

      You've just broken polymorphism with your use of ref() and UNIVERSAL::isa(). The correct incantation follows.

      defined( blessed( $_[0] ) ) and $_[0]->isa( __PACKAGE__ )

      ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: enabling OO and non-OO access to the same module using a hash reference
by ikegami (Patriarch) on Jan 15, 2007 at 17:07 UTC
    Lots of modules have functions that accept hash references. For example, options are passed to DBI functions through a hash ref. CGI goes a step further and give its users the option of using a hash ref or a list of pairs to pass arguments to its methods.

    Update: I forgot to mention that CGI's functions can be called both as a methods and as functions. From the thread title, that sounds like what you want to do. Check it out!

      Thanks Ikegami - it makes me more comfortable about going the href route to know that as popular a module as CGI went there already.

      -M

      Free your mind

Re: enabling OO and non-OO access to the same module using a hash reference
by perrin (Chancellor) on Jan 15, 2007 at 17:04 UTC
    No, there is no reason. And it doesn't have much to do with OO, in my opinion. Sometimes you need a complex data structure as input and sometimes you don't.
Re: enabling OO and non-OO access to the same module using a hash reference
by Moron (Curate) on Jan 15, 2007 at 17:33 UTC
    I suppose my first thought was that foo( bar => { hoo => hah } ) would be 3 parameters (which would not coerce back) when I now realise it is 2, with the second being a hash reference to the (anonymous) nested hash.

    But the problem I am actually having with providing support for a coerced hash (or even an ordered list of parameters for that matter) is this:

    my $oref = OtherModule -> new(); Package::method( $oref, 'gotcha' );
    Just to make it clearer we'll say OtherModule is a subclass of Package. So the problem is that testing for OO-access in Package::method will go false-positive if the above non-oo construction is used. Requiring all non-OO users to use a hash reference for their parameters makes it (a) easier to test access method and (b) code applying to the object reference that normally arrives will work compatibly at once if a hash reference is passed non-oo intead. So this is why I am hoping I can go that route. For example, perlbot even suggests testing ref($_[0]) as an alternative to the forbidden (ref($_[0]) eq 'ClassName'). But that would prevent some trivial methods from having say a file handle as parameter owing to the same false-positive result I am trying to avoid. And I don;t want to have to test all possible references that aren't to some class! (update: because a class is not allowed to make assumptions about subclasses so that would be what had to be tested for a completely generic test for oo-ness)

    -M

    Free your mind

      ref($_[0]) works if you use named parameters, since the first arg is either an object or a parameter name.
Re: enabling OO and non-OO access to the same module using a hash reference
by Moron (Curate) on Jan 18, 2007 at 10:23 UTC
    Thanks for all suggestions, especially from Ikegami which led me to write the code under. There is also the possibility of using builtin caller(), but supporting these four ways to call seems more than enough flexibility and there is also the consideration of performance given that the particular method I am dealing with recurses millions of times for large inputs.
    sub method { my $self = $_[0]; # leave it alone for obj/hashref unless( ref( $self ) || !$#_ ) { # else is immed. hash or # list of pairs my %opt = @_; $self = \%opt; } # now $self can be processed as a hashref whichever # of the four calling styles was used }

    -M

    Free your mind