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

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

Sample Code:

use List::Util qw( first ) ; sub lowercase { return first {$_} map { lc } @_ unless wantarray ; return map { lc } @_ ; } $jim = lowercase( 'Jim' ) ;

I've exhausted my imagination trying to reduce the lowercase function to a single line. But surely this problem has been solved by better minds than mine. Monks?

Thanks! -Jim

UPDATED I've posted several clarifications based on the replies so far. My original question is whether there's an equivalent function to first that is context sensitive.

UPDATE 2

Three conditions: @me = lowercase( qw( TQIS Jim ) ) ; ## returns qw( tqis jim ) $me = lowercase( 'Jim' ) ; ## returns 'jim' @me = lowercase( qw( Jim ) ) ; ## returns qw( jim )

UPDATE 3

tye's solution below, which uses a separate function named context, is elegant and concise and pretty close to what I'm looking for.

To restate my question one final time: What is the right way to get perl to do the right thing? I assumed there'd be an obvious answer. Although no consensus, I *did* get a lively conversation.

Replies are listed 'Best First'.
Re: wantarray alternative
by davido (Cardinal) on Jul 10, 2013 at 18:52 UTC

    Isn't first { $_ } @array the same as $array[0]? (except where $array[0] contains a "false" value.)

    sub lowercase { return ( wantarray ) ? map { lc } @_ : lc $_[0]; }

    Is your intended use of 'first' really just looking for elements that contain some non-false value (because that's what it does)? If so:

    return ( wantarray ) ? map { lc } @_ : lc( first { $_ } @_ );

    ie, no need to 'lc' the entire @_ if you are only returning one. Find the one you want, 'lc' it, and return that.


    Dave

      I'm really struggling to articulate my question. The real question is: If there's only one value in the return list, how can I return that value in scalar context, not the count of 1?.

      sub lowercase { my @out = map { lc } @_ ; return @out == 1 && ! wantarray ? $out[0]: @out ; }

      This code is optimized based on all the answers so far, and doesn't use first either. In fact, I've probably used this construction thousands of times.

      My question is based on 3 assumptions:

      1. As mentioned, I prefer to avoid using placeholder variables.
      2. I find it awkward to refer to 3 forms of a variable in a single statement.
      3. If I've used this construction thousands of times, and so have others, why hasn't someone developed a single operator?

      I assumed that List::Util would include this functionality. That is, if first or some variant could determine and respond to the wantarray context.

      UPDATE (no update)
        > If there's only one value in the return list, how can I return that value in scalar context, not the count of 1?

        just return the list! In scalar context the last element is taken, so if you are only returning "one value in the return list" thats the same.

        It's the scalar of an array(!) which counts.

        UPDATE

        ok not that easy, avoid scalar context and explicitly put the scalar into a list at LHS.

        DB<129> @out=A..C => ("A", "B", "C") DB<130> sub tst { return map{lc} @out } DB<131> @list = tst() => ("a", "b", "c") DB<132> ($scalar) = tst() => "a"

        Though we asked several times you haven't clarified which result you expect in in case of scalar context and longer list!

        Cheers Rolf

        ( addicted to the Perl Programming Language)

        return @out == 1 && ! wantarray ? $out[0] : @out;

        So if @out has a single element, return '1'. If it contains zero or 2, or more elements, then if "wantarray", return @out, otherwise return the first element.

        I can't imagine using that once, let alone thousands of times. ;)

        Update: Woops, "&&" is higher on the precedence table than "?:" in any language I know of. My mistake.


        Dave

Re: wantarray alternative
by tobyink (Canon) on Jul 10, 2013 at 20:17 UTC

    This is quite a concise way of doing things:

    sub lowercase { wantarray ? (map { lc } @_) : lc(shift); }

    This is an insane way of doing things:

    sub lowercase { ( wantarray ? sub{@_} : sub{shift} )->( map { lc } @_ ); }

    Update: Here's another solution using my module returning that allows you to create additional keywords that act like return...

    #!/usr/bin/env perl use v5.14; use returning { lreturn => sub { wantarray ? @_ : $_[0] } }; sub lowercase { lreturn map { lc; } @_; warn "this line never executes"; } say for lowercase('Alice', 'Bob'); say scalar lowercase('Carol', 'Dave');
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
Re: wantarray alternative (Context propagation)
by LanX (Saint) on Jul 10, 2013 at 20:44 UTC
    context is propagated into the return statement.

    see Context propagation into subs and evals

    and according to perldoc: map in scalar context, returns the total number of elements so generated.

    But the following code forces the map-statement into a sliced list.

    DB<156> sub tst { (map {lc} @_)[0.. wantarray * $#_] } DB<157> @list=tst("A".."D") => ("a", "b", "c", "d") DB<158> $scalar=tst("A".."D") => "a"

    and if the last value is enough for you in scalar context do

    DB<159> sub tst { (map {lc} @_)[0.. $#_] } DB<160> @list=tst("A".."D") => ("a", "b", "c", "d") DB<161> $scalar=tst("A".."D") => "d"

    you haven't answered my question in Re^5: wantarray alternative yet, so you have both alternatives now.

    Cheers Rolf

    ( addicted to the Perl Programming Language)

      I don't seem to be answering as fast as you're asking.

      If the explanation is that map is being called in scalar context, then the following is unexpected:

      sub lowercase { my @out = map { lc } @_ ; return @out ; } $name = lowercase( 'Jim', "Jeff" ) ; ## $name == 2

      Even though map is called in an array context, the results don't change.

      Very clever solution, yours. Clever, but not elegant.

        > If the explanation is that map is being called in scalar context, then the following is unexpected:

        no! whatever comes after return is executed in the context of the sub's call.

        so in this case¹ $name = scalar @out

        It's true many people expect a LIST to be returned but thats generally wrong in scalar context.

        A helper routine  sub listify { (@_)[0..$#_] } might help to assure this "expected" behaviour, whenever you need to return a LIST².

        Cheers Rolf

        ( addicted to the Perl Programming Language)

        PS: please read the thread I linked before asking more questions.

        ¹) which isn't the code from the post I replied to, where a map-statement is returned

        UPDATE

        > Clever, but not elegant.

        Larry will be so devastated to hear this ... :´(

        UPDATE

        ²) FWIW

        DB<112> sub listify { (@_)[0..$#_] } DB<113> sub tst { listify map{lc} @_ } DB<114> $s =tst("A".."D") => "d" DB<115> @l =tst("A".."D") => ("a", "b", "c", "d")
Re: wantarray alternative (helper)
by tye (Sage) on Jul 10, 2013 at 20:10 UTC
    sub context { wantarray ? @_ : $_[0] } sub whatever { return context( map { ... } @_ ); }

    - tye        

      This works, but why isn't this function built in somewhere? It seems most responders assume that this is perl's default behavior.

        Because that function is trivial to write and it isn't the only or even the obviously right choice.

        sub first_or_all { wantarray ? @_ : $_[0] } sub last_or_all { wantarray ? @_ : $_[-1] } sub list_or_aref { wantarray ? @_ : \@_ } sub aref_or_list { wantarray ? @{$_[0]} : $_[0] } sub pairs_or_href { wantarray ? @_ : {@_} } sub href_or_pairs { wantarray ? %{$_[0]} : $_[0] } sub all_or_concat { wantarray ? @_ : join $,, @_ } sub all_or_die { return @_ if wantarray; return $_[0] if @_ <= 1; require Carp; local $Carp::level = 2; my $sub = ( caller(1) )[3]; Carp::croak( "Don't call $sub() like that in a scalar context" ); }

        (to list just a few off the top of my head that I've heard people argue are wise choices)

        - tye        

Re: wantarray alternative
by Preceptor (Deacon) on Jul 10, 2013 at 18:36 UTC

    #include disclaimer on why one-liners are generally undesirable.

    Consider a ternary operator. Specifically:

    condition ? result_if_true : result_if_false

    You could then make this function:

    wantarray ? first {$_} map { lc } @_ : map { lc } @_ ;

    Note also - return is (almost) redundant if it's the last line of the code - by default Perl returns from a sub 'the result of the last operation'. This too though, isn't always a good thing, because readable code almost always trumps really short code.

      readable code almost always trumps really short code

      I know what you're saying but shorter code, like shorter sentences, tends to be more readable; short != confusing|obfuscated. Omitting return is a best practice, to me, when there is no control flow and the sub is short.

      I did not intend to ask the question How do I use the tertiary operator?. Both these answers rely on the trivial lc operation I used in my example.

      To rephrase: What's the most elegant way to modify the default behavior of a subroutine that returns a list of one?

      sub lowercase { my @out = map { lc } @_ ; return wantarray ? map { $_ } @out : first { $_ } @out ; }

      So my specific question is: How can I move the operation from the first line into the second line to avoid using the place holder out?

Re: wantarray alternative
by kcott (Archbishop) on Jul 11, 2013 at 11:30 UTC

    G'day tqisjim,

    I changed this a few times as you posted updates. Anyway, here lowercase() contains one line, doesn't use temporary arrays, doesn't require other functions and returns the values you show in "Three conditions:" (using the same lowercase(...) calls).

    $ perl -Mstrict -Mwarnings -E ' sub lowercase { return (map { lc } @_)[wantarray ? (0 .. $#_) : 0]; } my (@me, $me); @me = lowercase( qw( TQIS Jim ) ) ; ## returns qw( tqis jim ) say "@me"; $me = lowercase( q{Jim} ) ; ## returns q{jim} say "$me"; @me = lowercase( qw( Jim ) ) ; ## returns qw( jim ) say "@me"; ' tqis jim jim jim

    Update: It would appear that, amidst all the various updates, I missed Re: wantarray alternative (Context propagation) by ++LanX which is a virtually identical solution but with a somewhat cleverer idiom:

    (map { lc } @_)[0 .. wantarray * $#_]

    -- Ken

Re: wantarray alternative
by gsiems (Deacon) on Jul 10, 2013 at 18:38 UTC
    return (wantarray) ? map { lc } @_ : first {$_} map { lc } @_;

    Update: Doh! Preceptor beat me to it.