Приоритет операций
После выделения и вычисления термов выражение разбирается с целью выявления последовательности выполнения операций в выражении: какая из них должна быть выполнена раньше другой. Это достаточно ответственная процедура, так как порядок выполнения операций существенно влияет на результат вычисления всего выражения. Например, результатом вычисления выражения
4+3*2
будет 14, если сначала выполнить сложение, а потом умножение, и ю, если сначала выполнить умножение, а потом сложение. Дабы избежать подобных двусмысленностей в языках программирования, вводится приоритет, или старшинство операций, который учитывается при вычислении выражения. Приоритет операции умножения выше приоритета сложения, а поэтому наше арифметическое выражение будет однозначно вычислено равным ю.
В табл. 4.4 представлены все операции Perl в порядке убывания их приоритета, в ней также рпределен порядок выполнения операций с одинаковым приоритетом (столбец Сочетаемость).
Таблица 4.4. Приоритет и сочетаемость операций Perl
Приоритет |
Операция |
Сочетаемость | |||
1 |
Вычисление термов и левосторонних списковых операций |
Слева направо | |||
2 |
-> |
Слева направо | |||
3 |
++ -- |
Не сочетаются | |||
4 |
* * |
Справа налево | |||
5 |
! ~ \ унарные + и - |
Справа налево | |||
6 |
=~ ! = |
Слева направо | |||
7 |
* / % х |
Слева направо | |||
8 |
+ - . |
Слева направо | |||
9 |
« » |
Слева направо | |||
10 |
Именованные унарные операции |
Не сочетаются | |||
11 |
<><=>= It gt le ge |
Не сочетаются | |||
12 |
== != <=> eq ne cmp |
Не сочетаются | |||
13 |
& |
Слева направо | |||
14 |
I л |
Слева направо | |||
15 |
&& |
Слева направо | |||
16 |
I I |
Слева направо | |||
17 |
. . ... |
Не сочетаются | |||
18 |
?; |
Справа налево | |||
19 |
= **= += -= .= *= /= %= х= &= |= л = |
Справа налево | |||
«= »= &&=|| = |
| ||||
20 |
, => |
Слева направо | |||
21 |
Правосторонние списковые операции |
Не сочетаются | |||
22 |
not |
Справа налево | |||
23 |
and |
Слева направо | |||
24 |
or xor |
Слева направо | |||
Некоторые операции, приведенные в табл. 4.4, требуют пояснения. И первым в этом ряду стоят операции с наивысшим приоритетом: термы и левосторонние списковые операции. Термы мы определили в предыдущем разделе и там же разъяснили, что списковые операции и унарные именованные операции рассматриваются компилятором Perl как термы, если список их параметров заключен в круглые скобки. Так как умножение имеет больший приоритет, чем унарная именованная операция sin, то следующие операции вычисляются так, как указано в комментариях к ним:
use Math::Trig; # В пакете определена константа
# pi = 3.14159265358979
sin I * pi; # sin( 1 * pi) = 1.22460635382238e-016 sin (1) * pi; f (sin 1) * pi = 2.64355906408146
В последнем выражении sin (i) рассматривается как терм, так как после имени операции первой распознаваемое лексемой стоит открывающая круглая скобка, а если это терм, — то и вычислять его надо в первую очередь, как операцию с наивысшим приоритетом.
Можно чисто визуально в тексте программы списковую или унарную именованную операцию с параметрами в круглых скобках сделать не похожей на вызов функции, поставив префикс + перед списком ее параметров:
sin +(1) * pi; # (sin 1} * pi = 2.64355906408146
Этот префикс не выполняет никакой семантической роли в программе, даже не преобразует параметр в числовой тип данных. Он просто служит для акцентирования того факта, что sin не является функцией, а представляет собой унарную именованную операцию.
Если в списковой операции отсутствуют скобки вокруг параметров, то она может иметь либо наивысший, либо самый низкий (ниже только логические операции not, and, or и хог) приоритет. Это зависит от того, где расположена операция относительно других операций в выражении: слева или справа. Все операции в выражении, расположенные слева от списковой операции (сама операция расположена справа от них), имеют более высокий приоритет относительно такой списковой операции, и вычисляются, естественно, раньше нее. Именно это имелось в виду, когда в табл. 4.4 вносилась строка с правосторонними списковыми операциями. Следующий пример иллюстрирует правостороннее расположение списковой операции:
$т = $п II print "Нуль, пустая строка или не определена!";
По замыслу программиста это выражение должно напечатать сообщение, если только значение переменной $п равно нулю, пустой строке или не определено. На первый взгляд, кажется, так и должно быть: выполнится операция присваивания и возвратит присвоенное значение. Если оно не равняется нулю, пустой строке или значение переменной $п не определено, то в булевом контексте операции логического ИЛИ (| |) оно трактуется как Истина, а поэтому второй операнд этой логической операции (операция печати) не вычисляется, так как мы помним, что логическое ИЛИ вычисляется по укороченной схеме. Однако реально переменной $т всегда будет присваиваться 1, правда сообщение будет печататься именно тогда, когда переменная $п равна нулю, пустой строке или не определена.
В чем дело? Программист забыл о приоритете правосторонних списковых операций! В этом выражении списковая операция print расположена справа от всех остальных операций, поэтому она имеет самый низкий приоритет. Выражение будет вычисляться по следующему алгоритму. Сначала будет вычислен левый операнд операции i i. Если он имеет значение Истина (переменная $п имеет значение, не равное нулю или пустой строке), то второй операнд этой операции (print) вычисляться не будет, а переменной $т будет присвоена Истина, т. е. 1. Если первый операнд вычисляется как Ложь (переменная $п равна нулю, пустой строке или не определена), то вычисляется второй операнд, выводящий сообщение на экран монитора. Но так как возвращаемым значением операции печати является Истина, то именно она и присваивается переменной $т.
Правильное решение — использовать низкоприоритетную операцию or логического ИЛИ:
$m = $n or print "Нуль, пустая строка или не определена! " ; или скобками изменить порядок выполнения операций:
($m = $n) I I print "Нуль, пустая строка или не определена!";
Теперь обратимся к случаю, когда списковая операция стоит слева от других операций в выражении (левосторонняя списковая операция). В этом случае, в соответствии с табл. 4.4, она имеет наивысший приоритет и все, что стоит справа от нее, она рассматривает как список своих параметров. Рассмотрим небольшой пример. Предположим, что необходимо удалить из массива @а все элементы, начиная со второго, и вставить их в создаваемый массив @т после второго элемента. Списковая операция splice со списком параметров @а, 1 удаляет из массива @а все элементы, начиная с элемента с индексом 1, т. е. со второго элемента до конца массива, и возвращает список удаленных элементов. Ее можно использовать в конструкторе нового массива для решения поставленной задачи:
@а = ("al", "a2", "аЗ", "а4");
9m = ("m0", "ml", splice @a, 1, "m2", "mЗ"); ,
В конструкторе массива мы специально задали параметры операции splice без скобок. Если выполнить этот фрагмент и распечатать значения элементов массивов, то результат будет следующим:
@m: m0 ml
@а: al mЗ а2 аЗ а4
Совершенно не то, что нам надо: в массив @т не вставлен фрагмент массива @а, да и из него самого не удалены элементы, начиная со второго. Все дело в том, что операция splice в этом выражении левосторонняя, и весь расположенный справа от нее список рассматривает как список своих параметров: @а, i, "m2", "тЗ". Ее третьим параметром должно быть число, определяющее количество удаляемых из массива элементов, начиная с элемента, индекс которого определен вторым параметром. В нашем случае третий параметр не является числовым, и функция завершается с ошибкой, возвращая Ложь. Исправить положение помогут опять скобки:
@m = ("m0", "ml", (splice @a, 1), "т2", "тЗ");
ИЛИ """' @т = ("m0", "ml", splice (@a, 1), "т2", "тЗ");
Завершая разговор о приоритете выполнения операций, следует объяснить свойство сочетаемости операций и его практическое применение. Сочетаемость важна при вычислении выражений, содержащих операции с одинаковым приоритетом, и определяет порядок их вычисления. Рассмотрим выражение:
$т += $п += 1;
Как следует его понимать? Как ($т += $п) +=1 или как $т += ($п += 1)? Ответ дает правило сочетаемости. Смотрим в табл. 4.4 и видим, что все операции присваивания сочетаются справа налево. Это означает, что сначала должно выполниться присваивание $п += 1, а потом результат увеличенной на единицу переменной $п прибавляется к переменной $т. Следовательно, это выражение эквивалентно следующему:
$т += ($п += 1) ;
Аналогично применяется правило сочетаемости и к другим операциям языка Perl:
$a>$b<$c; # Эквивалентно: ($а>$Ь)<$с; Сочетаемость: слева направо. $а**$Ь**$с; # Эквивалентно: $а**($Ь**$с); Сочетаемость: справа налево.
Скобки изменяют порядок вычислений, определяемый по правилу приоритетов и сочетаемости. Любое, заключенное в скобки подвыражение, будет вычисляться с наивысшим приоритетом, так как Perl рассматривает его как терм, имеющий наивысший приоритет.
Контекст
Наш разговор о выражениях Perl был бы не полным, если бы обошли стороной такое понятие, как контекст. Каждая операция и каждый терм вычисляются в определенном контексте, который определяет поведение операции и интерпретацию возвращаемого ею значения. Существует два основных контекста: скалярный и списковый. В главе 3 мы уже немного познакомились с ними, когда определяли поведение конструктора массива и переменной массива в правой части оператора присваивания. Их можно "определить" так: если для выполнения операции требуются скалярные данные, то действует скалярный контекст, если необходимы массивы скаляров, то программа находится в списковом контексте. Например, если левый операнд операции присваивания, скалярная переменная, то действует скалярный контекст, если же в левой части задан массив, хеш или фрагмент массива или хеша, то вычисления в правой части осуществляются в списковом контексте. Присваивание списку скалярных переменных также инициирует списковый контекст для вычислений, осуществляемых в правой части операции.
Некоторые операции Perl распознают контекст, в котором они вычисляются, и возвращают список в списковом контексте и скалярное значение в скалярном контексте. Обладает ли операция подобным поведением, можно всегда выяснить из ее описания в документации. Например, можно рассматривать префикс @ перед идентификатором как унарную операцию объявления массива скаляров, распознающую контекст, в котором она вычисляется. В списковом контексте она возвращает список элементов массива, а в скалярном — число элементов массива.
Некоторые операции поддерживают списковый контекст для своих операндов (в основном это списковые операции), и это также можно узнать из описания их синтаксиса, в котором присутствует СПИСОК (LIST). Например, известная уже нам операция создания фрагмента массива splice поддерживает списковый контекст для своих параметров, поэтому ее можно вызывать вот таким образом:
@s = (1, 2);
splice @m, @s; # Эквивалентно: splice @m, I, 2;
Скалярный контекст можно подразделить на числовой, строковый и безразличный. Если скалярный и списковый контекст некоторыми операциями распознается, то ни одна операция не может определить, вычисляется ли она в числовом или строковом скалярном контексте. Perl просто при необходимости преобразует возвращаемые операцией числа в строки и наоборот. В некоторых случаях вообще не важно, возвращается ли операцией число или строка. Подобное, например, происходит при присваивании переменной какого-либо значения. Переменная просто принимает подтип присваиваемого значения. Такой контекст называется безразличным.
В языке существует булевый контекст — специальный тип скалярного контекста, в котором вычисленное значение выражения трактуется только как Истина или Ложь. Как уже отмечалось ранее, в Perl нет специального булева типа данных. Здесь любая скалярная величина трактуется как Истина, если только она не равна пустой строке "" или числу о. (Строковый эквивалент ложности — строка из одного нуля "о"; любая другая строка, эквивалентная нулевому значению, считается в булевом контексте истинной, например, "оо" или "о.о".)
Другим специфическим типом скалярного контекста является void-контекст. Он не только не заботится о подтипе возвращаемого значения — скалярный или числовой, но ему и не надо никакого возвращаемого значения. Этот контекст возникает при вычислении выражения без побочного эффекта, т. е. когда не изменяется никакая переменная программы. Например, следующие выражения вычисляются в void-контексте:
$n; "текст";
Этот контекст можно "обнаружить", если установить ключ -w компилятора Perl. Тогда можно получить предупреждающее сообщение следующего типа:
Useless use of a variable in void context at D:\P\EX.PL line 3.
(Бесполезное использование переменной в, void-контексте в строке 3 программы D:\P\EX.PL) х
Завершит наш рассказ о контексте подстановочный контекст (interpolative context), в котором вычисляются операции заключения в кавычки (кроме заключения в одинарные кавычки). В этом контексте вместо любой переменной, заданной в строке, в нее подставляется значение этой переменной, а также интерпретируются управляющие последовательности.
В этой главе мы изучили практически все скалярные операции языка Perl, чуть-чуть коснулись операций сопоставления по образцу, создания ссылок и операций ввода\вывода, познакомились с основами работы со списковыми и унарными именованными операциями. Узнали, что такое выражение, а также в каком порядке вычисляются в нем операции на основе их приоритета и сочетаемости. Научились выделять термы в выражениях и выяснили, в каких контекстах могут вычисляться выражения.