пятница, 15 апреля 2016 г.

MUMPS: Очередность выполнения и вычисления выражений

Одна из основных проблем языка mumps состоит в недостаточно точном понимании программистами очередности вычисления выражений и выполнения операций в языке. Так, что это не совсем проблема языка, а некоторого его отличия от других. В других языках точно так же присутствует модель вычисления выражений и определенные соглашений, которые могут поставить программиста в тупик при смене языка. Например выражение
int i = 5;
i = ++i + ++i;
в разных языках может дать различные результаты:
C++      14
cl       14
bcc      14
lcc      13
gcc      13
php      13
C#       13
С языком mumps тоже не все так просто как кажется на первый взгляд. Но в отличие от многих других языков в языке mumps отсутствует undefined behavior. Очередность выполнения операций и вычисления выражений в языке mumps строго определены стандартом. К очередности выполнения и вычисления я бы отнес два пункта:
  • Очередность вычисления выражений.
  • Очередность вычисления имен.

Очередность вычисления выражений.

Выражения вычисляются слева направо, приоритеты операций отсутствуют. Если нужно указать иную очередность вычисления выражения то следует поставить скобки. Это обычно первая проблема с которой могут столкнуться программисты при прочтении кода на mumps. Например:
>w 1+2*3
9
Если при написании программы программист обычно при первом же тестовом прогоне обнаруживает что он неправильно записал выражение, то при внесении исправлений в в имеющийся код могут возникнуть ошибки. Типичная ситуация - пусть имеется код
>s a=1,b=2 i a=1 w "#",!
#
Со временем может возникнуть необходимость усложнить условие, например a=1 и b=2 одновременно. Обычно первое что пишет программист, это:
>s a=1,b=2 i a=1&b=2 w "#",!
И условие уже не выполняется. Потому что здесь очередность вычисления слева направо, сначала выполняется сравнение a и 1, результат 1. Потом вычисляется операция & с результатом (1) и значением b, получаем 1, и этот результат сравнивается с значением 2. Получаем 0.

Решение конфликта - в расстановке приоритетов вычисления скобками:
>s a=1,b=2 i (a=1)&(b=2) w "#",!
#
Теперь все работает нормально. Вывод: нужно внимательно относиться как к написанию сложных выражений, так и к их изменению. Поскольку автор исходного выражения может использовать отсутствие приоритетов для расположения операций для правильного вычисления выражения. Бояться тут нечего, надо соблюдать принятые в языке соглашения.

Очередность вычисления имен.

Имена вычисляются слева направо, значения индексов имени вычисляются в том порядке в котором они следуют, и вычисляются как выражения. Например:
>k  s a($i(a))=$i(a) w
a=2
a(1)=2
Здесь видно, что сначала было вычисление значения индекса (получили 1), потом значения выражения стоящего справа (получили 2). Если нужно явно задать порядок вычисления "сначала присваиваемое значение, потом имя", то для гарантирования следует явно разнести эти операции на две:
>k  s tmp=$i(a) s a($i(a))=tmp w
a=2
a(2)=1
tmp=1


Все на первый взгляд просто, но тут тоже есть нюанс, на который я хочу обратить внимание. Это голая ссылка (naked indicator). При вычислении имени образованном голой ссылкой сначала вычисляются записанные программистом индексы, потом команда использующая имя вычисляет полное имя непосредственно перед использованием, используя текущее значение голой ссылки (naked indicator). Например, при вычислении имени
^($h)
Сначала вычисляется выражение $h. И лишь при непосредственном использовании имени выполняется подстановка голой ссылки. Пример:
; очищаем экспериментальную глобаль
>k ^a    

; взводим индикатор голой ссылки
>i $d(^a(1))

; проверяем чему он равен
>w $zr
^a(1)

; выполняем присваивание с использованием
; naked indicator
>s ^(123,$zr)=$d(^(2,3))

; смотрим результат
>zw ^a
^a(2,123,"^a(1)")=0
В этом примере четко видно, в какой последовательности реально было вычислено имя глобала для присваивания. Если бы вычисление имени выполнялось слева направо, то сначала был бы взят naked indicator (значение ^a(1)), и от него было бы образовано имя ^a(123,"^a(1)"). Но в действительности стандарт языка mumps предписывает вычислять слева направо лишь значения индексов имени, и применять голую ссылку непосредственно при использовании имени.

В нашем примере выполняется сначала вычисление индексов, имя присваивания развертывается с значения ^(123,$zr) до ^(123,"^a(1)"). После чего вычисляется правая часть выражения - берется голая ссылка (^a(1)) и от нее вычисляется новое имя (^a(2,3)). По этому имени берется выражение ($d() в нашем случае возвращает 0). Функция $d() меняет индикатор голой ссылки на новое значение (^a(2,3)). Команда set выполняет присваивание. При этом получает на вход имя с голой ссылкой. Голая ссылка со значения ^(123,"^a(1)") используя текущее значение naked indicator равный ^a(2,3) развертывается до полного имени ^a(2,123,"^a(1)").

Еще к одному нюансу очередности выполнения команды set относится ее списочная форма set (name1,name2)=expr. В этом варианте сначала вычисляются последовательно слева направо значения индексов имен name1 и name2, потом значение выражения expr, потом последовательно слева направо производится развертывание имен до полных если там есть naked indicator и выполняется присваивание в очередности как указаны имена. Пример:
>k ^a
 
>i $d(^a(1))
 
>w $zr
^a(1)

>s (^(123,$zr),^(456,$zr))=$na(^(2,3))
 
>zw ^a
^a(123,456,"^a(1)")="^a(2,3)"
^a(123,"^a(1)")="^a(2,3)"
Здесь четко видно, что сначала было проведено развертывание первого имени, а потом второго. Отметим, что очередность выполнения списочной формы присваивания
set (name1,name2)=expr
отличается от очередности выполнения присваивания по отдельности:
set name1=expr,name2=expr
>k ^a
 
>i $d(^a(1))
 
>w $zr
^a(1)

>s ^(123,$zr)=$na(^(2,3)),^(456,$zr)=$na(^(2,3))
 
>zw ^a
^a(123,456,"^a(123,""^a(1)"")")="^a(123,2,3)"
^a(123,"^a(1)")="^a(2,3)"
Какой можно сделать практический вывод? Нужно быть внимательным при модификации кода, который использует голые ссылки. Автор кода может использовать нюансы поведения интерпретатора mumps с целью достижения корректного результата и запись выражения и операций может оказаться неочевидной на первый взгляд. Не возьмусь советовать не пользоваться нюансами языка, скажу лишь что к соглашениям языка следует относиться с вниманием, тем более что поведение mumps определено стандартом сильнее чем для каких-либо других языков.

Комментариев нет:

Отправить комментарий