Context and the Comma Operator - perl

One of my colleagues used a comma after a statement instead of a semi-colon, what resulted was similar to the below code:
my $SPECIAL_FIELD = 'd';
my %FIELD_MAP = (
1 => 'a',
2 => 'b',
3 => 'c',
);
sub _translate_to_engine {
my ($href) = #_;
my %mapped_hash
= map { $FIELD_MAP{$_} => $href->{$_} } keys %$href;
$mapped_hash{$SPECIAL_FIELD} = FakeObject->new(
params => $mapped_hash{$SPECIAL_FIELD}
), # << comma here
return \%mapped_hash;
}
At first I was surprised that this passed perl -c, then I remembered the comma operator and thought I understood what was going on, but the results of the two print statements below made me doubt again.
my $scalar_return = _translate_to_engine(
{ 1 => 'uno', 2 => 'dos', 3 => 'tres' }
);
print Dumper $scalar_return;
# {'c' => 'tres','a' => 'uno','b' => 'dos','d' => bless( {}, 'FakeObject' )}
This call was made in scalar context and the result that I get is the expected result. The comma operator evaluated the LHS of the comma discarded, then evaluated the RHS. I don't believe that it can return the value of the RHS here, because evaluating the return statements leaves the subroutine.
my #list_return = _translate_to_engine(
{ 1 => 'uno', 2 => 'dos', 3 => 'tres' }
);
print Dumper \#list_return;
# [{'c' => 'tres','a' => 'uno','b' => 'dos','d' => bless( {}, 'FakeObject' )}]
This call was made in list context but the result I get is effectively the same as the call in scalar context. What I think is happening here: Both arguments are evaluated since the sub was called in list context, when the RHS is evaluated the return statement is executed so the LHS is effectively discarded.
Any clarification on the specific semantics that happen in either case would be appreciated.

Your explanation is accurate.
The context in which _translate_to_engine is called affects is the context in which all final expressions of the function are evaluated, including the argument to all return. There are two expressions affected in this case: the comma you mentioned, and \%mapped_hash.
In the first test, the returned value is \%mapped_hash evaluated in scalar context. And in the second, the returned value is \%mapped_hash evaluated in list context. \%mapped_hash evaluates to a reference to the hash, regardless of context. As such, the result of the sub is the same regardless of context.

The LHS of the expression is $mapped_hash{$SPECIAL_FIELD} = FakeObject->new(...) and the RHS is return \%mapped_hash. As you said, the comma operator evaluates the left hand side (which assigns a FakeObject instance to the hash key d), and then evaluates the right hand side, causing the sub to return the hashref. Makes sense to me.
Calling the sub in list or scalar context doesn't matter. It isn't going to change the context of the comma operator, which is the same in both cases.

Related

Is there a way to distinguish error and empty array returned from subroutine when it is called in list context?

I have script:
#!/usr/bin/env perl
sub t0 {
return; # We return nothing for ERROR
}
sub t1 {
#z = ();
return #z; # We return array (which maybe empty) for no ERROR
}
In scalar context I can distinguish error and OK status:
my $x1 = t0(); # undef
my $x2 = t1(); # 0
Is there a way to distinguish error and empty array returned from subroutine when it is called in list context?
my #x1 = t0(); # empty list
my #x2 = t1(); # empty list
I am feeling that I need "0E0" (Zero but TRUE) but for list context.
A way to distingish between the empty list and an error in this case would be to return an array reference, e.g.
sub t0 {
return undef; # We return undef for ERROR
}
sub t1 {
#z = ();
return \#z; # We return array (which maybe empty) for no ERROR
}
So basically the functions do not behave differently in list context. They always return a single scalar, which may be undef, which indicates an error, or return a reference to an array.
No.
Return an array reference if successful (which the caller will have to dereference) and undef on failure.
Or throw an exception on failure (which the calling code could catch).
In list context a sub will return a list, even if it only has one element which is undef. If you must return data in a flat list – if it is not feasible to change the callers, say in a large codebase -- you can die on error and eval somewhere at a top level. (As die is an exception it "bubbles.")
The other option for the flat list is rather unappealing, and it still requires changes to callers. You can return an empty list or an undef and then test accordingly -- but see the comment below.
sub test {
# set #z (but not with undef!), $error
return undef if $error;
return #z;
}
my #ret = test();
if (not #ret) {
# empty list
}
else {
foreach my $elem (#ret) {
if (not defined $elem) {
warn "Undefined";
last
}
# ...
}
}
However, as pointed out by ikegami, if #z gets assigned an undef legitimately (without error), for example a variable that happens to be undefined, this code can't tell the difference. So the function would have to be changed further to deal with that.

Perl syntactical details - list context with or without parens?

This works:
my $r = someSubroutine( map { ( 0 => $_ ) } #hosts)
This does not work, giving a syntax error:
my $r = someSubroutine( map { 0 => $_ } #hosts)
What I think I understand is that the { } after the map amounts to a closure or anonymous subroutine.
But if I put a "value, value" at the end of a normal subroutine, it will return a list of those values. If I use this brevity with the map, it is a syntax error.
First of all, this is a very strange statement. The list that map produces will look like
0, $hosts[0], 0, $hosts[1], 0, $hosts[2], ...
so it's useless for assignment to a hash as it would be the same as
my %hash = (0 => $hosts[-1])
map will accept either a BLOCK (which is what you're using) or a simple EXPRESSION for its first parameter. The problem here is that { 0 => $_ } looks very like an anonymous hash with a single element, which is an EXPRESSION, and that is what the parser guesses it is. An EXPRESSION requires a comma after it, before the second parameter, but when perl gets to the closing brace in map { 0 => $_ } #hosts it doesn't find one so it has to throw a syntax error as it is too far to backtrack to the opening brace and assume a block instead
The documentation puts it like this
{ starts both hash references and blocks, so map { ... could be either the start of map BLOCK LIST or map EXPR, LIST. Because Perl doesn't look ahead for the closing } it has to take a guess at which it's dealing with based on what it finds just after the {. Usually it gets it right, but if it doesn't it won't realize something is wrong until it gets to the } and encounters the missing (or unexpected) comma. The syntax error will be reported close to the }, but you'll need to change something near the { such as using a unary + or semicolon to give Perl some help
The solution is to disambiguate it as you discovered. Any of these will work
map +( 0 => $_ ), #hosts
map(( 0 => $_ ), #hosts)
map { +0 => $_ } #hosts
map { ( 0 => $_ ) } #hosts
map { ; 0 => $_ } #hosts
map has two syntax:
map BLOCK LIST e.g. map { f() } g()
map EXPR, LIST e.g. map f(), g()
When Perl encounters map, it needs to determine which syntax was used. Let's say the first token after map is {. That's the start of a BLOCK, right? Hold on! Expressions can start with { too!
my $hash_ref = { key => 'val' };
The grammar is ambiguous. Perl has to "guess" which syntax you are using. Perl looks ahead at the next token to help guess, but sometimes it guesses incorrectly nonetheless. This is one of those cases.
The following are the standard workarounds for this:
map {; ... } LIST # Force Perl to recognize the curly as the start of a BLOCK
map +{ ... }, LIST # Force Perl to recognize the curly as the start of a hash constructor
; can't be part of a hash constructor, so the { can only start a BLOCK.
+ necessarily starts an EXPR (and not a BLOCK). It's an operator that does nothing but help in situations like this.
For example,
map {; +{ $row->{id} => $row->{val} } } #rows
This is described in perldoc on map: http://perldoc.perl.org/functions/map.html
In short you should use little helper like parens or +-symbol so perl will be able to parse {...} construct correctly:
my $r = someSubroutine( map { + 0 => $_ } #hosts)

Convert Hash To Array in Perl Catalyst

I need help regarding handling of Perl variables. Here I am getting input as a hash. I now need to send this hash variable to another subroutine. How can pass data as an argument to another subroutine? The code below shows how I am approaching this:
if ($csData->{'CUSTOMER_INVOICE_DETAILS'})
{
$c->log->debug("API Response:". Dumper $csData->{'CUSTOMER_INVOICE_DETAILS'});
my $Charges = [];
my #customerCharges = $csData->{'CUSTOMER_INVOICE_DETAILS'};
foreach(#customerCharges)
{
my ($customername,$customeramount) = split /:/;
my $charge_hash = ({
customername => $customername,
customeramount => $customeramount
});
push(#$Charges, $charge_hash);
}
my #ReturnCharges = $self->API->get_customer_charges($Charges, $Customer->customerid, $params->{'invoiceid'});
The other subroutine where this data is being received is as follows:
sub get_customer_charges
{
my $self = shift;
my ($charge, $CustomerId, $INID) = #_;
my $http_request = {
action => 'GetTariff',
customerid => $CustomerId,
csid => $INID,
};
my $markups = $self->APIRequest($http_request);
###Charge Level ID Inserting As 10
my #ChargeLevels;
my #BaseLevelID;
foreach my $ch (#$charge)
{
my ($customername,$customeramount) = split(':', $ch->{'customername'}, $ch->{'customername'});
my $chargelevel = join(':', $ch->{'customername'}, $ch->{'customeramount'}, '10');
push(#BaseLevelID, $chargelevel);
}
push(#ChargeLevels, #BaseLevelID);
return #ChargeLevels;
}
When I print to the server log for CUSTOMER_INVOICE_DETAILS variable I am getting the following values:
API Response:$VAR1 = {
'Product' => '34.04',
'basetax' => '2.38',
'vattax' => '4.36'
};
After sending data to second subroutine the data coming in server log for second subroutine variable is as following:
Charges in API:$VAR1 = 'HASH(0xb75d6d8)::10';
Can anyone help how could I send the hash data from one subroutine to another?
Given your comments and that your source is:
API Response:$VAR1 = {
'Product' => '34.04',
'basetax' => '2.38',
'vattax' => '4.36'
};
And you're looking for:
API Response:$VAR1 = { 34.04:2.38:4.36:10 };
(and somehow you're getting:
Charges in API:$VAR1 = 'HASH(0xb75d6d8)::10';
This suggests this may be as simple as using the values system call. values extracts an array of all the values in the hash. Something like this (guessing a bit on which part of your code needs it).
my #list_of_values = values ( %{$csData->{'CUSTOMER_INVOICE_DETAILS'}} );
You say you want to "convert" a hash to an array, but your issue seems more complex and subtle so simple conversion is not likely what will solve your problem. Something in your subroutine is returning a hash reference when the rest of your code does not expect it to do so. If the data-structure you are passing contains the correct information but not in the form you expect, then you can either change the code to produce it in the expected form (e.g. to return an ARRAY) or change your subroutine so that it is able to handle the data that it is passed correctly.
As for "converting a hash" per se, if your data structure doesn't contain complex nested references and all you want to do is "convert" your hash to an array or list, then you can simply assign the hash to an array. Perhaps I'm not understanding your question but if this kind of simple "flattening" is all you want then you could try:
my $customer_purchase = {
'Product' => '34.04',
'basetax' => '2.38',
'vattax' => '4.36'
};
my #flat_customer_purchase = %{ $customer_purchase };
say "#flat_customer_purchase" ;
Output:
basetax 2.38 Product 34.04 vattax 4.36
You can then supply the hash data as the "array" to the second subroutine. e.g. treat #flat_customer_purchase as a list:
use List::AllUtils ':all';
say join " ", pairkeys #flat_customer_purchase
# basetax Product vattax
say join " ", pairvalues #flat_customer_purchase
# 2.38 34.04 4.36
etc.
NB: this assumes that for some reason you must pass an array. The example of running the pairvalues routine simply replicates #Sobrique's suggestion to use values directly on the hash you are passing but in my answer this grabs the values pairs from the array instead of the hash.
My sense is that there is more to the question. If API Response is a more complicated hash/object or, if for some other reason this basic perl doesn't work, then you will have to supply more information. You need to find out where your unexpected hash reference is coming from before you can decide how to handle it. You might find this SO discussion helpful:
Are Perl subroutines call-by-reference or call-by-value?

map perl looping from value

I am not able to understand why following code is not working.
<%method getvivekBox>
<%args>
$BoxName
</%args>
<%perl>
return {
type => 'vivek',
};
</%perl>
</%method>
<%method getAll>
<%args>
$BoxGroup
$indexex
</%args>
<%perl>
my $x = map { $m->comp('SELF:getvivekBox' , BoxName => "$BoxGroup-$_"); } #$indexex;
return $x;
</%perl>
</%method>
I am calling getAll with arguments , box and (1..10). It is returning 11 but expected behavior for me it should return 10 elements.
map on a list produces another list. You are assigning the result of the map to a scalar, thus getting the number of elements assigned to $x.
Change $x to #x, or put [] around the map (to make $x an array reference).

Getting an "Odd number of elements in hash assignment" error

I'm getting the following error when running a Perl script
Odd number of elements in hash assignment at GenerateInterchangeFromIntegrationManifest.pl line 197.
{
"Change list" : "0"
}
This is the script:
my %labelFieldMap = (IUItemName => convertIuItemName,
Changelist => sub {},
IUItemLevel => createNormalConvert('iuItemLevel'),
ContactPOC => \&convertContacts,
Cspec => \&convertCspec,
IsNew => createBooleanConvert('isNew'),
Submitter => createNormalConvert('submitter'),
LabelType => createNormalConvert('type'),
Revision => createNestedConvert('component', 'revision'),
RevisionName => sub {},
ComponentBaseName => createNestedConvert('component', 'baseName'),
Version => createNestedConvert('component', 'version'),
PLMapping => createNormalConvert('plMapping'),
BidMapping => createNormalConvert('bidMapping'),
ClientId => createNormalConvert('clientId'),
Path => \&convertPath,
ExtendedData => \&convertExtendedData);
Can any one help me resolve this issue?
There are several subroutine calls in assignment to the hash that could be returning lists with an even number of elements (which would make the list count odd overall, and also change which data is keys and which values from that point in the list on, which is probably worse for you). As Dallaylaen has pointed out in comments, this could simply be a line which returns "nothing", return; which will evaluate to empty list (), i.e. an even length of 0, in list context. All the subroutine calls in the question code will be evaluated in list context.
I would suggest a simple debug technique:
Comment out all the lines with a function call, that should remove the warning.
Then add back a few at a time and re-test.
When the warning re-appears, you will have isolated the problem to one of a few subroutines.
Repeat until you know which one.
Then investigate that call to see how you might fix it.