Perl: скобки против скобок для определения массива, почему считается скаляр?

Я следовал этому руководству по модулю HTML::Template для Perl. Вот шаблон:

<!--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>

И вот тестовая программа CGI:

#!"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();

Это терпит неудачу со следующей ошибкой: HTML::Template::param() : attempt to set parameter 'language' with a scalar - parameter is not a TMPL_VAR!

Однако, когда я меняю определение @rows с круглых скобок (с my @rows=(...) в my @rows = [...]) код работает нормально; он отображает таблицу с данными.

Как я понял из прочтения этой статьи, первая форма - это массив, определенный из списка, а вторая - ссылка на анонимный массив. Мне все еще не ясно, почему первая форма не работает. Буду признателен за разъяснение этого для меня.

3 ответа

Решение

Учебник, который вы читаете, содержит ошибку. Линия

$template->param( language => @languages );

должно быть

$template->param( language => \@languages );

Зачем? Краткий ответ: правая часть имени цикла, которое вы передаете param должен быть ссылкой на массив, а не массив.

Длинный ответ: когда вы передаете аргументы функции или методу, все аргументы раскрываются в один длинный список. Это распространенный источник ошибок для начинающих. Таким образом, в вашем коде (и в коде учебника) вы не передаете два параметра param метод, вы передаете четыре (один для строки 'language' и три для элементов @languages.

Вот пример распутывания этого списка аргументов. Если у вас есть три переменные следующим образом:

my $scalar = 'bear';
my @array  = ('rat', 'moose', 'owl');
my %hash   = (mass => 500, units => 'kg');

и вы передаете их функции следующим образом:

some_function($scalar, @array, %hash);

тогда функция увидит восемь аргументов: 'bear', 'rat', 'moose', 'owl', 'mass', 500, 'units', а также 'kg'! Возможно, еще более удивительно, что два набора значений из хэша могут быть переданы в другом порядке, поскольку хэши не сохраняются и не извлекаются в определенном порядке.

Ваше решение изменить скобки на квадратные скобки работает, но не по очень веской причине. Списки в скобках (которые могут храниться в массивах или хэшах); квадратные скобки разграничивают ссылки на массивы. Таким образом, ваш квадратный код создает ссылку на анонимный массив, который затем сохраняется как первый (и единственный) элемент вашего именованного массива. @rows, Вместо этого вы должны либо сохранить ссылку на массив (разделенную квадратными скобками) в скалярной переменной (скажем, $rows), или вы должны использовать скобки, сохранить список в массиве @rowsи передать ссылку на этот массив param метод (используя обратную косую черту, как я делал выше с \@languages).

language => @rows 

средства

'language', $rows[0], $rows[1], $rows[2], ...

или же

language => $rows[0],
$rows[1] => $rows[2],
...

Ты хочешь

language => \@rows

param() Метод в HTML::Template принимает пары аргументов. Первое значение в паре - это имя переменной шаблона, которую вы хотите установить, а второе - это значение, в которое вы хотите установить эту переменную.

Таким образом, вы можете сделать вызов, который устанавливает одну переменную:

$template->param(foo => 1);

Или вы можете установить несколько переменных за один вызов:

$template->param(foo => 1, bar => 2, baz => 3);

По понятным причинам имена переменных, указанные в вашем вызове param() все должны быть переменными, которые определены в вашем шаблоне (либо как стандарт tmpl_var переменные или как цикл tmpl_loop переменные).

Если вы устанавливаете tmpl_loop переменная (как в данном случае), то ассоциированное значение должно быть ссылкой на массив, содержащий ваши значения. Существует некоторая попытка объяснить это в документации дляparam(), но я могу увидеть, как это может быть неясно, поскольку он просто делает это, показывая примеры в квадратных скобках (конструктор ссылки на массив), а не фактически объясняя требование.

Причина этого заключается в том, что список параметров, передаваемых подпрограмме в Perl, "сплющен" - поэтому массив разбит на отдельные элементы. Это означает, что при прохождении:

$template->param(languages => @rows);

Perl видит это как:

$template->param(languages => $row[0], $row[1] => $row[2]);

Элементы вашего массива являются хеш-ссылками. Это означает, что $row[1] будет интерпретироваться как строковая ссылка на хеш (что-то вроде "HASH(0x12345678)"), которая определенно не является именем одной из переменных в вашем шаблоне.

Так как же это исправить? Ну, есть несколько альтернатив. Вы наткнулись на плохого. Вы использовали такой код:

@rows = [ ... ];

Это создает @rows массив с единственным элементом, который является ссылкой на ваш реальный массив. Это означает, что:

$template->param(language => @rows);

интерпретируется как:

$template->param(language => $rows[0]);

И в качестве $rows[0] это ссылка на ваш массив, все это работает.

Гораздо лучше было бы явно передать ссылку на @rows,

@rows = ( ... ); # your original version

$template->param(language => \@rows);

Или создать ссылку на массив, хранящийся в скаляре.

$rows = [ ... ];

$template->param(language => $rows);

Там действительно нечего выбирать между этими двумя вариантами.

Тем не менее, я бы попросил вас подумать, почему вы тратите время на изучение HTML::Template. Прошло много лет с тех пор, как я видел, как это используется. Template Toolkit, похоже, стал фактически стандартным движком шаблонов Perl.

Другие вопросы по тегам