Приглашаем посетить
Анненский (annenskiy.lit-info.ru)

Section 9.8.  Contextual Return Values

Previous
Table of Contents
Next

9.8. Contextual Return Values

Make list-returning subroutines return the "obvious" value in scalar context.

There is only one kind of list in Perl, so returning in a list context is easyyou just return all the values you produced:


    sub defined_samples_in {
        return grep {defined $_} @_;
    }

But what should that subroutine return in a scalar context? It might legitimately return an integer count (like grep itself does), in which case the subroutine stays exactly the same:


    sub defined_samples_in {
        return grep {defined $_} @_;
    }

Or it might instead return some serialized string representation of the list (like localtime does in scalar context):


    sub defined_samples_in {
        my @defined_samples = grep {defined $_} @_;

        
# Return all defined args in list context...
if (wantarray) { return @defined_samples; }
# Otherwise a serialized version in scalar context...
return join($COMMA, @defined_samples); }

Or it might return the "next" value in a series (like readline does):


    use List::Util qw( first );

    sub defined_samples_in {
        
# Return all defined args in list context...
if (wantarray) { return grep {defined $_} @_; }
# Or, in scalar context, extract the first defined arg...
return first {defined $_} @_; }

It might try to preserve as much information as possible and return the full list of values using an array reference (which no Perl 5 builtin does):


    sub defined_samples_in {
        my @defined_samples = grep {defined $_} @_;

        
# Return all defined args in list context...
if (wantarray) { return @defined_samples; }
# Return all defined args (indirectly) in scalar context...
return \@defined_samples; }

It might even give up in disgust (like sort does):


    sub defined_samples_in {
        croak q{Useless use of 'defined_samples_in' in a non-list context}
            if !wantarray;

        return grep {defined $_} @_;
    }

Perl's list-returning builtins don't have a consistent behaviour in scalar context. They try to "do the right thing" on a case-by-case basis. Mostly they get it right; the scalar context results of grep, and localtime, and readline are what most people expect them to be.

Unfortunately, they don't always get it right. The scalar return values of select, readpipe, splice, unpack, and the various get... functions can be surprising to infrequent users of these functions. They have to be either memorized or repeatedly looked up in the fine manual. For many people, this makes using those builtins harder than it should be.

Don't perpetuate those difficulties in your own development. If you're writing a library of subroutines, make them predictable. Make every list-returning subroutine return the "obvious" value in scalar context.

What's the "obvious" value? It's the value that the developers who use the subroutine actually expect it to return. For example, if they all use defined_samples_in( ) like so:


    if ( defined_samples_in(@samples) > 0 ) {
        process(@samples);
    }

then they obviously expect it to return a count of defined samples. So the "obvious" scalar context return value is that count.

On the other hand, if everyone uses it like this:


    my $floor_samples_ref     = defined_samples_in(@floor_samples);
    my $restocked_samples_ref = defined_samples_in(@restocked_samples);

    
# and later...
swap_arrays($floor_samples_ref, $restocked_samples_ref);

then the expectation is clearly that the subroutine returns a reference to the array of results. So that's the "obvious" scalar return value.

In other words, the "obvious" return value in a scalar context is whatever the people who use your code think it's going to be (before they read the fine manual). That definition of obviousness presents a dilemma, though. The way you work out whether your proposed scalar-context behaviour is obvious is by implementing it and seeing how many people it trips up. But once the subroutine is deployed and client code is relying on it, it's too late to change its return value if that value turns out not to be what most people expect.

The solution (which is discussed in greater detail in Chapter 17) is to "play test" the subroutine before it's deployed. That is, ask the people who will actually be using your subroutine what they expect it will do in scalar context. Or, better yet, have them write sample code that uses the subroutine, and see how they use it. If you get a consensus (or even just a simple majority opinion), implement that. If you don't get agreement on a single "obvious" behaviour, see the "Multi-Contextual Return Values" guideline later in this chapter.

Unfortunately, getting this kind of preliminary feedback isn't always feasible. In such cases, you should simply select a reasonable default, based on the three fundamental categories of list-returning subroutines: homogeneous, heterogeneous, and iterative.

A homogeneous list-returning subroutine is one that returns a list of data values that are all of a single type: a list of samples, a list of names, or a list of images. Perl's built-in map, grep, and sort are examples of this type of subroutine. Because no one value in a homogeneous list is more significant than any other, the only interesting property of the list in a scalar context is usually the number of values it contains. Hence, in scalar contexts, homogeneous subroutines are usually expected to return a count, as map and grep both do.

A heterogeneous list-returning subroutine is one that returns a list containing distinct pieces of information: name, rank, and serial number; account number, account name, and balance; year, month, day. For example, the stat, caller, and getpwent builtins are all heterogeneous. The lists returned by subroutines of this type often do have a single piece of information that is more significant than any other, and they're typically expected to return that value in scalar contexts. For example, caller returns the caller's package name, whilst getpwent returns the relevant username.

Alternatively, all of the information returned by a heterogeneous subroutine might be equally important. So this type of subroutine is sometimes expected to return some kind of serialized representation of that information in scalar context, as localtime and gmtime do.

An iterative list-returning subroutine is one that returns an iterated series of values, typically the result of successive input operations. The builtins readline and readdir work this way. Iterative subroutines are always used for stepping through sequences of data, so in a scalar context, they should always return the result of a single iteration.

Remember, though, that these suggested default behaviours are recommendations, not natural laws. You may find that your "play testing" suggests that some other return value is more appropriatemore expectedin your particular subroutine. In that case, you should implement and deploy that behaviour instead, and then explicitly document the reasons for your choice.

    Previous
    Table of Contents
    Next