Сегодня исполняется 24 года языку программирования Perl.
Последнее в категории Версии Perl
Презентация Дейва Кросса с прошедшего на днях лондонского перл-воркшопа с кратким обзором новых возможностей, появившихся в языке с версии 5.10.
Анатолий Шарифулин подготовил перевод презентации Джесси Винсента Perl 5.16 and Beyond, с которой он приезжал на несколько недавних YAPC.
14 мая 2011 вышла стабильная версия Perl 5.14. На YAPC::Russia в тот же день я сделал презентацию о некоторых интересных нововведениях.
20 апреля вышла предварительная версия Perl 5.14 — perl-5.14.0-RC1. Если за неделю не найдется страшных багов, то дистрибутив почти один в один станет очередной стабильной версией.
В этой серии публикаций будут рассмотрены новые возможности языка.
Модификатор r
Оператор замены s получил новый модификатор r, который предписывает возвратить результат замены, не изменяя оригинальную строку:
use v5.14;
my $version = "Perl 5.10";
say $version =~ s/5\.10/5.14/r; # На печати: Perl 5.14
Выделенный фрагмент делает запрошенную замену, но не изменяет прежнее значение переменной $version.
Использовать результат замены нужно с осторожностью, помня, что отсутствие модификатора r полностью меняет семантику оператора.
my $newversion = $version =~ s/5\.10/5.14/r;
my $count = $version =~ s/5\.10/5.14/;
В первом случае $newversion получит новое значение с заменой: Perl 5.14, а во втором — переменная $count лишь сообщит о совершенной замене. Оригинальное значение сохранится только при наличии r.
Пример 1
use v5.14;
use utf8;
use open qw(:std :utf8);
my $old = "Погода в Ленинграде";
my $new = $old =~ s/Ленинград/Санкт-Петербург/r;
say $old;
say $new;
$ perl s.pl
Погода в Ленинграде
Погода в Санкт-Петербурге
Пример 2
use v5.14;
use utf8;
use open qw(:std :utf8);
my $old = "Погода в Ленинграде";
my $new = $old =~ s/Ленинград/Санкт-Петербург/;
say $old;
say $new;
$ perl s.pl
Погода в Санкт-Петербурге
1
Модификатор r дает важное преимущество: теперь замену можно производить на неизменяемых литеральных строках, не создавая промежуточные переменные:
say "в Украину" =~ s/в/на/r;
Аналогичное действие модификтор r оказывает и на работу операторов подстановки tr и y:
my $their_language = 'ruby';
my $language = $their_language =~ tr/bruy/rpel/r;
say $language; # perl
Кстати, стоит напомнить, что начиная с Perl 5.12 указание версии (use v5.14) автоматически включает и режим strict.
В первой части были приведены три программы, работающие под 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 @a, разумеется не обязательно, можно и объяснить, но вопросы остаются теми же).
Программа А.
use v5.12;
my @letters = 'a'..'z';
for (my ($number, $char) = each @letters) {
say "$number $char";
}
Программа Б.
use v5.12;
my @letters = 'a'..'z';
say $_ for each @letters;
Программа В.
use v5.12;
my @letters = 'a'..'z';
say $_ foreach @letters;
Что эти программы напечатают и почему?
Британец Барри Уолш пишет в своем блоге о важной особенности работы оператора 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
Бывает, что какой-то возможностью либо настолько редко пользуешься, либо она настолько неудобна, что про нее забываешь напрочь.
Сегодня меня заново познакомили (hsw++) с модификатором регулярных выражений /m.
В дизайне регулярных выражений Perl 5 есть аж пять метасимволов, совпадающие с границами строк (физических или логических):
^ $ \A \Z \z
И на все это наслаивается модификатор /m, изменяющий действие первых двух из списка. И, до кучи, пара символов для переноса строк (\n и \R). Полное безобразие. Иными словами, отказ от /m в Perl 6 — очень правильное решение.
Я погрепал свои старые файлы, и обнаружил, что этот модификатор, даже когда и используется, ставился или по инерции, или, в совсем старом коде, из-за трепетного отношения к магическим в то время регулярным выражениям.
На днях прочитал 42 статьи о том, как пользоваться юникодом в современном C++ и заодно придумал еще один практический пример, где может быть полезна state-переменная, появившаяся в Perl 5.10, — при разборе последовательности байтов UTF-8.
Вот простая функция parse_utf8, которая принимает очередной байт и формирует в переданном по ссылке массиве @$buf последовательность кодов символов. Важно, что функция при каждом вызове принимает байт, но выходной буфер изменяется только после того, как принята вся последовательность, соответствующая юникодному символу, то есть на каждый второй, третий или четвертый раз, если очередной символ требует для записи в UTF-8 несколько байтов.
В этом примере суть довольно точно описывается словом state: переменные хранят состояние разбора между вызовами функции.
use v5.12;
(5.12 удобно использовать потому, что инструкция use v5.12 автоматически подключает и use strict. Но все должно работать и с 5.10.)
sub parse_utf8 {
my ($byte, $buf) = @_;
state $bytes = 0;
state $value = 0;
my $mask = 0;
given($byte) {
when(!($_ & 0x80)) {
($bytes, $value, $mask) = (0, $byte, 0);
}
when(0b1111_0000 == ($_ & 0b1111_1000)) {
($bytes, $value, $mask) = (3, 0, 0b0000_0111);
}
when(0b1110_0000 == ($_ & 0b1111_0000)) {
($bytes, $value, $mask) = (2, 0, 0b0000_1111);
}
when(0b1100_0000 == ($_ & 0b1110_0000)) {
($bytes, $value, $mask) = (1, 0, 0b0001_1111);
}
when(0b1000_0000 == ($_ & 0b1100_0000)) {
$bytes--;
$mask = 0b0011_1111;
}
default {
$mask = 0;
$value = ord('?');
}
}
$value += ($byte & $mask) << ($bytes * 6) if $mask;
push @$buf, $value unless $bytes;
}
Когда обнаруживается начало многобайтовой последовательности, в state-переменной сохраняется число оставшихся байтов, а в $value начинает накапливаться результат. Каждый последующий байт (старшие биты которого — единица и нуль) на единицу уменьшает значение $bytes.
Обновление буфера происходит в последней строке только в том случае, если прочитана вся последовательность байтов очередного символа.
Здесь есть даже примитивная обработка ошибок (хотя она находит ошибку лишь в первом байте многобайтовой последовательности).
Теперь проверяем:
my @buf;
parse_utf8($_, \@buf) for (
0x34, # 4
0x32, # 2
0xd1, 0x9e, # ў
0xc2, 0xa2, # ¢
0xe3, 0x89, 0xbf, # ㉿
0xe2, 0x82, 0xac, # €
0xf0, 0xa4, 0xad, 0xa2, # 𤭢
);
say "&#$_;" for @buf;
Вызов say печатает HTML-сущности опознанных символов (да, строка "&#$_;" может вызвать улыбку):
4
2
ў
¢
㉿
€
𤭢Именно такой результат и ожидался: 4 2 ў ¢ ㉿ € 𤭢.
В следующий раз обратим внимание на бинарные операции внутри when.