in reply to Use of wantarray Considered Harmful

Which way "just works"?

At $work right now, you can find both of these:

return wantarray ? @x : \@x; return wantarray ? @x : $x[0];

I wonder what will happen when one of those two programmers has to work on the other's code.

Both are just bad practice.

That's not something wantarray is to blame for. Both obscure the intent of the calling code and place the usage intention of the function return to that function, out of the wrong type of laziness.

It is so much clearer to make the intent clear in the calling code,

my $ref = [ $foo -> bar( $stuff) ]; my @ary = $foo -> bar( $stuff); my $baz = ( $foo -> bar( $stuff) )[0];

which usage doesn't even need comments.

If the code of the method bar() produces a list, it should return a list and leave it to the caller to handle the result: stuff it into a reference, an array, place the first element in a scalar or count the list's elements. Things get interestingly different if the semantics and/or return values of the function in question are non-trivially different in scalar, list or void context, for purposes made clear in the calling code.

But then,

I didn't think context mattered
...or I didn't know I changed the context.

context is a basic perl principle and built into the language, for good reasons, hence there's wantarray as a built-in function which is absolutely needed. Bad usage is no reason to deprecate it.

Core perl functions behave different when called in scalar or list context, and such behavior is documented. Your code examples just show the lack of documented conventional coding standards at your working place, which wantarray isn't to be blamed for, either. If you have dual-use functions or methods, you have them documented, and are aware of the context of the calling code.

wantarray has its good uses - Contextual::Return relies on it.

Replies are listed 'Best First'.
Re^2: Use of wantarray Considered Harmful (bad use)
by BrowserUk (Patriarch) on Dec 12, 2008 at 20:32 UTC
    It is so much clearer to make the intent clear in the calling code,
    my $ref = [ $foo -> bar( $stuff) ];

    And yet, it is so profligate. This way of obtain a reference to a million item array built within a sub:

    C:\test>p1 sub x1{ my @a; $a[ $_ ] = $_ for 1 .. 1e6; return @a };; print time; $r = [ x1() ]; print time;; 1229112810.36413 1229112815.5985

    Takes 5 seconds and uses 50 MB of RAM.

    This way:

    C:\test>p1 sub x2{ my @a; $a[ $_ ] = $_ for 1 .. 1e6; return \@a };; print time; $s = x2(); print time;; 1229112742.05163 1229112742.43096

    Takes under half a second and uses 20MB of RAM.

    If every time someone asked google to give them a reference to wikipedia, they transmitted the whole 4.4GB, people would rightly find the wasting of scarse resources distasteful.

    If you going to go the "context is bad" route, always return the reference and let the caller expand it if they need to.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      And yet, it is so profligate.

      Of course, since it is copying all the stuff around. What about

      sub x3{ 1 .. 1e6 } print time; $r = [ x3() ]; print time;

      this?

      But of course that wasn't my point - I'm rather saying that it is a bad idea to hide the decision of how to deal with returns in a place far away. And no, I'm not going the "context is bad" route; but using context to obfuscate things should be used in obfuscation type of code, only...

        What about sub x3{ 1 .. 1e6 } this?

        What about it? Can you demonstrate a good use for that construct?

        I'm rather saying that it is a bad idea to hide the decision of how to deal with returns in a place far away.

        I tried hard but failed to decide which of the fallacious arguments that sentence should be categorised under. I guess that straw man is it's generic category, but it could equally be any of several others.

        One of the nicest things about Perl is that the source is almost never hidden. Failure to "use the source" and blame "a lack of documentation" for errors is laziness of the wrong kind.

        but using context to obfuscate things

        Hm. I seriously doubt that either programmer in the OPs example deliberately set out to obfuscate their code. Or that outside of Obfuscated Code, that you could find many examples of where a programmer has deliberately set out to do so.

        Whilst individual uses of context sensitive returns can be examined after the fact and declared ill-conceived, doing so in isolation of the purpose of the function itself, or the application(s) of its use, serve little useful purpose. For example, in isolation, the usefulness of:

        ... return wantarray ? @a : $a[0]; }

        seems very questionable. But if the context is:

        sub each { ... my @a = getKeyAndValue(); return wantarray ? @a : $a[0]; }

        Maybe not so much.

        And any attempt to dismiss the use of another of Perl's powerful and unique features as "bad practice" on the basis of two contrasting examples without context [sic] is a knee-jerk reaction and a premature pessimisation.

        Every one of us use one or more languages every day, that rely heavily upon context to convey meaning, and to tailor meaning to the context of use. And we learn to do this from a very early age, and without really explicitly thinking about it. The "it's too hard to learn" argument doesn't really fly in my book.

        Pick up any newspaper, magazine, technical article or transcript and pull half a dozen sentences at random from them, and examine them in isolation and it will be very difficult to understand their full meaning.

        Conversely, if we (that subset of the human species that program in Perl, and read articles on perlmonks ... (I'm giving up on that idea before I go any further:)), were to even begin to try to totally disambiguate every sentence we write, so that each sentence could stand alone in isolation, we'd fail. Because each sentence would have to run on for several pages, and have every other word be a hyperlink.

        If we cannot do it, (and wouldn't want to), in our native languages, why would we either expect or want to do it in our programming languages?

        And I find the "creates the potential for errors during refactoring" argument equally weak. If a function returns a reference and the refactorer fails to dereference it before appending it to a string, he's made a error, plain and simple. That does not form a salient argument for the abolition of references.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re^2: Use of wantarray Considered Harmful (bad use)
by kyle (Abbot) on Dec 12, 2008 at 19:30 UTC

    I agree that wantarray has good uses, but I rarely see them. I might even go so far as to say the good uses of wantarray are as rare as the good uses of prototypes. I'm curious as to what you'd consider a good use of it. I don't consider Contextual::Return to be a "good use" mostly because it does the same thing only moreso. It's like saying a good use of open is IPC::Open3.

      Well, as I wrote some time ago, one such use are subroutines that just produce data and behave different in void, scalar and list context

      $thingy -> foo (1,2); # results printed to current filehandl +e $string = $thingy -> foo (1,2); # results returned as a single string @lines = $thingy -> foo (1,2); # results returned as a bunch of lines

      which works fine and is just fine - if documented.

      Another such use would be subroutines which behave like google's "I feel lucky" (scalar) vs. "normal" (list) search.

      I agree with you that good uses of wantarray are rare, and the only type of prototype I use now and then is '&', which lets me write subs that get passed blocks, like map or sort.

      I don't consider Contextual::Return to be a "good use" mostly because it does the same thing only moreso.

      That statement contradicts the OP's last heading - "Better" versions of wantarray.

Re^2: Use of wantarray Considered Harmful (bad use)
by TGI (Parson) on Dec 12, 2008 at 21:06 UTC

    Hear, hear. You make many good points.

    return wantarray ? @x : \@x; return wantarray ? @x : $x[0];

    I wonder what will happen when one of those two programmers has to work on the other's code.

    Both are just bad practice.

    The first one is just plain bad. Someone would have to work hard to persuade me that it's a good idea to use it. It costs functionality and doesn't buy anything. If there is a strong need to return an array ref, then why not return the reference every time. If one "needs" a version of the routine that returns a list, wrap the reference-returning version and and give the wrapper a clear name.

    I don't think the second usage is necessarily bad practice. This is the same interface used by readline. If there is a good reason not to return the whole list at once (it's huge, your reads are destructive, etc), then this API makes sense.

    Context sensitive APIs are like operator overloading and tie. If used properly, they improve the readability of your code. If used badly, life begins to suck. Think carefully before using any of these techniques.


    TGI says moo

      I don't think the second usage is necessarily bad practice. This is the same interface used by readline.

      Oh no it isn't! (Cue audience...)

      readline returns line 2 on the second call, line 3 on the third call, etc -- and it only reads those lines when they're requested. This is useful, because you can stick it in a while loop and iterate over a potentially large "array" but only hold one element in memory at a time.

      return wantarray ? @x : $x[0]; does nothing so useful. It creates the entire array every single time the sub is called, and then it always returns the same first element. If the whole list is huge, then you're creating the whole huge list anyway. If the reads are destructive, then you just destroyed everything but the first item. Not so useful...

        Oh yes it is! :)

        Granted, if you create your @x by copying the large array you may wish to avoid copying, then it is pretty stupid to use wantarray to avoid making another copy.

        But if you are working with an extant huge @x, its OK.

        With only the one line we have to guess what the provenance of @x is.

        Even if your read is destructive (like from a filehandle, or another similar iterator), there should be no problem. The ternary short circuits, and only one term is evaluated. So @x never gets evaluated in a list context and we have no massive destruction.

        This assumes that you are working with a preexisting @x. If you are destructively copying from some source into @x, then the results will be ugly.

        Anyhow, I think we basically agree that context sensitive return values can be useful to improve an API, but the should be used with care, and that they must be clearly documented. Also that creating an interface similar to readline (without creating massive unneeded copying of data) is a valid use of the technique.


        TGI says moo