Perl: Parentheses vs Brackets for array definition, why is one considered a scalar? - arrays

I was following this tutorial on the HTML::Template module for Perl. Here's the template:
<!--template2.tmpl-->
<html>
<body>
<table>
<tr>
<th>Language</th>
<th>Description</th>
</tr>
<tmpl_loop name="language">
<tr>
<td><tmpl_var name="language_name"></td>
<td><tmpl_var name="description"></td>
</tr>
</tmpl_loop>
</table>
</body>
</html>
And here's the CGI test program:
#!"C:\Strawberry\perl\bin\perl.exe" -wT
use CGI qw(:all);
use CGI::Carp qw(fatalsToBrowser);
use HTML::Template;
my #rows = (
{
language_name => 'C#',
description => 'Created by Microsoft'
},
{
language_name => 'PHP',
description => 'Hypertext Preprocessor'
},
{
language_name => 'Haskell',
description => 'Functional language'
},
);
print header;
my $template=HTML::Template->new(filename=>'template2.tmpl');
$template->param(language => #rows);
print $template->output();
This fails with the following error: HTML::Template::param() : attempt to set parameter 'language' with a scalar - parameter is not a TMPL_VAR!
However, when I change the definition of #rows from using parenthesis to using square brackets(from my #rows=(...) to my #rows = [...]) the code works fine; it displays a table with the data.
As I understood from reading this article, the first form is an array defined from a list and the second one is a reference to an anonymous array. It's still not clear to me why the first form doesn't work. I'd appreciate you clarifying this for me.

The tutorial you're following contains an error. The line
$template->param( language => #languages );
should be
$template->param( language => \#languages );
Why? Short answer: the right-hand side of the loop name you pass to param must be a reference to an array, not an array.
Long answer: When you pass arguments to a function or method, all of the arguments get expanded into one long list. This is a common source of mistakes for beginners. So in your code (and in the tutorial's code), you're not passing two parameters to the param method, you're passing four (one for the string 'language', and three for the elements of #languages.
Here's an example of this argument-list unraveling. If you have three variables as follows:
my $scalar = 'bear';
my #array = ('rat', 'moose', 'owl');
my %hash = (mass => 500, units => 'kg');
and you pass them to a function like so:
some_function($scalar, #array, %hash);
then the function will see eight arguments: 'bear', 'rat', 'moose', 'owl', 'mass', 500, 'units', and 'kg'! Perhaps even more surprising, the two sets of values from the hash might be passed in a different order, because hashes are not stored or retrieved in a determinate order.
Your solution of changing the parentheses to square brackets works, but not for a very good reason. Parentheses delimit lists (which can be stored in arrays or hashes); square brackets delimit references to arrays. So your square-bracket code creates a reference to an anonymous array, which is then stored as the first (and only) element of your named array #rows. Instead, you should either store the array reference (delimited with square brackets) in a scalar variable (say, $rows), or you should use parentheses, store the list in the array #rows, and pass a reference to that array to the param method (by using a backslash, as I did above with \#languages).

language => #rows
means
'language', $rows[0], $rows[1], $rows[2], ...
or
language => $rows[0],
$rows[1] => $rows[2],
...
You want
language => \#rows

The param() method in HTML::Template takes pairs of arguments. The first value in the pair is the name of a template variable that you want to set and the second is the value that you want to set that variable to.
So you can make a call that sets a single variable:
$template->param(foo => 1);
Or you can set multiple variables in one call:
$template->param(foo => 1, bar => 2, baz => 3);
For reasons that should be obvious, the variable names given in your call to param() should all be variables that are defined in your template (either as standard tmpl_var variables or as looping tmpl_loop variables).
If you're setting a tmpl_loop variable (as you are in this case) then the associated value needs to be a reference to an array containing your values. There is some attempt to explain this in the documentation for param(), but I can see how it might be unclear as it just does it by showing examples in square (array reference constructor) brackets rather than actually explaining the requirement.
The reason for this is that the list of parameters passed to a subroutine in Perl is "flattened" - so an array is broken up into its individual elements. This means that when you pass:
$template->param(languages => #rows);
Perl sees it as:
$template->param(languages => $row[0], $row[1] => $row[2]);
The elements of your array are hash references. This means that $row[1] will be interpreted as a stringified hash reference (something like "HASH(0x12345678)") which definitely isn't the name of one of the variables in your template.
So how do we fix this? Well, there are a few alternatives. You have stumbled over a bad one. You have used code like this:
#rows = [ ... ];
This creates #rows an array with a single element which is a reference to your real array. This means that:
$template->param(language => #rows);
is interpreted as:
$template->param(language => $rows[0]);
And as $rows[0] is a reference to your array, it all works.
Far better would be to explicitly pass a reference to #rows.
#rows = ( ... ); # your original version
$template->param(language => \#rows);
Or to create an array reference, stored in a scalar.
$rows = [ ... ];
$template->param(language => $rows);
There's really nothing to choose between these two options.
However, I would ask you to consider why you are spending time teaching yourself HTML::Template. It has been many years since I have seen it being used. The Template Toolkit seems to have become the de-facto standard Perl templating engine.

Related

In Perl 6, can I use an Array as a Hash key?

In the Hash documentation, the section on Object keys seems to imply that you can use any type as a Hash key as long as you indicate but I am having trouble when trying to use an array as the key:
> my %h{Array};
{}
> %h{[1,2]} = [3,4];
Type check failed in binding to parameter 'key'; expected Array but got Int (1)
in block <unit> at <unknown file> line 1
Is it possible to do this?
The [1,2] inside the %h{[1,2]} = [3,4] is interpreted as a slice. So it tries to assign %h{1} and %{2}. And since the key must be an Array, that does not typecheck well. Which is what the error message is telling you.
If you itemize the array, it "does" work:
my %h{Array};
%h{ $[1,2] } = [3,4];
say %h.perl; # (my Any %{Array} = ([1, 2]) => $[3, 4])
However, that probably does not get what you want, because:
say %h{ $[1,2] }; # (Any)
That's because object hashes use the value of the .WHICH method as the key in the underlying array.
say [1,2].WHICH; say [1,2].WHICH;
# Array|140324137953800
# Array|140324137962312
Note that the .WHICH values for those seemingly identical arrays are different.
That's because Arrays are mutable. As Lists can be, so that's not really going to work.
So what are you trying to achieve? If the order of the values in the array is not important, you can probably use Sets as keys:
say [1,2].Set.WHICH; say [1,2].Set.WHICH
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
Note that these two .WHICHes are the same. So you could maybe write this as:
my %h{Set};
dd %h{ (1,2).Set } = (3,4); # $(3, 4)
dd %h; # (my Any %{Set} = ((2,1).Set) => $(3, 4))
Hope this clarifies things. More info at: https://docs.raku.org/routine/WHICH
If you are really only interested in use of an Object Hash for some reason, refer to Liz's answer here and especially the answers to, and comments on, a similar earlier question.
The (final1) focus of this answer is a simple way to use an Array like [1,'abc',[3/4,Mu,["more",5e6],9.9],"It's {<sunny rainy>.pick} today"] as a regular string hash key.
The basic principle is use of .perl to approximate an immutable "value type" array until such time as there is a canonical immutable Positional type with a more robust value type .WHICH.
A simple way to use an array as a hash key
my %hash;
%hash{ [1,2,3].perl } = 'foo';
say %hash{ [1,2,3].perl }; # displays 'foo'
.perl converts its argument to a string of Perl 6 code that's a literal version of that argument.
say [1,2,3].perl; # displays '[1, 2, 3]'
Note how spaces have been added but that doesn't matter.
This isn't a perfect solution. You'll obviously get broken results if you mutate the array between key accesses. Less obviously you'll get broken results corresponding to any limitations or bugs in .perl:
say [my %foo{Array},42].perl; # displays '[(my Any %{Array}), 42]'
1 This is, hopefully, the end of my final final answer to your question. See my earlier 10th (!!) version of this answer for discussion of the alternative of using prefix ~ to achieve a more limited but similar effect and/or to try make some sense of my exchange with Liz in the comments below.

Storing values obtained from for each loop Scala

Scala beginner who is trying to store values obtains in a Scala foreach loop but failing miserably.
The basic foreach loop looks like this currently:
order.orderList.foreach((x: OrderRef) => {
val references = x.ref}))
When run this foreach loop will execute twice and return a reference each time. I'm trying to capture the reference value it returns on each run (so two references in either a list or array form so I can access these values later)
I'm really confused about how to go about doing this...
I attempted to retrieve and store the values as an array but when ran, the array list doesn't seem to hold any values.
This was my attempt:
val newArray = Array(order.orderList.foreach((x: OrderRef) => {
val references = x.ref
}))
println(newArray)
Any advice would be much appreciated. If there is a better way to achieve this, please share. Thanks
Use map instead of foreach
order.orderList.map((x: OrderRef) => {x.ref}))
Also val references = x.ref doesn't return anything. It create new local variable and assign value to it.
Agree with answer 1, and I believe the reason is below:
Return of 'foreach' or 'for' should be 'Unit', and 'map' is an with an changed type result like example below:
def map[B](f: (A) ⇒ B): Array[B]
Compare To for and foreach, the prototype should be like this
def foreach(f: (A) ⇒ Unit): Unit
So If you wanna to get an changed data which is maped from your source data, considering more about functions like map, flatMap, and these functions will traverse all datas like for and foreach(except with yield), but with return values.

Perl array referencing reversing

I am newbie to perl. And have been working with CSV files, JSON strings, arrays and hashes.
I have written this code, but it is giving me error. I want to use $header_copy in foreach loop.
1. my #headers=qw/January February March April May June/;
2. my $header_copy=\#headers;
3. print("$header_copy->[2]"); # Prints "March" Correctly
4. print("$header_copy[2]"); #Gives Error.
Error:
Global symbol "#header_copy" requires explicit package name at line 4
And I want to use $header_copy in for loop: like:
foreach $i ($header_copy){ **code..** }
You are taking the reference of #headers array using \#headers into $header_copy. So, before accessing it, you need to dereference it.
There are two ways(actually more than that) for it:
Using Arrow operator(->) - Most suitable for accesing a single item from arrayref
Using # { } - Suitable for iterating over arrayrefs .
$header_copy[2] will give error because you are accessing an element from arrayref without dereferencing it. The interpreter assumes it as an array #header_copy not an arrayref because the syntax says it.
Below program summarizes both approach:
#!/usr/bin/perl
use strict;
use warnings;
# define # headers
my #headers = qw/January February March April May June/;
# taken refrence of #headers array into $header_copy
my $header_copy = \#headers;
# dereferencing using arrow(->) operator
print $header_copy -> [2],"\n";
# derefrencing for iteration using #{...}
foreach(#{ $header_copy }) {
print $_,"\n";
}
The error 'Global symbol "..." requires explicit package name' is Perl-speak for "you're trying to use an undeclared variable". It even tells you the name of the undeclared variable - in this case it's #header_copy.
And if you look in your code, you'll see there's no declaration for an array called #header_copy. Oh, you have a scalar variable called $header_copy. And that contains a reference to an array (the array #headers). But that has no connection to an array called #header_copy.
So why does Perl think you want to use the array #header_copy? Well in the last line of your code, you use $header_copy[2] - which means "the third element in array #header_copy". And that generates an error because (as I've already pointed out) you don't have that array.
On the previous line you use $header_copy->[2]. And that works fine because ->[...] is the correct way to look up an element in an array that you have a reference to.
The important thing to realise is that $header_copy->[2] and $header_copy[2] mean two completely different things and refer to two completely different variables.
You also ask how you can get back to an array that you have a reference to (this is called "dereferencing"). That is simple. In general you use:
#{ expression that returns an array reference }
So, in your case, that would be:
#{ $header_copy }
But in cases where your expression is a scalar variable, you can simplify it by omitting the braces, so it becomes:
#$header_copy
So you want:
foreach my $i (#$header_copy) {
...
}
# reads data
my #headers=qw/January February March April May June/;
# use a foreach loop and store local copy of item nr in $headeritem_copy
foreach my $headeritem_copy (#headers) {
print("$headeritem_copy\n");
}
my #headers=qw/January February March April May June/;
my $header_copy=\#headers;
print("$header_copy->[2]"); # Prints "March" Correctly
De-reference the particular index
print("#{$header_copy}->[2]"); #Gives Error.

Perl data structures - looping an array of hashes inside a hash

I need a data structure to keep metadata about a field in a database, which I'm going to access to write dynamic SQL.
I'm using a hash to store things like the name, maybe data type, etc. And most importantly, an array of hashes containing information about the values I want to query out of the field, and the name I want to alias them with.
When I try to access elements of that array, I get:
Global symbol "%elem" requires explicit package name at test.pl line 18.
It sounds like maybe it's having trouble registering the fact that the loop variable representing the array elements is a hash, not a scalar. If I try:
foreach my %elem
then I get:
Missing $ on loop variable at test.pl line 17 (#1)
So far I can't find the relevant Perl documentation that addresses this.
#!/usr/local/bin/perl
use warnings;
use strict;
use diagnostics;
use POSIX 'strftime';
my %struct = (
#"field" = "foobar",
"values" => [
{value => "Y", name => "FOO"}
, {value => "N", name => "BAR"}
]
);
foreach my $elem (#{$struct->{'values'}}) {
print $elem->{'value'};
}
I expect the program to print "YN" to the console.
UPDATE, as someone pointed out I needed to use %hash->{'ref'} in the loop addressing. I added it. Now, I get a notification saying that using a hash as a reference is deprecated (?) but it is printing to the console now!
When I tried running your code, I got a different error than you reported:
Global symbol "$struct" requires explicit package name
This is because you've defined a hash %struct, not a hashref $struct, so you don't need to dereference it. Thus, I changed the line
foreach my $elem (#{$struct->{'values'}}) {
to
foreach my $elem (#{$struct{'values'}}) {
(note no -> to dereference) and it ran perfectly, no errors or warnings, and emitted the output
YN
as expected.
%struct is a hash, not a hash reference. Therefore, $struct->{'values'} is not the correct way to access the values key.
for my $elem (#{$struct{values}}) {
print "$elem->{value}\n";
}

Perl -- DBI selectall_arrayref when querying getting Not Hash Reference

I am very new to perl (but from a c# background) and I am trying to move some scripts to a windows box.
Due to some modules not working easily with windows I have changed the way it connects to the DB.
I have an sqlserver DB and I had a loop reading each row in a table, and then within this loop another query was sent to select different info.
I was the error where two statements can't be executed at once within the same connection.
As my connection object is global I couldn't see an easy way round this, so decided to store the first set of data in an array using:
my $query = shift;
my $aryref = $dbh->selectall_arrayref($query) || die "Could not select to array\n";
return($aryref);
(this is in a module file that is called)
I then do a foreach loop (where #$s_study is the $aryref returned above)
foreach my $r_study ( #$s_study ) {
~~~
my $surveyId=$r_study->{surveyid}; <-------error this line
~~~~
};
When I run this I get an error "Not a hash reference". I don't understand?!
Can anyone help!
Bex
You need to provide the { Slice => {} } parameter to selectall_arrayref if you want each row to be stored as a hash:
my $aryref = $dbh->selectall_arrayref($query, { Slice => {} });
By default, it returns a reference to an array containing a reference to an array for each row of data fetched.
$r_study->{surveyid} is a hashref
$r_study->[0] is an arrayref
this is your error.
You should use the second one
If you have a problem with a method, then a good first step is to read the documentation for that method. Here's a link to the documentation for selectall_arrayref. It says:
This utility method combines
"prepare", "execute" and
"fetchall_arrayref" into a single
call. It returns a reference to an
array containing a reference to an
array (or hash, see below) for each
row of data fetched.
So the default behaviour is to return a reference to an array which contains an array reference for each row. That explains your error. You're getting an array reference and you're trying to treat it as a hash reference. I'm not sure that the error could be much clearer.
There is, however, that interesting bit where it says "or hash, see below". Reading on, we find:
You may often want to fetch an array
of rows where each row is stored as a
hash. That can be done simple using:
my $emps = $dbh->selectall_arrayref(
"SELECT ename FROM emp ORDER BY ename",
{ Slice => {} }
);
foreach my $emp ( #$emps ) {
print "Employee: $emp->{ename}\n";
}
So you have two options. Either switch your code to use an array ref rather than a hash ref. Or add the "{ Slice => {} }" option to the call, which will return a hash ref.
The documentation is clear. It's well worth reading it.
When you encounter something like "Not a hash reference" or "Not an array reference" or similar you can always take Data::Dumper to just dump out your variable and you will quickly see what data you are dealing with: arrays of arrayrefs, hashes of something etc.
And concerning reading the data, this { Slice => {} } is most valuable addition.

Resources