Последнее в категории Язык

Замечательная задача для собеседования.

Дано: хеш вида

my %what_when = (
    13 => 'Lunch',
    17 => 'Tea',
    20 => 'Dinner',
     9 => 'Breakfast',
);

Требуется: сформировать список пар, отсортированных по ключу хеша (в примере — по времени):

my @schedule = (
    [9,  'Breakfast'],
    [13, 'Lunch'],
    [17, 'Tea'],
    [20, 'Dinner'],
);

В первой части были приведены три программы, работающие под Perl 5.12, и предлагалось определить, что они выведут.

А выведут они следующее.

Программа А.

use v5.12;
my @letters = 'a'..'z';
for (my ($number, $char) = each @letters) {
    say "$number $char";
}

0 a
0 a

Программа Б.

use v5.12;
my @letters = 'a'..'z';
say $_ for each @letters;

0
a

Программа В.

use v5.12;
my @letters = 'a'..'z';
say $_ foreach @letters;

a
b
c
. . .
x
y
z

Британец Барри Уолш пишет в своем блоге о важной особенности работы оператора each в Perl 5.12.

Вызов each, примененный к массиву, возвращает пару величин: индекс и значение очередного элемента. Очевидно, что в пределах цикла (напрмер, while) each сохраняет указатель на текущую позицию.

use v5.12;

my @letters = 'a'..'z';
while (my ($number, $char) = each @letters) {
    say "$number $char";
}

Однако, менее очевидно, что если преравать итерацию, то при следующем вызове нумерация будет продолжена с прежней позиции. Например:

use v5.12;

my @letters = 'a'..'z';
while (my ($number, $char) = each @letters) {
    say "$number $char";
    last if $number == 15;
}

say "Second loop";
while (my ($number, $char) = each @letters) {
    say "$number $char";
}

В этом примере напечатается следующее:

0 a
1 b
2 c
. . .
14 o
15 p
Second loop
16 q
17 r
. . .
24 y
25 z

Видно, что each продолжил с того же места, где остановился в первом цикле.

Сбросить счетчик удается вызовом keys @array или values @array. Также важно, что изменение массива (с помощью push или pop) и даже присвоение новых значений не изменяет позицию счетчика:

use v5.12;

my @letters = 'a'..'z';
while (my ($number, $char) = each @letters) {
    say "$number $char";
    last if $number == 15;
}

@letters = 'A'..'Z';
say "Second loop";
while (my ($number, $char) = each @letters) {
    say "$number $char";
}

В этом случае второй цикл упрямо продолжит с того же места, но уже с другим содержанием:

0 a
1 b
2 c
. . .
14 o
15 p
Second loop
16 Q
17 R
. . .
24 Y
25 Z

В операторе when — будь он внутри «родного» блока given или внутри цикла for — обычно происходит смартматчинг переменной $_ с указанным выражением. В документации perlsyn перечислены искючения, однако есть и не слишком очевидное поведение, о котором нужно знать.

Вначале о том, что явно указано как исключения. Блок when(EXPR) не выполняет смартматчинг $_ ~~ EXPR, а является просто булевым выражением, если EXPR — одно из следущего:

  • Вызов функции или метода.
  • Регулярное выражение (тут надо помнить, что операторы =~ и ~~ ведут себя по-разному).
  • Сравнение (как с переменной по умолчанию, так и с любой друой) или явный смартматчинг.
  • Отрицание (!, not или xor).
  • Файловый тест.
  • Операторы .. и ....

А теперь о том, где можно наступить на грабли. В предыдущей заметке встречались выражения типа

when(0b1110_0000 == ($_ & 0b1111_0000))

Дополнительная проверка на равенство здесь необходима (даже если проигнорировать младшие биты). Прямолинейная битовая операция

when($_ & 0b1111_0000)

сравнивает переменную $_ со значением, полученным после битовой операции $_ & 0b1111_0000, и для проверки установленных битов нужно выполнять явное сравнение, которое входит в один из пунктов перечисленных выше исключений.

for vs. given

| 1 комментарий

Иногда вместо given удобно использовать for, например, чтобы сделать несколько проверок для каждого элемента массива.

Однако, эта легкость замены дает ложное ощущение возможности и обратного — использования given вместо for.

Есть такой старый трюк, когда for нужен лишь для того, чтобы топикализировать переменную и с легкостью пользоваться функциями, по умолчанию использующими $_.

Например:

for ($url) {
    s{\.?*}{};
    s{^/}{};
    s{/$}{};
}

Фактически никакого цикла здесь нет, и уж если подключен use 5.10, хочется использовать given. Однако, не тут-то было. Хотя given и создаст переменную $_, но она будет всего лишь лексической переменной в пределах блока given, а не синонимом $url.

use v5.10;
use strict;

my $string = 'Hello, Perl';
my $another = $string;

for ($string) {
    s/Hello/Hallo/;
    s/Perl/C++/;

}

given ($another) {
    s/Hello/Hi/;
    s/Perl/JavaScript/;

}

say $string;  # Hallo, C++
say $another; # Hello, Perl

Этот пример выведет измененную строку после блока с for и неизменную — после given. Будьте внимательны :-)

В серверном коде, обслуживащем сайт, блоки given/when весьма удобны в частности для того, чтобы последовательно протестировать URL запрошенного ресурса и выполнить соответствующие действия:

given($url) {
    when(m{^/$}) {home_page()}
    when(m{^/about/$} {about_page()}
    . . .
}

В Perl 5.10 помимо операторов given/when доступны именованные сохраняющие скобки, которые могут облегчить выделение параметров из адреса одновременно с его разбором:

given($url) {
    . . .
    when(m{^/news/(?<year>\d+)/(?<month>\d+)/(?<day>)/$})
        {news_that_day()}
}

Параметры, захваченные именованными скобками, попадают в хеши %+ и %-, которые следует передать дальше на обработку.

Однако, в зависимости от общей архитектуры (в нашем примере — схемы разбора URL) может проявиться ограниченность области видимости, в которой доступны упомянутые хеши. То есть сразу после закрывающей скобки, завершающей блок given, сохраненные значения перестают быть доступными.

given($url) {
    . . .
}
say Dumper(\%+); #
Пусто

В этом месте кода переменные %+ и %- сохраняют значения, полученные до начала блока given.

Вплоне логично, хорошо локализовано, но не всегда удобно.

Разумеется, возможно сохранить собранные данные непосредственно в блоке given, хотя при легкомысленном проектировании это легко приводит к большим фрагментам повторяющегося кода:

given($url) {
    . . .
    when(m{^/(?<type>this)_page/$})
        {%data = %+; do_this()}
    when(m{^/(?<type>that)_page/$})
        {%data = %+; do_that()}
}

Часть III
Как пишут другие

В этом разделе собраны и сгруппированы примеры использования новых возможностей Perl 5.10, которые встречаются в модулях на CPAN.

Как включить режим Perl 5.10

Поскольку новые ключевые слова могут сделать программу несовместимой с предыдущими версиями компилятора, необходимо дать явные указания, чтобы активировать соответствующие фичи Perl 5.10.

Cпособы, которыми пользуются авторы, как нельзя лучше демоснтрируют принцип TIMTOWTDI:

use 5.010000;
use 5.01001;
use 5.010;
use 5.010_000;
use 5.10.0;
use v5.10.0;
use v5.10;
use feature ':5.10';

Строки, начинающиеся с буквы v, либо содержащие две точки, называются v-string (version string или, иногда, vector string). При объявлении версии интерпретатора следущие варианты эквивалентны.

5.10.0
v5.10.0
v5.10

Обратите внимание, если при указании версии используется обычное число (с одной десятичной точкой), то следует писать не 5.10, а 5.010.

say для отладки

Функцию say удобно использовать во время отладки программы для вывода промежуточных значений переменных. На CPAN можно найти следы такой отладки — закомментированный вызов say.

given ($action) {
    when (/^include_cmd:/) {
        my $cmd = $child->content;
        $cmd =~ /^include_cmd:(\s*)/;
        my $ws = $1 || '';
        $cmd =~ s/^include_cmd:\s*//;
        #say("cmd:$ws$cmd");
        $cmd = cwd() . '/' . $cmd;
        @output = qx($cmd);
        $child->content($ws . join($ws, @output));
    }

Pod::Elemental::Transformer::Include — 08 Jan 2010
include output via files and commands

(Здесь и далее рядом с примером кода указано название модуля, откуда взят пример, дата выхода его первой версии и авторское описание; пунктуация, отступы и пробелы в коде в большинстве случаев сохранены.)

// и //= для значений по умолчанию

Пожалуй, самое распространенное применение оператора defined-or — установка значений по умолчанию.

В частности, удобно пользоваться вариантом с присвоением.

$port //=  5432;
$host //= 'localhost';

$col  //= '';

Pg::Loader — 07 Jul 2008
Perl extension for loading Postgres tables

$attrz{ maxjob  } //= 1;
$value //= 1;

$attrz{ $_ } //= 0;
$attrz{ verbose } //= '';
$attrz{ debug   } //= '';
$val //= 1;
$exit //= 0;
$skipz->{ $job_id } //= 'Skip on SIGHUP'

Parallel::Depend — 12 Aug 2009
Parallel-dependent dispatch of perl or shell code

Чуть более сложный пример — с обращением к встроенной функции:

sub import   {
    shift;
    my %args = @_;
    # we do not care about autoviv
    $^H{fixedtime} = $args{epoch_offset} // 
                     CORE::time;
}

fixedtime — 14 Aug 2008
lexical pragma to fix the epoch offset for time related functions

Defined-or используют и непосредственно при передаче аргументов функциям.

say $answer // 
    "I don't know enough to answer you yet.";

Hailo — 29 Jan 2010
A pluggable Markov engine analogous to MegaHAL

Следует обратить внимание, что оператор возможно использовать не только с истинными скалярами.

my $marpa_version  = 
$Parse::Marpa::VERSION // 'undef';
my $source_version = 
$Parse::Marpa::Source::VERSION // 'undef';

$options //= {};

Parse::Marpa — 14 Dec 2008
Generate Parsers from any BNF grammar

В следующем примере помимо многократного использования // интересно отметить, что say передается файловый дескриптор.

my $nulling_symbol =
  $rhs_symbol->[Parse::Marpa::Internal::Symbol::NULL_ALIAS] // $rhs_symbol;

$action //= $default_action;

say {$trace_fh}
    'Problems compiling action for original rule: ',
    Parse::Marpa::brief_original_rule($rule);

my $clone = $clone_arg // 1;
my $current_parse_set = $parse_set_arg // $default_parse_set;

$choice //= 0;

$lines //= [0];
$source_options //= {};

Parse::Marpa::Internal::Evaluator — 14 Dec 2008
Generate Parsers from any BNF grammar

Наконец, и сам файловый дескриптор удобно вписывается в работу с defined-or:

my $trace_fh = $arg_trace_fh // (*STDERR);

my $trace_fh = shift;
$trace_fh //= *STDERR;

Parse::Marpa::Recognizer — 14 Dec 2008
Generate Parsers from any BNF grammar

// внутри return

Оператор defined-or часто встречается внутри вызова return, опять же чтобы вернуть определенное значение, если оно не получено на предыдущих шагах.

return $self->_get_infection( $disease->id ) // 0;

my $val = $self->_get($key) // $default->{$key};


return @{ $self->_players // [] };

Games::Pandemic::City, Games::Pandemic::Config — 07 Sep 2009
Games::Risk — 18 Oct 2008

Иногда в одном из операндов // вызывают фукнции, которые могут привести к досрочному завершению программы или выходу из блока.

sub homedir {
  my ($self) = @_;
  require File::HomeDir;
  return File::HomeDir->my_home
    // croak 'File::HomeDir says you have no home
              directory';
}

App::RSS2Leafnode — 02 Feb 2010
post RSS feeds to newsgroups

А вот пример, когда операндом является целый блок кода do:

sub config_filename {
  my ($self) = @_;
  return $self->{'config_filename'} // do {
    require File::Spec;
    File::Spec->catfile ($self->homedir, '.rss2leafnode.conf');
  };
}

App::RSS2Leafnode — 02 Feb 2010
post RSS feeds to newsgroups

В частных случаях // действует лишь внутри части выражения, передаваемое return.

return 
isodate_to_rfc822($date // $self->{'now822'});

return URI::Title::title
    ({ url  => ($resp->request->uri // ''),
       data => $resp->decoded_content 
 (charset => 'none')});

App::RSS2Leafnode — 02 Feb 2010
post RSS feeds to newsgroups

return внутри //

Весьма распространена и противоположная конструкция, когда в одном из плеч // делают попытку возврата из подпрограммы.

my $b_time = $self->item_to_timet($b_item)
   // return $a_item;

my $a_time = $self->item_to_timet($a_item) 
   // return $b_item;;

my $str = $self->item_to_date($item) 
   // return;

App::RSS2Leafnode — 02 Feb 2010
post RSS feeds to newsgroups

// легко объединяется в цепочку, поэтому возможно написать конструкцию, которая делает несколько попыток присвоить defined-значение:

return (elt_to_email ($item->first_child('author'))
        // elt_to_email ($item   ->first_child('dc:creator'))
        // elt_to_email ($item   ->first_child('dc:contributor'))
        // non_empty ($item->first_child_text('wiki:username'))

        // elt_to_email ($channel->first_child('dc:creator'))
        // elt_to_email ($channel->first_child('author'))
        // elt_to_email ($channel->first_child('managingEditor'))
        // elt_to_email ($channel->first_child('webMaster'))

        // elt_to_email ($item   ->first_child('dc:publisher'))
        // elt_to_email ($channel->first_child('dc:publisher'))

        // non_empty ($channel->first_child_text('title'))

        # RFC822
        // 'nobody@'.$self->uri_to_host
       );

App::RSS2Leafnode — 02 Feb 2010
post RSS feeds to newsgroups

Возможен и подход, при котором возвращается одна-единственная переменная, которая перед этим проходит через несколько проверок с оператором defined-or:

sub item_to_language {
  my ($self, $item) = @_;
  my $content;
  my $ret = (elt_to_language($item)
             // elt_to_language($item->first_child('content')));
  for (;;) {
    $item = $item->parent // last;
    $ret //= elt_to_language($item);
  }
  $ret //= $self->{'resp'}->content_language;
  return $ret;
}

App::RSS2Leafnode — 02 Feb 2010
post RSS feeds to newsgroups

Несколько //

Объединение в цепочку нам уже встречалось, однако на нем следует задержать внимание еще раз.

my $captures    = $arg {captures}       // [];
my $comment     = escape $arg {comment} // $name // "";
my $upgrade     = $arg {utf8_upgrade}   // 1;
my $downgrade   = $arg {utf8_downgrade} // 1;
my $match       = $arg {match}          // 1;

Games::Wumpus — 24 Nov 2009
Play Hunt the Wumpus


Продолжение будет.

Часть I
История и статистика

Perl 5.10 увидел свет в 20 день рождения языка — 18 декабря 2007 года. Уже прошло более двух лет, и за это время успели появиться девелоперские версии 5.11.0, 5.11.1, 5.11.2, 5.11.3 и 5.11.4, и вот-вот появится версия 5.12, предназначенная для использования в реальных приложениях.

Perl 5.10 представил много нововведений, и сегодня интересно посмотреть, как часто они используются авторами модулей CPAN — модулей, появившихся после релиза 5.10, либо обновленных с того времени.

На CPAN сейчас хранится около 80 000 модулей в 20 000 дистрибутивах, список авторов содержит около 8000 имен. Как часто здесь используются фичи Perl 5.10?

Ответ: новые фичи встречаются в двух сотнях модулей, созданных сотней авторов.


Часть II
Фичи Perl 5.10

Кратко о том, что появилось в Perl 5.10.

— Встроенная функция say, которая работает аналогично print, но добавляет перевод строки.

— Оператор сопоставления (смартматчинга) ~~ сопоставляет свои аргументы согласно здравому смыслу (и, разумеется, четким правилам, описанным в документации). Например, в этих примерах аргументы имеют разный тип, но используется один и тот же оператор.

$a ~~ /\d/

$a ~~ @list

@list ~~ %hash

— Составной оператор выбора под условным названием switch. С помощью новых ключевых слов given, when и default возможно создать условную конструкцию, аналогичную switch/case в C и других языках. Важное отличие в том, что внутри given происходит не простое сравнение, а сопоставление (или смартматчинг).

given($x) {
    when(/a/) {...}
    when('b') {...}
    default   {...}
}

— Модификатор state позволяет создавать лексические переменные, сохраняющие значение между вызовами подпрограммы. Использование state похоже на объявление автоматической переменной со словом static в C.

sub counter {
    state $c = 0;
    return ++$c;
}

— Новшества в регулярных выражениях. Регулярные выражения существенно переработаны, в частности, добавлены именованные сохраняющие скобки. В этой статье про регулярные выражение почти не упоминается, поскольку они достойны отдельного рассказа.

(?<name>)
\K
\R
%+
%-
\g<name>

— Бинарный оператор defined-or (//). Возвращает первый из аргументов, который содержит определенное (не undef) значение. Может использоваться и в варианте с присвоением.

Следующие два примера иллюстрируют логику, которая часто прослеживается в сообщениях о вакансиях.

$city = $arg // 'Moscow';

$vacancy{city} //= 'Moscow';


Продолжение будет.

Из 49 ответов на вопрос «@var — это массив или список?», оставшихся после отсеивания накруток сторонников массивов, сложилась такая картина общественного мнения:

Что для вас @var?

| 1 комментарий

Как вы называете в повседневном обиходе переменную с именем @var? Массивом или списком?

Проголосуйте

Страницы

  • img

Об архиве

Эта страница содержит последние записи категории Язык.

Предыдущая категория — Тесты.

Следующая категория — Golf.

Смотрите новые записи на главной странице или загляните в архив, где есть ссылки на все сообщения.