Команда given

Управляющая конструкция given позволяет выполнить блок кода, если аргумент удовлетворяет указанному условию. В сущности, это Perl-эквивалент команды switch языка C; но, как и многие конструкции Perl, given обладает оригинальными возможностями, поэтому ей присваивается оригинальное имя. Следующий фрагмент кода берет первый аргумент из командной строки $ARGV[0], перебирает условия when и смотрит, где в аргументе удастся найти Fred. Каждый блок when сообщает об одном способе вхождения Fred, начиная с самого общего и заканчивая самым конкретным:

use 5.010;
given( $ARGV[0] ) {
when( /fred/i ) { say 'Name has fred in it' }
when( /^Fred/ ) { say 'Name starts with Fred' }
when( 'Fred' ) { say 'Name is Fred' }
default { say "I don't see a Fred" }
}

В конструкции given аргумент представлен псевдонимом $_, а каждое условие пытается применить умное сравнение к $_. Предыдущий пример можно переписать с явным применением умного сравнения, чтобы понять, что происходит:

use 5.010;
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it' }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred' }
when( $_ ~~ 'Fred' ) { say 'Name is Fred' }
default { say "I don't see a Fred" }
}

Если $_ не удовлетворяет ни одному условию when, выполняется блок default.


Вот как выглядят результаты нескольких пробных запусков:

$ perl5.10.0 switch.pl Fred
Name has fred in it
$ perl5.10.0 switch.pl Frederick
Name has fred in it
$ perl5.10.0 switch.pl Barney
I don't see a Fred
$ perl5.10.0 switch.pl Alfred
Name has fred in it

«Подумаешь», – скажете вы. – «Я бы мог записать этот пример с if-elsif-else». В следующем примере именно это и делается с использованием переменной $_, объявленной с ключевым словом my. На эту переменную распространяются все правила лексической видимости my, еще одной новой возможности Perl 5.10:

use 5.010;
{
my $_ = $ARGV[0]; # lexical $_ as of 5.10!
if( $_ ~~ /fred/i ) { say 'Name has fred in it' }
elsif( $_ ~~ /^Fred/ ) { say 'Name starts with Fred' }
elsif( $_ ~~ 'Fred' ) { say 'Name is Fred' }
else { say "I don't see a Fred" }
}

Если бы конструкция given делала абсолютно то же, что if-elsif-else никакого интереса она бы не представляла. Но, в отличие от if-elsif-else, конструкция given-when после выполнения одного условия может продолжить проверку остальных условий. С другой стороны, if-elsif-else при выполнении одного условия всегда выполняет ровно один блок кода.

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


Если не принять специальные меры, в конец каждого блока включается неявная команда break, которая приказывает Perl прервать обработку given-when и продолжить выполнение программы. Таким образом, предыдущий пример содержал команды break, хотя вам и не пришлось вводить их самостоятельно:

use 5.010;
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it'; break }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred'; break }
when( $_ ~~ 'Fred' ) { say 'Name is Fred'; break }
default { say "I don't see a Fred"; break }
}

Но для нашей задачи такой способ обработки не подходит. Так как наш пример переходит от общих условий к более конкретным, в случае совпадения /fred/i Perl не проверяет другие условия when. До проверки аргумента на полное совпадение с Fred дело уже не доходит, потому что первый же блок when прерывает дальнейшую обработку конструкции. Но если завершить блок when ключевым словом continue, Perl даже в случае выполнения условия перейдет к следующему условию, и весь процесс повторится заново. Конструкция if-elsif-else на такое не способна. Если другое условие when окажется истинным, Perl выполняет его блок (снова неявно завершаемый командой break, если в программе явно не указано обратное).


Если добавить continue в конец каждого блока, Perl опробует все условия:

use 5.010;
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it'; continue }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred'; continue }
when( $_ ~~ 'Fred' ) { say 'Name is Fred'; continue } # OOPS!
default { say "I don't see a Fred" }
}

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

$ perl5.10.0 switch.pl Alfred
Name has fred in it
I don't see a Fred

Блок default в действительности представляет собой when с условием, которое всегда истинно. Если блок when перед default содержит continue, Perl переходит к default. С его точки зрения все выглядит так, словно default является очередным блоком when:

use 5.010;
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it'; continue }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred'; continue }
when( $_ ~~ 'Fred' ) { say 'Name is Fred'; continue } # OOPS!
when( 1 == 1 ) { say "I don't see a Fred" } # default
}

Чтобы этого не происходило, достаточно убрать последнюю команду continue.


В этом случае последний блок when завершает процесс:

use 5.010;
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it'; continue }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred'; continue }
when( $_ ~~ 'Fred' ) { say 'Name is Fred'; break } # OK now!
when( 1 == 1 ) { say "I don't see a Fred" }
}

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

use 5.010;
given( $ARGV[0] ) {
when( /fred/i ) { say 'Name has fred in it'; continue }
when( /^Fred/ ) { say 'Name starts with Fred'; continue }
when( 'Fred' ) { say 'Name is Fred'; }
default { say "I don't see a Fred" }
}

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела Perl на эту тему:
Обычное сравнение
Оператор умного сравнения
Приоритеты умного сравнения
Условия when с несколькими элементами

Вернуться в раздел: Perl / 14. Умные сравнения и given)when