Последнее в категории Обучение

state (2)

| Нет комментариев

Хотя вчера я и написал, что ключевое слово state в перле похоже на static в C++, документация утверждает, что это не вполне так. Описано, впрочем довольно размыто.

Что еще нужно знать про state, кроме того, что эта инструкция стала доступна только с версии Perl 5.10?

Во-первых, state можно эмулировать и в ранних версиях.

{
    my $counter;
    sub counter {
        return ++$counter;
    }
}

print counter(), "\n"; # 1
print counter(), "\n"; # 2

Переменная $counter, оказываясь в замыкании, с одной стороны, недоступна для внешнего кода, а с другой, сохраняет свое значение между вызовами функции counter().

Во-вторых, для переменной, объявленной с ключевым словом state, возможно возвращать обычную ссылку, что дает возможность изменять значение переменной.

use v5.10;

sub counter {
    state $counter;
    ++$counter;
    return \$counter;
}

my $c = counter();
say ++$$c;        # 2
say ${counter()}; # 3

В-третьих, state-переменную допустимо инициализировать при создании, но это разрешено только для скаляров.

use v5.10;

sub counter {
    state $counter = 10;
    return ++$counter;
}

say counter(); # 11
say counter(); # 12

Попытка же инициализации списка или хеша приводит к ошибке:

state @a = (1, 2, 3);
Initialization of state variables in list context currently forbidden

state @a //= (1, 2, 3);
Can't modify private array in defined or assignment (//=)

В документации (perlsub) явно указано, что такие присвоения в языке не определены.

Тем не менее, при необходимости обойти это ограничение довольно просто.

state @a;
@a = (1, 2, 3) unless defined @a;

Теперь настало время передвать более сложные аргументы, чем простые строки. Нет ничего проще — use Storable.

Перед тем, как передать задачу воркерам, упаковываем аргументы:

my $c = 0;
for (qw (red orange yellow green blue violet)) {
    $tasks->add_task(
       'search' => freeze([$c++, "$_ $text"]),
       {
         on_complete => \&search_completed
       }
    );
}

А внутри функции воркера — распаковываем:

my $request = thaw($job->arg);
my $result = MySearch::search($request->[1]);

После того, как получен результат, с ним потребуется произвести те же манипуляции, но в обратную сторону: заморозить (freeze) результат, вернуть серверу gearmand, а на клиентре — растопить (thaw).

Правда или нет, но передача данных от воркера в вызывающую задачу — глючит (особенно в Gearman::XS::Worker), да и бесконечные freeze/thaw только уродуют код. Намного удобнее, если воркер не будет ничего возвращать, а вместо этого складировать свои результаты в общее хранилище (например, в базу данных), доступное вызывающему приложению.

Переделка скрипта для работы с Gearman весьма проста, если решение задачи запрограммировано таким образом, чтобы у нее были только вход и выход :-) В случае со вчерашним примером функция поиска, помещенная в воркер, состоит из двух строк:

sub search {
    my $job = shift;

    return MySearch::search($job->arg);
}

Клиентский код несколько усложняется: сначала необходимо создать пул задач, а затем дождаться их выполнения:

for (qw (red orange yellow green blue violet)) {
    $tasks->add_task(
        'search' => "$_ $text",
        {
            on_complete => \&search_completed
        } 
    );
}
$tasks->wait;

Сбор данных по окончании каждой задачи тоже крайне прост:

sub search_completed {
    my $result = shift;
   
    $html_result .= $$result;
}

Теперь осталось запустить с десяток отдельных воркеров, и скорость работы тестового скрипта возрастает на порядок: последовательные запросы на поиск картинок шести цветов зарубежной радуги завершались за 1-3 секунды, после модернизации — за 0,1-0,6. Задачи параллелятся на ура, но важно понимать, что это имеет смысл только для тех задач, которые по своей природе проводят много времени в ожидании ответа, не тратя процессорное время локальной машины.

На болгарском Perl-воркшопе я хочу немного рассказать о том, как на практике обрабатывать задачи, используя Gearman.

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

Отдельная задача минимальна: для заданного запроса $text найти на гугле несколько картинок разного цвета. В первом случае, когда используется цикл, работает такой код:

for (qw (red orange yellow green blue violet)) {
    $html_result .= MySearch::search("$_ $text");
}

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

Cравнить два списка — задача, которую без привлечения модулей (например, List::Compare) в перле решать не очень удобно.

Начинающих может удивить, что выражение @a == @b сравнивает не сами списки, а их размер.

С приходом Perl 5.10 возникает соблазн использовать для сравнения двух списков оператор смарт-матчинга (~~):

say @a ~~ @b;

Это хорошо работает до тех пор, пока в списках содержатся скаляры, например:

my @a = (1, 3, 5);
my @b = (1, 3, 5);
say @a ~~ @b; # 1

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

Поэтому следующий пример тоже напечатает единицу, хотя исходные списки — разные:

my @a = (1, 3, 5);
my @b = (1, qr/\d/, 5);
say @a ~~ @b; # 1

Сегодня в рассылке итальянских Perl-монгеров появилось вот такое письмо (оригинал требует регистрации):

Scusate la domanda stupida che mi permetto di fare a voi esperti. Sto studiando
il PERL per l'applicazione OTRS. Ho notato che molti moduli finiscono con un
'1;' Ha un significato ben preciso? Una label? Un return-code? Ho provato a cercare, ma purtroppo con scarso successo! Grazie dell'aiuto.

Простите за глупый вопрос, который я хотел бы задать вам экспертам. Я изучаю Perl по приложению OTRS. Заметил, что многие модули заканчиваются на '1;'. Это имеет какой-то смысл? Метка? Код возврата? Я попробовал поискать, но, к сожалению, неудачно! Спасибо за помощь.

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

Про эту единицу я спрашивал старшего коллегу десять лет назад — когда впервые ее увидел, было совершенно непонятно, зачем она нужна. (Впрочем, сейчас, когда понятны требования языка, становится понятно, что true после загрузки модуля в общем-то не нужен.)

1; — то, про что обязательно нужно писать в книгах для начинающих отдельным абзацем.

Разумеется, допустимо возвращать не только единицу, а любое число, преобразующееся в true. Необычные варианты собраны на странице returnvalues.useperl.at/values.html. Самое частое значение — 42 :-)

Соль минор

| Нет комментариев

Я хотел написать про одно, а напишу про другое.

Сегодня мне стало интересно, что на спане умеет рисовать ноты. Оказалось, не так уж и много. Есть пара интересных модулей, которые умеют делать картинки с клавишами, которые нужно нажать, получая на входе название аккорда.

Например, модуль GD::Chord::Piano рисует такие простые картинки:

chord.png

Интерфейс крайне прост и приятен:

use GD::Chord::Piano;
my $im = GD::Chord::Piano->new;
print $im->chord('Gm')->png;

Все, что требуется, — передать название аккорда методу chord. Этот пример скопирован из документации к модулю.

А вот другой модуль — Music::Image::Chord, который, судя по названию и описанию, должен делать нечто похожее.

Но если скопировать пример из документации, то ничего не работает:

Use of uninitialized value in division (/) at /usr/local/lib/perl5/site_perl/5.10.0/Music/Image/Chord.pm line 152.
Can't use an undefined value as a HASH reference at /usr/local/lib/perl5/site_perl/5.10.0/Music/Image/Chord.pm line 153.

Нет, можно конечно продолжить разбираться, но не хочется.

К сожалению, такое весьма часто такое случается с примерами в книгах, причем не из-за того, что у автора и читателя оказались компиляторы разных версий. А ведь это так просто — скопипейстить код из набранной рукописи и проверить ее.

У разработчиков периодически возникают ситуации, когда они уверены в написанном коде и не проверяют его. Ну разве можно ошибиться, исправив один-два символа в регулярном выражении на боевом сервере? А вот можно :-) Сила опытного разработчика не в том, что он может писать код с закрытыми глазами, а в том, что он не стремается проверять себя.

В качестве офтопика можно вспомнить историю, расказанную создателем Бейсика Томасом Курцом о том, что какой-то аспирант написал компилятор PL/1 без отладки, впервые запустив его только после того, как код был полностью написан:

...he worked at Darthmouth in the computer center. He wrote a PL/1 compiler, and it's a big thing, and he checked it, and looked at it, and so on, but he never tested it, he never ran it until it was all done. You know, 20,000 or 30,000 lines of code, and the only test he did was to read it. Then he ran it and it worked the first time!

В той же книге рассказано и о том, что означат отладка для создателя C++ Бьярна Страуструпа:

— How do you debug? Do you have any suggestion for C++ developers?
— By introspection. I study the program for so long and poke at it more or less systematically for so long that I have sufficient understanding to provide an educated guess where the bug is.

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

({}) vs. ()

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

Сколько существует YAPC::TV, столько существует и небольшой вспомогательный скрипт, который автоматизирует работу по конвертированию видеофайлов в разные форматы.

В этой программе есть функция convert, которая принимает ссылку на хеш:

sub convert {
    my $param = shift;

И затем разыменовывает ее:

my $s = "-s $$param{s}" if $$param{s};

При этом каждый вызов convert содержит обилие скобок — одни для вызова функции, другие для создания анонимного хеша:

convert({
    type => 'mpg',
    s => '480x270',
    r => 25,
    b => '512kb',
    ar => 22050,
});

Конечно, можно было бы вторые скобки не ставить вообще, а в функции принимать готовый хеш:

sub convert {
    my %param = @_;

Но тут возникает вопрос о том, как этому научить начинающего программиста. Конструкции вида %hash = @array не так просто понять в начале изучения языка, как это кажется после нескольких лет использования перла.

Хотя различие между следующими двумя строками схватывается на ура:

$a = @a;
($a) = @a;

Предварительное видео с белорусского воркшопа.

Андрей Костенко. ‎DBIx::Class — базы данных в стиле Perl‎. 80 минут.

Я вот подумал, что обучать регулярным выражениям нужно сразу в режиме с модификатором x. Точнее, первые пару примеров по поиску совпадений с одной-двумя буквами или строками достаточно показать и без x, но всё более сложное — только в расширенном режиме.

Причины две. Во-первых, синтаксис регулярных выражений для непосвещенного человека сам по себе не слишком выразительный. Во-вторых, это даст возможность более легко адаптироваться к регексам Perl 6.

Предлагать читать Фридла начинающим не стоит. Недавно вышедший в свет кукбук по регулярным выражениям, хотя и содержит меньше теории, тоже будет интересен прежде всего тем, кто уже немного разобрался в теме. К тому же обе книги не расчитаны исключительно на Perl-аудиторию.

А вот в современном учебнике для начинающих нужно давать «переходную» версию от Perl 5 к Perl 6, хотя бы на уровне расстановки пробелов. Да и вообще, более или менее сложное регулярное выражение много легче и приятнее, когда оно записано в расширенном режиме (даже без комментариев).

Страницы

  • img

Об архиве

Эта страница содержит последние записи категории Обучение.

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

Следующая категория — Презентации.

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