|No such thing as a small change|
Use of wantarray Considered Harmfulby kyle (Abbot)
|on Dec 12, 2008 at 16:09 UTC||Need Help??|
Using wantarray, a sub can tell what context it was called in so it can behave differently depending on whether its return value will be ignored, put into a scalar, or put into a list. An idiom I've seen a number of times uses wantarray to decide whether to return an array or an array reference.
This way, you can either write "@foo = get_x()" or "$foo = get_x()", and it will "just work".
I am against this practice.
Which way "just works"?
At $work right now, you can find both of these:
I wonder what will happen when one of those two programmers has to work on the other's code.
I didn't think context mattered
...or I didn't know I changed the context.
Imagine refactoring this:
We want to eliminate the repetition. Make it like this:
Nice, right? Yes, except now it's broken because at the end of get_moles() we have:
Our straight-forward refactoring has changed the context of get_moles() from scalar to list, and the result is that $dewdrop_description gets polluted with all manner of extra stuff. If you're lucky, get_moles() returns an even number of items, and you have warnings on, and you are told "Odd number of elements in anonymous hash". If you're not lucky, there's no warning, and an odd index element from get_moles() clobbers a key that get_droplet() returned.
Don't surprise me
I'm often looking at code already written for some example of how to use things. Given a "$scalar = x()", I expect that x() returns a scalar. I know very well that's not necessarily true and that context can have effects far beyond its source. Nevertheless, it generally does not occur to me that maybe this scalar-returning behavior is conditional.
A sub that returns more than one value can have unpredictable behavior in scalar context. What you get depends on whether the scalar context applies to an array or a list. If a sub returns a list, you can't just apply a scalar context to it and know what it will do without looking inside the sub to see what's to the right of return. In light of this, it almost seems merciful to use wantarray to lay down the law. A consistent use across code might actually clear some things up.
Still, I am not in favor of this.
Always return the same thing.
If your caller wants to stick your list in a scalar instead of an array, make it build the array reference itself.
If the list you return is so massive that copying it all is a burden, return an array reference. If the caller wants to copy it into an array anyway, it can dereference your return value and suffer the consequences.
On the defense
I've seen it suggested that every assignment from a sub call should be with a list context.
This way, if it's giving you a scalar, you get it. If it's giving you a list, you get the first element. If it's giving you an array, you get the first element. It's about as consistent as you can get without looking into why().
I'm not quite to that point yet, but I can see where it's coming from.
A good use of wantarray
I think it's a good idea to use wantarray to check for void context. That can be used for run-time programming mistakes like calling an accessor in void context.
This will catch the case where you don't know you've passed in nothing.
"Better" versions of wantarray
I'll note without comment that there are a couple of modules out there that do what wantarray does and more, if you're into that kind of thing.