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


in reply to Re: Context aware functions - best practices?
in thread Context aware functions - best practices?

I haven't yet some across a case where I've felt a special desire to pass more arguments to the function than the function will use.

Funny. There a number of situations where ive taken advantage of this very feature. And frankly would have been annoyed if the code warned when I did so. An example might be a situation where we extract the contents of a line via a regex, and then process that line via a dispatch function:

my ($meth,@parts)=/.../; exists($dispatch{$meth}) and $disptach{$meth}->(@parts);
or something like it. Another example would be overriding a method in a class that takes a certain number of parameters with one that takes a few more. When calling the overriden sub its fairly natural say something like
sub blah { $foo->SUPER::blah(@_); ... }
So IMO warning when someone passes too many parameters seems a little extreme. If you do that then I at least hope that you
use warnings::register;
so that I can turn off the annoying messages instead of having to write tortured code just so your sub gets the correct number of parameters. I have come across a similar annoyance when people use prototypes (usually for the wrong reasons, such as argument count checking).

I'm especially paranoid against other programmers,

Im sympathetic to this postion, but I think its a fine line between being paranoid, and being unduely restrictive. If the end user wants to give you more arguments then you need, then theres a decent chance that decision was a sound one. At a certain point you have to let the end user take responsibility for their own code, and thus their own (mis)use of your code. I certainly disagree with code that makes it impossible to do something just because the author couldnt see any reason why anyone would want to do it. (A position that you arent necessarily advocating I realize, but you can see the relevance I hope.) A good example is code like this:

sub foo { my $array_ref=shift; ref($array_ref) ne "ARRAY" and die "Can't use anything but an array"; ... }
Cheers,

--- demerphq
my friends call me, usually because I'm late....

Replies are listed 'Best First'.
Re: Re: Re: Context aware functions - best practices?
by ihb (Deacon) on Jan 20, 2003 at 12:41 UTC
    First of all I want to point out that my reply to which demerphq replied should be read in its right context: as a reply to a question about wantarray and proper usage.

    Funny. There a number of situations where ive taken advantage of this very feature [passing extra arguments].

    There's a big difference between seeing where you can take advantage and have a desire to take that advantage. I can see when you might want to do it, but I've never felt I actually want to do it. When you do it it's quite likely that you've written your dispatch routines yourself. And if that's the case then I see no problem with it. You've hopefully documented that extra arguments will be ignored and that it's safe to do that.

    However, when I use such techniques myself I've found that I do it mainly when the routines takes the same arguments. Sometimes the routines are generalized to ignore some arguments though, just to fit into the template. But they're almost always related. But even if they're not, I'm not all convinced it in the general case it A-OK to pass extra arguments. I'm not into too much stricture, so I wouldn't disallow it. But I hope that the programmer doing it is aware of that it can lead to bugs in the future, like when a module is updated. Module authors expect their users to follow the manual. I wouldn't hesitate a second to add an extra argument to a subroutine if I had documented the subroutine to take a specific number of arguments. But whilst I'm against excessive stricture I still want to help the programmer discover his error. If I see a great potential for a user to error I consider putting in a warning. In my argumentation to which you replied, that is the case. There is a great chance that if the warning is issued something unforeseen or unintended happened in the user's code.

    When calling the overriden sub its fairly natural say something like sub blah { $foo->SUPER::blah(@_); ... }

    This does not relate much to my post, as far as I can see. But this is not an obvious issue, as I understand it, and could be the topic of another discussion.

    So IMO warning when someone passes too many parameters seems a little extreme.

    I can't see where I've advocated this. I've recommended programmers to use the same philosophy as Perl itself does when it comes to warnings. For instance, @foo = $bar, $baz gives a warning in void context, because most likely the programmer didn't intend it to be (@foo = $baz), $baz. I'm saying the same here: Warn if it looks like the programmer did something he didn't want to, but also give a way to disambiguate and get rid of the warning. In my case above, that simply means you make a list assignment instead of a scalar assignment.

    # Consider a subroutine with a 1-to-1 # argument to return length mapping. my ($a) = foo($bar); # OK my $a = foo($bar); # OK my ($a) = foo($bar, $baz); # OK my $a = foo($bar, $baz); # Warns. # Why did the programmer pass two arguments # if he just wanted one return value?

    In real life it could look like this:

      my $a = foo(burk());

    and &burk is believed to return just one element. Perhaps the user was wrong in that, and &burk returned more than one element. Or perhaps &burk needed to be called in scalar context to give the expected return behaviour of one element, like localtime(). If I'm reading you right, you would be annoyed by that it warns?

    I also would like to extra clearly point out that I wasn't talking about arguments length checks per se. I was talking about return list length. Checking against @_ is just a way to in a simple way often be able to check the return length. Again, my statements in my reply must be read in its proper context. I was talking about my &get_headers method, that uses one-to-one argument to return length mapping. If you read my rule of thumb (which is just that; a rule of thumb and thus has exceptions) again you'll see that it's generalized to be about the output rather than the input. That ought to be clear in the code illustration.

    If you do that then I at least hope that you use warnings::register;

    Or I use a global like $Pkg::WARN so pre-perl5.6 programmers can control the warnings. Or a combination of the two. The functionality warnings::register gives you (described in perllexwarn for those that are unfamiliar with it), is unfortunately not so well known, nor used. I think it should be given more attention.

    ... so that I can turn off the annoying messages instead of having to write tortured code just so your sub gets the correct number of parameters.

    Or you could just disambiguate... don't torture yourself when you don't have to.

    Me: I'm especially paranoid against other programmers
    You: Im sympathetic to this postion, but I think its a fine line between being paranoid, and being unduely restrictive.

    I'm not saying that I'm paranoid against my users. I'm not afraid of their abuse of my code. I'm paranoid when I am the user. I choose to write my code to be restricted by the documentation of the module I'm using. If the documentation says the subroutine wants three arguments, then it gets three arguments. If I break this deal between me and the module author I have no one to blame but myself. So I do as the documentation tells me.

    If the end user wants to give you more arguments then you need, then theres a decent chance that decision was a sound one.

    Not in this case! It's most probably a slip-up, and I want to be fair and warn the user. See the example above.

    I certainly disagree with code that makes it impossible to do something just because the author couldnt see any reason why anyone would want to do it. (A position that you arent necessarily advocating I realize, but you can see the relevance I hope.)

    I sure don't advocate that. I don't see the relevance, but I trust you that there is. But while on the topic I can just as well elaborate a bit. Why I definately not am advocating this is because I believe some of my users to be smart--often a lot smarter than I. So they might come up with cool tricks that follow by the unrestrictive interface. And I want to let people be creative. Sometimes though, restriction can be accepted. One of those cases are when you see potential future constructions that might necessarily restrain certain things. It's better to start restrictive in such cases, and easy up the rules later on. If users are significantly disturbed by my restrictions they'll hopefully let me know. If they come with good arguments, I'll sure consider it. But I don't want to paint myself in a corner due to premature design. (CPAN has examples of this.) Some modules need a couple of releases to grow, and initially you want to keep your doors open. I much prefer this over having the whole module marked as experimental, since that probably leads to that the people who actually want/need to use it in real-life situations won't, and I won't get the feedback I need on it.

    ihb
      Good reply. ++. Like your previous node theres a considerable room for digression onto other topics. Something that I certainly did in my reply to you.

      :-)

      Hmm. I have to admit that my reply didnt take full account of the context that you were discussing. Let me put it this way, I probably wouldn't write

      sub my_lc_warn { not defined wantarray and warnings::warnif("void", "my_lc used in vo +id context"); unless (wantarray) { @_>1 and warnings::warnif("my_lc called in scalar context with ex +tra arguments (".join(",",@_).")"); return lc shift; } else { return map lc $_,@_; } }
      preferring
      sub my_lc { not defined wantarray and warnings::warnif("void", "my_lc used in vo +id context"); return wantarray ? map lc $_,@_ : lc shift; }
      but I can see your point that
      my $lc=lc("A","B");
      would most likely be an error, and thus the warning could be useful.

      I admit that my comments more directly dealt with the general case that I know realize you weren't making. Although I still think that to a certain degree you were arguing the more general case than narrowly confining yourself to the specific one.

      A few comments to some particular things you mentioned, that may or may not be a digression. :-)

      You've hopefully documented that extra arguments will be ignored and that it's safe to do that.

      On the contrary. Perl idiom is relatively clear about what happens to supefluous arguments. They get ignored. So if a routine that does anything other than quietly ignore arguments should document it as so, not the other way around.

      can lead to bugs in the future, like when a module is updated.

      Well, I agree that when a module is updated and new arguments are added to old subroutines there is a possibility of problems. But this is an API change and as such means that careful checking will have to be done when upgrading to ensure that no problems arise anyway, so I'm not sure how good an argument this is.

      There is a great chance that if the warning is issued something unforeseen or unintended happened in the user's code.

      Yes. I agree with this point in this context.

      This does not relate much to my post, as far as I can see.

      But it does. Ive seen enough code where overriden methods get new arguments appended in child classes to see this as relevent. (Again i'm refering to the more general case.)

      I choose to write my code to be restricted by the documentation of the module I'm using. If the documentation says the subroutine wants three arguments, then it gets three arguments.

      Well on this one im not so convinced. Does the documentation say Takes three arguments... or does it say takes three and only three arguments...?. Unless the documentation is extremely specific the general conventions of the language need to be taken into account. One of them is extreme flexibility in handling arguments, with a bias towards tolerance. This is one reason why prototypes and related argument checking can be quite annoying. You expect that in the absence of a statment to the contrary that you can pass arguments as flattened lists (an argument against $$$ type prototypes), and that only the arguments relevent get used (an argument against caring about extra parameters). So for instance if a subroutine is documented that it takes a reference to a hash that contains the keys 'foo', 'bar', 'baz', 'bob' that it wont do anything horrible to or about the keys 'fred' and 'wilma'. And for me one expectation is that a subroutine won't use or complain about extra arguments that aren't needed or used. (Again this is more general observation, you've convinced me about the specific case.) I actually am saying this from a position of experience. THe POSIX::strftime() function and friends demands that you give them only the correct arguments. On a number of occasions I have had objects that comprised of an array(ref) made up of a format string and a localtime() output, with a number of other arguments following them in the array. I personally would have preferred (and attempted) to write

      my $timestr=strftime(@$self);
      and was annoyed at having to write
      my $timestr=POSIX::strftime(@{$self}[0..9]);
      It just seemed like an unnecessary step, particularly as the operation is just a cosmetic improvement over
      my @strftime=@{$self}[0..9]; my $timestr=POSIX::strftime(@strftime);

      I don't see the relevance, but I trust you that there is.

      Ok, well for me this comes under the subject of "interface usage restrcitions". I think that as a general rule an interface should be as flexible and forgiving as possible. And to me a warning is only slightly less annoying than a die(). Which is where I see the connection. Tenuous perhaps, but I did sort of miss your point. :-)

      Sometimes though, restriction can be accepted. One of those cases are when you see potential future constructions that might necessarily restrain certain things

      Hearty agreement for this paragraph. Although as a thought for another time I think that careful use of perls flexibility in argument handling at the initial interface design level can really cut down the number of changes to the literal interface. By this I mean something like using a hashref for options or even the use of key=>value pairs (named arguments) in parameter passing. Doing this means you usually can add attributes without fear of breaking peoples code. Incidentally if you delve through the older nodes here there are a number of discussions of argument handling tactics.

      Anyways, thanks for the reply. The next time I write something like my_lc() I just might stick a warning in there as you suggest.

      :-)

      --- demerphq
      my friends call me, usually because I'm late....

Re: Re: Re: Context aware functions - best practices?
by BrowserUk (Patriarch) on Jan 16, 2003 at 19:45 UTC

    I'll bite:) What should I do if I write a sub that is expecting an array ref and I get passed something other than an array ref?


    Examine what is said, not who speaks.

    The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.

      Well theres three possibilities.... First one is just let Perl throw an exception when it tries to use the value in the wrong way. Second is to replace the test code with something that checks that the item ISA correct type, instead of checking if the type is ARRAY.
      UNIVERSAL::isa($array_ref,"ARRAY") or die "Can't use anything but an array";
      However this is still not an exhaustive check as it will fail if some moron does
      my $array_ref=bless {},"ARRAY";
      Which is a case where afaik only perl itself can tell the difference.

      The third option is I guess the most paranoid and would be something like:

      eval{ ref($arrayref) and @$arrayref ? 1 : 1 } or die "Must have a reference to something that behaves like an ARRA +Y.\n". "Failed to coerce '$arrayref': $@";
      So personally I would just let my code choke and have the end user track the problem down. (This assumes that I have already sufficient regression test to ensure that my code is not in error in the first place ;-)

      --- demerphq
      my friends call me, usually because I'm late....

        Perl 5.8.0 comes with Scalar::Util. One of the subroutines provided by this module is reftype(). reftype() is superior to UNIVERSAL::isa() in this context. For blessed or non-blessed array references, reftype() will always return 'ARRAY'.

        Personally, I plan to use Scalar::Util::reftype() quite a bit in cases where I used to use UNIVERSAL::isa().

        Okay. So am I right in thinking that the objection only really applies if the sub in question is a method of a class and therefore break when called via the subclass?


        Examine what is said, not who speaks.

        The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.