function return in scalar context

sub lowercase {
    return map { lc } @_ ;
}

$jim = lowercase('jim') ;
print "$jim\n" ;

Naturally that this snippet of code prints 1. I understand the explanation of "an array in scalar context blah blah blah". But it's so counter-intuitive because many functions are intended to mutate each element in a list. Presumably one should define separate functions depending on whether an array is expected, but that's so non-perlish. There's no elegant way to throw the wantarray operator in that example function. And even if there was, it's awkward to use the same idiom repeatedly.

I'd prefer simply to use a pragma such as the following:

use Function::ReturnScalar qw( first ) ;

## use Function::ReturnScalar qw( last ) ;      # optional alternative

## use Function::ReturnScalar qw( count ) ;   # optional alternative

sub lowercase {
    return map { lc } @_ ;
    }

$jim = lowercase('Jim') ;                                  # $jim == 'jim'

The pragmas in the above example don't exist. At least, even after posting on perlmonks, I can't find anything equivalent. But in a couple of hours, I wrote a rudimentary module that performs this function.

So if in fact there isn't one already, and others are interested in this functionality, I'd be perfectly happy to press on. In that case, please comment as appropriate.

Here's my prototype:


package Function::ReturnScalar ;

use strict ;
use warnings ;

my $originals_of_package_ref ;
my @callers ;
my %behavior_of ;

$behavior_of{first} = sub { $_[0] } ;
$behavior_of{last} = sub { $_[-1] } ;
$behavior_of{count} = sub { @_ } ;
		
INIT {
	map { _replace_package_symbols( @$_ ) } @callers ;
	}

##
#  Accepts one of first|last|count 
##

sub import {
	my ( $package, $behave_how ) = @_ ;
	push @callers, [ scalar caller, $behave_how ] ;
	}

sub _replace_package_symbols {
	my ( $package, $behave_how ) = @_ ;

	no strict qw( refs ) ;
	no warnings ;

	my $scalar_code_ref = $behavior_of{ $behave_how } ;
	return unless $scalar_code_ref ;

	my %symbols = %{ "${package}::" } ;
	my @subs = grep $_->[1],
			map { [ $_ => *{ $symbols{$_} }{CODE} ] } 
			keys %symbols ;
	map { $originals_of_package_ref->{ $package }->{ $_->[0] } = $_->[1] }
			@subs ;
	map { *{ "$symbols{ $_->[0] }" } 
			  = _create_closure( $package, $_->[0], 
			  $scalar_code_ref ) }
			@subs ;

	return ;
	}

sub _create_closure {
	my ( $package, $symbol_name, $scalar_code_ref ) = @_ ;
	return sub {
		my $original = 
		  $originals_of_package_ref->{ $package }->{ $symbol_name } ;
		return ( wantarray ? sub { @_ } : $scalar_code_ref
		  )->( $original->( @_ ) ) ;
		} ;
	}

1;

16 Comments

I've been getting bitten by this quite a bit these last few weeks. But as far as I can tell, it's not an array yet (it hasn't been saved to an AV). That's why it's always weird to me when it happens: There's a difference between lists and arrays, _except_ in this one case.

The correct answer is "the implementation of lowercase() is wrong."


sub lowercase {
my $w = wantarray;
return unless defined $w;
if ($w) {
return map { lc } @_;
}
if (@_) {
return lc($_[0]);
}
return;
}

Now, if you want to make something cool, write something that generates that function, letting the user write snippets for each bit.

The subroutine call puts @_ in scalar context, not the list returned by map. Consider

perl -E '@foo = ( "bar" ); say ~~map { lc } @foo'
1

Same thing. You are lowercaseing the list ( 1 ).

Alternatively you could create attributes with Attribute::Handlers and turn off the redefine warning and create a pair of functions like this (or even three functions, for void context):

sub lowercase :Scalar {
lc $_[0]
}

sub lowercase :List {
map { lc } @_
}

sub lowercase :Void {
die "something appropriate"
}

which then get rewritten into something like:
sub lowercase {
my $w = wantarray;
if (!defined $w) {
goto &$func_void;
} elsif (!$w) {
goto &$func_scalar;
} else {
goto &$func_list;
}
}

Now, if you want to make something cool, write something that generates that function, letting the user write snippets for each bit.

You mean, like Contextual::Return?

Mike, it's map that's in the scalar context; not @_. You're not lower-casing "1":


perl -E '@foo = ( "bar" ); say ~~map { say; lc } @foo'

My comment on your solution is 1a) I’d actively remove it from any codebase of mine if it had this interface, 1b) I wouldn’t be interested but also wouldn’t mind it in a codebase of mine if the interface didn’t have package-global effect (e.g. a sub attribute, like :ScalarContext(first) etc), and 2) I’m not aware of anything similar preexisting. HTH HAND

Sorry I don't have time to look over the new code, but I can quickly weigh in on the problem. I have often commented on the oddity that returning a list vs returning an array behaves differently in scalar context. That said you can create a list from an array by taking the full slice:

perl -E 'sub lower { (my @r = map { lc } @_)[0..$#r] } say scalar lower "Jim"'

Yeah its annoying, in fact its my usual answer to "what's your most disliked thing about your favorite language?" that gets asked sometimes

Joel: In larger projects I paste a sub listify { @_[0..$#_] } for that purpose. Then you can say sub lower { listify map { lc } @_ }.

I don't like it because it changes the behaviour of Perl by placing two relevant items far apart. As far as programmers go, out of sight is out of mind. The `use Function::ScalarReturn` has to be place near where the sub is called, or programmers are going to forget it has special behaviour.

Or as Damian Conway put it in Perl Best Practices, "Don't be clever."

perlfunc on map: In scalar context, returns the total number of elements so generated.

perlfunc on grep: In scalar context, returns the number of times the expression was true.

Please don't monkeypatch UNIVERSAL. If you want to make a pragma, then make a real pragma, but you'll probably need to patch core.

There is of course « more than one way to to it ». But some are more idiomatic than others.

map { _replace_package_symbols( @$_ ) } @callers ;

would be better written like that:

_replace_package_symbols( @$_ ) for @callers;

In general, using map in void context is a wrong usage of the divertisty of Perl syntax.

(That is not an endorsement for the rest of the post)

Leave a comment

About Jim Schueler

user-pic NoSQL::PL2SQL is everything I've got.