Книга: Основы объектно-ориентированного программирования

Нестрогие булевы операторы

Нестрогие булевы операторы

Операторы and then и or else (названия заимствованы из языка Ada), а также implies не коммутативны и называются нестрогими (non-strict) булевыми операторами. Их семантика следующая:

Нестрогие булевы операторы

[x]. a and then b ложно, если a ложно, иначе имеет значение b.

[x]. a or else b истинно, если a истинно, иначе имеет значение b.

[x]. a implies b имеет то же значение, что и: (not a) or else b.

Первые два определения, как может показаться, дают ту же семантику, что и and и or. Но разница выявляется, когда b не определено. В этом случае выражения, использующие стандартные булевы операторы, математически не определены, но данные выше определения дают результат: если a ложно, то a and then b ложно независимо от b; а если a истинно, то a and then b истинно независимо от b. Аналогично, a implies b истинно, если a ложно, даже если b не определено.

Итак, нестрогие операторы могут давать результат, когда стандартные не дают его. Типичный пример:

(i /= 0) and then (j // i = k)

которое, согласно определению, ложно, если i равно 0. Если бы в выражении использовался and, а не and then, то из-за неопределенности второго операнда при i равном 0 статус выражения неясен. Эта неопределенность скажется во время выполнения:

1 Если компилятор создает код, вычисляющий оба операнда, то во время выполнения произойдет деление на ноль, и возникнет исключительная ситуация.

2 Если же генерируется код, вычисляющий второй операнд только тогда, когда первый истинен, то при i равном 0 возвратится значение ложь.

Для гарантии интерпретации (2), используйте and then. Аналогично,

(i = 0) or else (j // i /= k)

истинно, если i равно 0, а вариант or может дать ошибку во время выполнения.

Можно недоумевать, почему необходимы два новых оператора - не проще и не надежнее ли просто поддерживать стандарт операторов and и or и принимать, что они означают and then и or else? Это не изменило бы значение булева выражения, когда оба оператора определены, но расширило бы круг случаев, где выражения могут получить непротиворечивое значение. Именно так некоторые языки программирования, в частности, ALGOL, W и C, интерпретируют булевы операторы. Однако есть теоретические и практические причины сохранять два набора различных операторов.

[x]. С точки зрения теории, стандартные математические булевы операторы коммутативны: a and b всегда имеет значение такое же, как b and a, в то время как a and then b может быть определенным, когда b and then a не определено. Когда порядок операндов не имеет значения, предпочтительно использовать коммутативный оператор.

[x]. С точки зрения практики, некоторые оптимизации компилятора становятся невозможными, если требуется, чтобы компилятор вычислял операнды в заданном выражением порядке, как в случае с некоммутативными операторами. Поэтому лучше использовать стандартные операторы, если известно, что оба операнда определены.

Отметим, что можно смоделировать нестрогие операторы посредством условных команд на языке, не включающем такие операторы. Например, вместо

b := ((i /= 0) and then (j // i = k))

можно написать

if i = 0 then b := false else b := (j // i = k) end

Нестрогая форма, конечно, проще. Это особенно ясно, когда она используется как условие выхода из цикла:

from
i := a.lower
invariant
-- Для всех элементов из интервала [a.lower .. i - 1], (a @ i) /= x
variant
a.upper - i
until
i > a.upper or else (a @ i = x)
loop
i := i + 1
end;
Result := (i <= a.upper)

Цель - сделать Result верным, если и только если значение x находится в массиве a. Использование or здесь будет неверным. В этом случае всегда могут вычисляться два операнда, так что при истинности первого операнда (i > a.upper) произойдет попытка доступа к несуществующему элементу массива a @(aupper+1), что приведет к ошибке во время выполнения (нарушение предусловия при включенной проверке утверждений).

Решение без нестрогих операторов будет неэлегантным.

Другой пример - утверждение, например, инварианта класса, выражающее, что первое значение списка l целых неотрицательно, при условии, что список непустой:

l.empty or else l.first >= 0

При использовании or инвариант был бы некорректен. Здесь нет способа написать условие без нестрогих операторов (кроме написания специальной функции и вызова ее в утверждении). Базовые библиотеки алгоритмов и структур данных содержат много таких случаев.

Оператор implies, описывающий включения, также нестрогий. Форма implies менее привычна, но часто более ясна, например, последний пример выглядит лучше в записи:

(not l.empty) implies (l.first >= 0)

Оглавление книги


Генерация: 1.281. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз