http://www.perlmonks.org?node_id=469097

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

I am trying to figure out if what I am trying to do is possible, obvious (to people != /me) or just "not the Perl way". Here's the way the I wanted to define a sub:

sub functionname($string_param, @array_param, $other_string_param) { # do something with parameters, return values }
I got this structure from doing things in JavaScript or Python, but it makes sense to me - more sense than calling on anonymous bits of @_. I've looked at prototypes, but those don't (a) make sense to me and (b) don't seem to be quite what I am looking for.

I want to do this because I want to have warnings thrown up when a sub is called with the wrong kind of input, and I want sensible parameter names in my sub blocks. This may be at odds with the "arbitrarily large flattened list" parameter structure that they talk about in the Camel book.

Replies are listed 'Best First'.
Re: Predefining sub Parameters
by diotalevi (Canon) on Jun 22, 2005 at 17:25 UTC

    This is built in to perl6. In perl5, use Params::Validate. The one thing you'll notice has really changed is that @array_param is now passed as a reference. You can't just say func( "...", @ary, ... ) and have @ary passed as a distinct value without doing some really nasty things. That is, prototypes. You really, really don't want to use them in perl5. They were a mistake to have included (except maybe the & prototype) in perl at all.

    functionname( "...", \ @bar, "..." ); use Params::Validate ':all'; sub functionname { my ( $string_param, $array_param, $other_string_param ) = validate_pos( @_, { type => SCALAR }, { type => ARRAYREF }, { type => SCALAR } ); }

      Drat - I was hoping I was missing something. Oh well, they tell me that doing things by hand makes you meticulous and careful, and gives you a richer understanding of the problem. I just thought it made me tired ;-)

      Is there a reason why the structure I was attempting to use wasn't built into Perl before now? Are there advantages that elude my understanding?

        Actually, I find that validating my parms is actually less useful in perl than it was in other languages. That's largely because perl is soooo much better at DWIMmery than other languages.

        For example, validating that the object you just got is a file handle is a complete waste of time. The object may be a glob reference, may be an IO::Handle (or derived) object, or may be something else tie'd to work like a file handle. Don't worry about it - just use it for reading or writing (as appropriate), and things work out great. If a wrong parm is passed in, you'll see that quickly enough ;-).

        For another example, validating the the object you just got is a scalar is another waste of time. What if it's an object that has all the proper overloads so I can do some really funky stuff with it? You'll never believe what someone who is more skilled than you can get out of your module - don't stop them.

        OTOH, validating user input from, say, the web, is of paramount importance. You're letting some arbitrary user who may be malicious use your computing resources, so you should be careful that they can't misuse them. Depends on what you're validating. In your case, it looks like the former, so you may be pleasantly surprised when suddenly the code you've been using for weeks or months in a certain way suddenly works wonderfully in another way.

Re: Predefining sub Parameters
by dynamo (Chaplain) on Jun 22, 2005 at 17:25 UTC
    Well, you're going to have to change that array in the middle. You can either make it a reference, or move it to the end. Also, you have to read in the parameters yourself, it's not quite as automatic as C or Java in that sense.

    So by the first option, it should be:

    sub functionname { my ($string_param, $array_param_ref, $other_string_param) = @_; my @array_param = @$array_param_ref; # do something with parameters, return values }
    That code would be called like: functionname("string",['array_item1', $arrayItem2, '...'], "other_string_param");

    Or, you could put it at the end:

    sub functionname{ my ($string_param, $other_string_param, @array_param) = @_; # do something with parameters, return values }
    where it'll slurp up all remaining args. And that code would be called like: functionname("string", "other_string_param", 'array_item1', $arrayItem2, '...');

    Hope that helps.

Re: Predefining sub Parameters
by mrborisguy (Hermit) on Jun 22, 2005 at 18:25 UTC

    I hesitate to mention this, but in this particular example (certainly not every example - and this probably isn't an advisable way to do it), the sub wouldn't need to have an arrayref passed, technically.

    sub somesub { my $first = shift; my @second = @_; my $third = pop @second; }

    Granted, this doesn't help the OP with his problem, but a couple of people mentioned that it wasn't possible to have a flattened array in the middle of the arguments, when in a crazy way, it is possible.

        -Bryan

      Very interesting approach :-) It makes sense in the hacker tradition of "assume a situtation is the way it is - then subvert it with cleverness". ++

Re: Predefining sub Parameters
by jimbojones (Friar) on Jun 22, 2005 at 17:28 UTC
    Hi

    You're kinda stuck with the fact that all parameters to your sub are going to be passed in through @_. You can make the assignment the first thing you do in the sub, either by shifting or assigning.
    sub functionname { my $string_param = shift; my $other_string_param = shift; my @array_param = @_ ; #-- the remnant after the first two are assign +ed #-- do edit check here, throw error #-- do rest of sub function }
    or all at once
    sub functionname { my ( $string_param, $other_string_param, @array_param ) = @_; #-- do edit check here, throw error #-- do rest of sub function }
    note that if you want to pass in a mix of strings and an array, the array has to come last, as everything gets 'flattened' onto @_. If you want to pass in two or more arrays, or an array in the middle of your list, you need to use a reference.
    sub functionname { my $string_param = shift; my $array_ref = shift; my @array = @{$array_ref}; my $other_string_param = shift; #-- do edit check here, throw error #-- do rest of sub function } #-- call as: sub functionname( $string_param, \@array_param, $other_string_param);
    Finally, stay away from sub prototypes. Search the monastery for all the reasons why.

    - j

Re: Predefining sub Parameters
by moot (Chaplain) on Jun 22, 2005 at 17:29 UTC
    If you want to provide a compile-time check that parameters passed to a function are of the correct type, use a prototype:
    sub functionname($@$) { ... }
    The problem with this example is that perl flattens the @ and second $ parameter into one list, so your function wouldn't know where the second parameter (the @) ends.

    There are some techniques in the cookbook and other literature that you might want to investigate, such as mimicking behaviour of built-ins with function prototypes. Some of these techniques can be used to alleviate the problem I've identified above.

    You may also think about having your function accept only a hash (actually, a list of arguments your function will treat as a hash), and requiring callers to use named parameters:

    sub functionname { my %hash = @_; # Validate keys of hash here } functionname(param1 => 1, param2 => 2, ...);
    Did I mention There's More Than One Way To Do It? ;)

    Now hiring in Atlanta. /msg moot for details.
Re: Predefining sub Parameters
by QM (Parson) on Jun 22, 2005 at 20:25 UTC

      Useful references, all. The Best Practices book looks like it'll be a winner. The problem for me is that I'll only be able to understand the first third of any given chapter, if the beta chapter is any indication. Oh well, I bought the Camel book years ago, and most of it still makes my head spin :-)

        The problem for me is that I'll only be able to understand the first third of any given chapter, if the beta chapter is any indication.
        Perl is very accommodating. If you only need 10%, that will work. If you don't need it, it generally isn't required.

        -QM
        --
        Quantum Mechanics: The dreams stuff is made of

Re: Predefining sub Parameters
by bageler (Hermit) on Jun 22, 2005 at 17:31 UTC
    of course, you can't have an array as a second parameter if you expect scalar params later in the list, so you'd want to pass an arrayref to line things up like that.
    You might check for appropriate values like this:
    sub functionname { $a = shift; $b = shift; $c = shift; warn unless (ref \$a =~ /SCALAR/); warn unless (ref $b =~ /ARRAY/); warn unless (ref \$c =~ /SCALAR/); }
    Or you might pass your data as a hash, and check that the appropriate keys are there.
Re: Predefining sub Parameters
by techcode (Hermit) on Jun 22, 2005 at 23:41 UTC
    While most of people gave you various (and very good) responses. I'd like to comment on this.

    I really like this way it's done in Perl. Sure it takes a bit of time to adjust to it - but once you do that. It comes somehow naturally.

    Also - once I discovered how hashes are great (before Perl I only used C on university, and some Basic variants like M$ VB and old Amiga AMOS on my own) I started almost just using them :)

    They are so great because many of the modules use them, and at the end of the day, my jobs ends up in passing references from one module to other :)

    In your case (actually in almost any case) I would probably use something like:

    sub_call ({ first => $first, second => \@second, third => $third, }); # ... sub sub_call { my $hash_ref = shift; # $hash_ref->{first} holds original $first # $hash_ref->{second} holds reference to original @second # $hash_ref->{third} holds original $third # NOTICE - just like passing pointers in C. # When you modify the array that array_ref points to (to @second) # you are changing the original. Might not be what you want. # In that case, pass @second, not \@second to get a "local copy" }
    Or other similar option: < EDIT: As sugested a little earlier >
    sub_call ( first => $first, second => \@second, third => $third, ); sub sub_call { my %hash = @_; # $hash{first}, $hash{second}, $hash{third} hold values like above }

    Good thing about this, is that when you write the sub, you don't need to set the local variable for each sent parameter. The order of parameters also isn't important, but caller needs to know exact names that you use through the sub. IMHO I just like this way better.

    While you can use any of the above, first one (passing reference) should be better in case you have many variables (in this case tree, since we pass array_ref). I would suggest you actually use just it.
    So you wouldn't come into a doubt which one did you used for particular sub. And be constant in it.
    Maybe someone wouldnt agree with all this?

    I also noticed that many CPAN modules use this for configuration type method calls.

    PS. This way you predefine the names of the variables but not their placing in sub call :)
Re: Predefining sub Parameters
by duct_tape (Hermit) on Jun 22, 2005 at 21:20 UTC

    Others have given you good answers, but I haven't seen anyone mention these modules yet. Sub::Signatures and Perl6::Subs allow you to define your subs in the way that you want by using source filters. I haven't used them yet, but I thought i'd mention them.

Re: Predefining sub Parameters
by tphyahoo (Vicar) on Jun 23, 2005 at 08:49 UTC
    You might also try Getargs::Long. Autarch, creator of Params::Validate mentions this as a module that does something similar, but with a different interface, in the "see also" section of the documentation for Params::Validate. Haven't used either yet, but I want to start doing this as well, and would be interested in hearing which of these two modules the monks prefer. My impression is that P::V seems to have more of a following.

    Autarch discusses the lay of the "data validation" land in Why reinvent the wheel?, in the Data Validation Tests thread. He also mentions Class::ParamParser and Class::ParmList there, maybe these are worth having a look at as well for when you're doing things object oriented.