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;
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."
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. Considerperl -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;
}
}
You mean, like Contextual::Return?
Mike, it's map that's in the scalar context; not @_. You're not lower-casing "1":
rob.kinyon- Please don't tell me I'm wrong without some elaboration.
I'm wrong because the code gives me the wrong answer? Well yeah. If this were truly a question in a tech forum, I'd hope most would avoid responding simply to confirm the OP.
I'm wrong because stylistically, the most verbose approach is the best? I'll entertain a different opinion. But I disagree with the fundamental premise that the right answer is always more code.
No- you're wrong. The example lowercase function performs a mutation of arbitrary complexity (not the trivial built in op in my example). Your solution requires that the complex mutation have a duplicate definition.
Although it's an elegant answer to your follow-up question, James's solution fails for the same reason. Although his could remedied by rewriting lowercase :List to invoke the Scalar version, my original question was "How do I avoid repeatedly using the same complicated idiom?" Depending on the interpretation of point 2, you probably think my problem is actually a desirable feature.
Most importantly, I wasn't asking this technical question to get an answer. I wanted comments on my proposed solution. Your alternative, which I didn't ask for, is wrong for the reason listed above and fails to meet the simple-for-repeatability objective explicit in my OP.
Aristotle- RTFM.
It's bad enough sloughing through speculative guesses in the tech support forums. I was really hoping one of the responders would be kind enough to comment on my solution.
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 HANDSorry 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 saysub 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.I've always assumed the Don't be clever caveat meant that if it was a good idea, Damian would've thought of it first.
Seems like you're making the same point as Aristotle did at 3:22am. But his response included a recommendation. Yours seems less gracious.
use strict ;
use warnings ;
use Attribute::Handlers ;
no warnings 'redefine' ;
my $function_code_ref ;
my %behavior_of ;
$behavior_of{first} = sub { $_[0] } ;
$behavior_of{last} = sub { $_[-1] } ;
$behavior_of{count} = sub { @_ } ;
sub UNIVERSAL::ScalarContext : ATTR {
my ( $package, $symbol, $referent, $attr, $behave_how_arg ) = @_ ;
my $symbol_name = *{$symbol}{NAME} ;
my $behavior = $behavior_of{ $behave_how_arg->[0] } ;
$function_code_ref->{ $package }->{ $symbol_name } = *{$symbol}{CODE} ;
*{ $symbol } = sub {
my $original =
$function_code_ref->{ $package }->{ $symbol_name } ;
return ( wantarray ? sub { @_ } : $behavior
)->( $original->( @_ ) ) ;
} ;
return ;
}
sub UNIVERSAL::Listify : ATTR {
my ( $package, $symbol, $referent, $attr, $behave_how_arg ) = @_ ;
my $symbol_name = *{$symbol}{NAME} ;
my $behavior = sub { @_[0..$#_] } ;
$function_code_ref->{ $package }->{ $symbol_name } = *{$symbol}{CODE} ;
*{ $symbol } = sub {
my $original =
$function_code_ref->{ $package }->{ $symbol_name } ;
return $behavior->( $original->( @_ ) ) ;
} ;
return ;
}
1 ;
This would be invoked exactly as you described.
As a gratuitous bonus, I added the Listify attribute in response to your comment about "not interested".
Still trying to figure out how to throw a compiler error if an illegal argument is passed, eg $data->[0] !~ /(?:first|last|count)/.
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.
would be better written like that:
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)