Глава 1: Введение.
1.5. Прогулка по стране Perl
1.5.8. Повышение степени модульности
Теперь, когда мы добавили так много строк к нашему первоначальному коду, нам при его просмотре будет непросто уловить общую логику построения программы. Поэтому было бы неплохо отделить высокоуровневую логику (запрос имени, циклы, используемые для обработки введенных секретных слов) от низкоуровневой (сравнение введенного секретного слова с заданным). Это необходимо сделать, например, для облегчения понимания программы другими пользователями, или по той причине, что один человек пишет высокоуровневую часть, а другой — низкоуровневые фрагменты.
В Perl существует понятие подпрограммы, имеющей параметры и возвращаемые значения. Подпрограмма определяется в программе один раз, но использоваться может многократно путем вызова ее из любого места программы.
Давайте создадим для нашей маленькой, но быстро растущей программы подпрограмму good_word, которая будет принимать имя и вариант слова и возвращать значение "истина", если это слово введено правильно, и "ложь", если слово набрано неправильно. Определение такой подпрограммы выглядит следующим образом:
sub good_word {
my($somename,$someguess) = @_; # назвать параметры
$somename =~ s/\W.*//; # избавиться от всех символов, стоящих после первого слова
$somename =~ tr/A-2/a-z/; # перевести все символы в нижний регистр
if ($somename eq "randal") { # не нужно угадывать
return 1; # возвращаемое значение — true
} elsif (($words($somename) || "groucho") eq $someguess) {
return 1; # возвращаемое значение — true
} else {
return 0; # возвращаемое значение — false
}
}
Во-первых, определение подпрограммы состоит из зарезервированного для этих целей слова sub, за которым идет имя подпрограммы и блок ее кода (выделенный фигурными скобками). Это определение может стоять в тексте программы где угодно, но большинство программыстов помещают его в конец.
Первая строка в данном конкретном определении — это операция присваивания, с помощью которой значения двух параметров подпрограммы копируются в две локальные переменные с именами $somename и $someguess (директива my() определяет эти переменные как локальные для блока, в который они входят (в данном случае для всей подпрограммы), а параметры первоначально находятся в специальном локальном массиве с именем @ .)
Следующие две строки удаляют символы, стоящие после имени (точно так же, как в предыдущей версии программы).
Оператор if-elsif-else позволяет определить, является ли введенный пользователем вариант слова ($someguess) верным для имени ($somename). Имя Randal не должно попасть в эту подпрограмму, но даже если и попадет, то любой вариант его ввода будет принят как правильный.
Для того чтобы подпрограмма немедленно возвращала в вызвавшую ее программу указанное в подпрограмме значение, можно воспользоваться оператором возврата. В отсутствие явного оператора возвращаемым значением является последнее выражение, вычисленное в подпрограмме. Мы посмотрим, как используется возвращаемое значение, после того как дадим определение подпрограммы.
Проверка в части eisif выглядит довольно сложной; давайте разобьем ее на фрагменты:
($words($somename) || "groucho") eq $someguess
Первый элемент в круглых скобках обеспечивает проведение уже знакомого нам хеш-поиска, в результате которого отыскивается некоторое значение в массиве %words на основании ключа, полученного из массива $somename. Знак ||, стоящий между этим значением и строкой groucho, обозначает операцию ИЛИ, аналогичную той, что используется в языке С, awk и в различных shell. Если поиск в хеше даст некоторое значение (это значит, что ключ $somename находился в хеше), то оно и будет являться значением данного выражения. Если ключ найден не был, то используется строка groucho. Это весьма характерно для Perl: приводится некоторое выражение, а затем с помощью операции || для него указывается значение по умолчанию на тот случай, если результатом поиска является значение "ложь".
В любом случае, будь то значение из хеша или принимаемое по умолчанию значение groucho, мы сравниваем его с вариантом, вводимым пользователем. Если результат сравнения положителен, возвращается единица, в противном случае возвращается нуль.
Выразим все это в виде правила: если имя — randal или если вводимый пользователем вариант соответствует одному из имен, находящемуся в массиве %words (со значением по умолчанию groucho, если имя в массиве не найдено), то подпрограмма возвращает 1; иначе подпрограмма возвращает 0. Теперь давайте свяжем все новые строки с остальной частью программы:
#!/usr/bin/perl
%words = qw( fred camel barney llama betty alpaca wilma alpaca );
print "What is your name? ";
$name = <STDIN>;
chomp ($name);
if ($name =~ /^randal\b/i) { # обратно на другой путь :-)
print "Hello, Randal! How good of you to be here!\n";
} elsif {
print "Hello, $name! \n"; # обычное приветствие
print "What is the secret word? ";
$guess = <STDIN>;
chomp ($guess);
while (! good_word($name, $guess)) {
print "Wrong, try again. What is the secret word? ";
$guess = <STDIN>;
chomp ($guess);
[... здесь вставляется определение подпрограммы good_word() ...]
Обратите внимание: мы вновь вернулись к использованию регулярного выражения для проверки наличия имени Randal в массиве, потому что теперь уже в основной программе не требуется выделять первое имя и заменять все его символы символами нижнего регистра.
Наибольшее отличие этой программы от предыдущей состоит в том, что здесь используется цикл while, содержащий подпрограмму &good_word. При вызове этой подпрограммы ей передаются два параметра, $name и $guess. Значение $somename устанавливается равным первому параметру, в данном случае $name. Аналогичным образом $someguess передается во втором параметре, $guess.
Значение, возвращаемое этой подпрограммой (1 или 0, если вы помните приведенное выше правило), логически инвертируется префиксной операцией ! (логическое НЕ). Эта операция возвращает значение "истина", если следующее за ней выражение ложно, или "ложь", если оно истинно. Результат этой операции управляет циклом while. Можете читать такую запись как "до тех пор, пока слово угадано неправильно...". Многие хорошо написанные Perl-программы очень похожи на обычный английский язык — если, конечно, не позволять себе много вольностей ни с языком Perl, ни с английским. (Но Пулитцеровскую премию даже за очень хорошую программу вам не дадут.)
Обратите внимание: при разработке этой подпрограммы предполагалось, что значение хеша %words задается в основной программе.
Столь деликатный подход к использованию глобальных переменных вызван тем, что обращаться с ними нужно очень аккуратно. Говоря в общем, переменные, не созданные с помощью ту, глобальны для всей программы, тогда как переменные ту действуют только до завершения выполнения блока, в которым они были объявлены. Но не беспокойтесь: в языке Perl имеется множество других разновидностей переменных, включая переменные, локальные для файла (или пакета), и переменные, локальные для функции, которые сохраняют свои значения от вызова к вызову — а это как раз то, что мы могли бы здесь использовать. Впрочем, на данном этапе вашего знакомства с Perl изучение этих переменных только осложнило бы вам жизнь. Когда вы будете достаточно готовы к этому, посмотрите, что говорится о контекстах, подпрограммах, модулях и объектах в книге Programming Perl, или обратитесь к диалоговой документации, имеющейся на man-страницах perlsub(\), perlmod(l), perlobJ(l) и perltool(l) .