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

Hi, I want to write a function (not a method) that accepts either a hash/array, or a hashref/arrayref as its sole argument. Is this possible to implement? Using prototypes I can say
sub myfunc (\[$@%]) {}
and this will work for in the following cases:
myfunc %hash; myfunc @array; myfunc $hashref; myfunc $arrayref;
but will not work for expressions that evaluate to hasrefs/arrayrefs, such as:
myfunc \%hash; myfunc \@array; myfunc { key => 'value', key => 'value'}; myfunc [ 'value', 'value' ];
Is there some way to have the best of both worlds and have both styles work? I was thinking about multiple dispatch or somehow wrapping myfunc in eval so that prototypes do not blow up, but I have no clear idea really how to go about it.

Replies are listed 'Best First'.
Re: Function that accepts both hashes/arrays and hashrefs/arreyrefs
by SuicideJunkie (Vicar) on Jun 19, 2009 at 14:30 UTC
    You can test to see if a value is a reference using ref (eg: if (ref($something) eq 'HASH').

    Provided that the array or hash contents being passed into this function are not allowed to be array or hash references themselves, this will work.
    Trying to distinguish between an array reference and an array (list) of array references will be a challenge. Particularly a list of size 1.
Re: Function that accepts both hashes/arrays and hashrefs/arreyrefs
by rovf (Priest) on Jun 19, 2009 at 14:31 UTC

    Aside from the fact that the idea of "accepting an array ... as its *sole* argument" is a bit contradictory by itself (unless the array is of length one), and that I don't see how you could possibly distinguish between someone passing you a array, and someone passing you a hash, the simple declaration of

    sub myfunc { .... }
    would accept everything.

    -- 
    Ronald Fischer <ynnor@mm.st>
Re: Function that accepts both hashes/arrays and hashrefs/arreyrefs
by pileofrogs (Priest) on Jun 19, 2009 at 17:01 UTC

    Sometimes I'll write a function that accepts either a hash, hashref or single scalar. The hash and hashref are for the long-form  foo( name => 'bar', hat => 'funny') and  foo( { name => 'baz', hat => 'hard' } ) forms and the single scalar is for the short lazy form  foo('antelope').

    I check if the length of @_ is greater than 1, I treat it as a hash. If the length of @_ is 1, I use ref to determine if it's a hash ref or a scalar.

    Notice I'm not trying to handle arrays and array refs. While you can tell the difference between an array ref and a hash ref, you can't really tell the difference between an array and a hash, unless you have other weird constrains, like your arrays always have an odd number of elements or your hashes allways have the same keys etc... etc...

    So, to sum up, unless you know how to tell the difference between a hash and an array in your context, you can't get there from here.

    --Pileofrogs

Re: Function that accepts both hashes/arrays and hashrefs/arreyrefs
by dsheroh (Monsignor) on Jun 19, 2009 at 16:46 UTC
    First off, if you're asking a question like this, don't use prototypes. They're not required in Perl and they probably don't do what you're expecting - a Perl prototype isn't the same thing as a C prototype. See the example at the end of the perlsub section on prototypes and/or The purpose of prototypes in perl subroutines for more details.

    Now, on to your actual question...

    You can use ref to test whether your first argument is a scalar, a hashref, or an array ref to get some multi-style support, but, if the first argument is a scalar, you have no way to determine whether it's the first element of an array or the first key in a hash, since arrays and hashes are both passed in as plain lists. (I suppose you could test the size of @_, which would tell you it's an array if there are an odd number of elements, but you'd still just be taking a shot in the dark if it's even.)

Re: Function that accepts both hashes/arrays and hashrefs/arreyrefs
by ikegami (Patriarch) on Jun 19, 2009 at 14:57 UTC

    You want Perl to sometimes take a reference, sometimes not, without tell it when. There's a problem with that.

Re: Function that accepts both hashes/arrays and hashrefs/arreyrefs
by jandrew (Chaplain) on Jun 19, 2009 at 16:45 UTC
    As mentioned by others the risk of sending an array or a hash to a function is risky with lots of unexpected things possible see: perlsub. However if you will limit yourself to only array refs or hash refs then the real question is how to disinguish which one was sent and what to do with it. I like;

    my $Input = $ArrayRef || $HashRef; set_value( $Input ); sub set_value { #recieve the passed variable(s) #my $self = shift;#For OO subs my $Arg = shift; if ( ref $Arg eq 'HASH' ) { ### Handle a hash $Arg->{key} } elsif ( ref $Arg eq 'ARRAY' ) { ### Handle an array $Arg->[$index] } else { ### Must not be an array ref or hash ref } }
    Updated code block
    Cleaner syntax, Thank you Porculus

      That's not a great way to do it; your code will, for example, think it's got an array reference if someone passes the string "This is NOT an array(really!)" as an argument.

      A better approach is to use the ref function.

Re: Function that accepts both hashes/arrays and hashrefs/arreyrefs
by Zarchne (Novice) on Jun 20, 2009 at 04:56 UTC
    If you're trying to catch argument errors as soon as possible, the simple solution might be to use a prototype of ($), which accepts all but the first two cases and rejects calls that obviously contain other than one argument, and then require users to backslash their hash or array. It also makes myfunc a little simpler because anything that isn't a HASH or ARRAY ref is an error. I don't think the current perl prototype system is flexible enough to do what you want; it basically just hints to the parser what it should be looking for and allows emulation of (most of) the builtin functions.