- •4.9.1. Изменяемость
- •4.9.2. Классы операций
- •4.9.3. Полнота
- •4.9.5. Операции egual, similar и copy
- •5. Исключительные ситуации
- •6.2.1. Сигнализация об исключительных ситуациях
- •5.2.2. Обработка исключительных ситуаций
- •5.2.3. Предложение resignal
- •6.2.4. Необрабатываемые исключительные ситуации
- •6.2.3. Предложение resignal
- •5.2.4. Необрабатываемые исключительные ситуации
- •6.2.1. Реализация итераторов
- •6.2.2. Использование итераторов
- •7.2. Абстракция данных
- •7.4. Генераторы
- •9.1.3. Пример
6.2.1. Реализация итераторов
Конструкция iter языка CLU аналогична конструкции proo. Она имеет заголовок, похожий на заголовок спецификации итератора. В теле этой конструкции для получения следующего элемента используется предложение yield. Однако выполнение операции iter на этом не закапчивается. Наоборот, она готова выдавать дополнительные элементы, если вызвавшая ее процедура в этом заинтересована, В этом случае она продолжает свое выполнение с предложения, следующего за предложением yield, а ее локальные переменные имеют те же самые значения, как и до выполнения предложения yield.
На рис. 6.3 показана некоторая реализация итератора •elements для наборов intset. Здесь мы предполагаем, что массивы представляются в том же виде, как и на рис. 4.5 и 5.5. Когда операция elements продолжает свое выполнение после предложения yield, ее переменная i является индексом выданного перед этим элемента. Операция elements затем продолжает работать и выдает следующий элемент, а заканчивает работу тогда, когда будут выданы все элементы.
гер == array [int.] elements == iter (s: cvt) yields (int) i; int :== rep$low (s) while true do yield (s fi])
except when bounds: return end i := i + I end end elements Рис. 6.3. Операция elements в наборе intset.
6.2.2. Использование итераторов
Для итераторов можно использовать оператор for, имеющийся в языке CLU. Этот оператор определяет некоторые переменные цикла. Если эти переменные являются локальными для цикла, форма такого оператора следующая:
for deeLlist in iter.call do body end
Если переменные не являются локальными, то используется такая форма:
for idn_list in iter. call do body end
Когда оператор for начинает свое выполнение, создаются, если необходимо, переменные цикла и выполняется операция iter_call. Когда итератор выдает элемент, выданные объекты записываются в переменные цикла и затем выполняется тело цикла. Когда тело цикла выполняется, управление передается назад в итератор. Когда итератор закончит работу, цикл for также заканчивает работу и управление в использующей эту конструкцию программе передается на оператор, следующий за оператором for. Кроме этого, если в теле цикла выполнится некоторый оператор, который завершит выполнение оператора for, то итератор автоматически завершит свою работу.
На рис. 6.4 показана третья реализация процедуры setsum. Каждый раз, когда выполняется тело цикла, в переменную цикла е устанавливается другой элемент данного набора. Этот элемент добавляется в переменную sum, и затем тело цикла заканчивается, вызывая продолжение выполнения итератора. Когда будут выданы все элементы, операция elements завершается и вызывает окончание работы оператора for. Затем процедура setsum выдает вычисленную сумму.
setsum == proc (s: intset) returns (int) sum; int :== 0 for е: int in intset$elements (s) do sum := sum + jg_ end return (sum) end setsum
Рис. 6.4. Использование итератора.
Итератор и цикл for, который его использует, передают управление друг другу. Сперва вызывается итератор. Он выдает в качестве результата тому, кто его использует, некоторый элемент, но сам не завершает работу. Вместо этого он возвращает управление тому, кто его использует. Когда использующая его процедура готова получить следующий элемент, она возобновляет выполнение итератора.
На рис. 6.5 показана трассировка выполнения процедуры sum е i рс В setsum рс В elements
После первой выдачи После второй выдачи После третьей выдачи Окончание
О
4
5 12
начало тела начало тела начало тела оператор return
Рис. 6.5. Трассировка выполнения процедуры setsum.
после yield после yield после yield
132 Глава 6
бстракция итерации
setsum, когда к ней обращаются с набором intset jl, 4, 71, предполагая, что представляющий этот набор массив содержит эти элементы в порядке 4, 1, 7, На этом рисунке показаны значения локальных в процедуре setsum переменных sum и е и локальной в операции elements переменной i сразу же после выдачи элемента. Показано также значение переменной sum после окончания цикла в процедуре setsum. В этом случае значения переменных i и е не показаны, поскольку обращение к операции elements и тело цикла завершилось. После выдачи каждого элемента счетчик команд (рс) в процедуре setsum находится в начале тела оператора for, а счетчик команд для операции elements находится на операторе, следующем за оператором yield, г. е. на операторе присваивания для переменной i.
На рис. 6.6 показан второй пример использования операции elements — поиск некоторого отрицательного элемента. Если выданный элемент отрицателен, то процедура find. neg сразу же выдает результат. При этом возврате оператор for, а значит, и операция elements завершают работу до того, как будут выданы другие целые числа в наборе intset.
find.neg= pi-ос (s: intset) returns (int) signal (notJouiid) for x: int in intsel$e!ernents (s) do if x < 0 then return (x) end er.d signal not „found end find.neg
Рис. 6.6. Использование итератора для поиска.
К итераторам можно обращаться только из операторов for, и из каждого оператора for можно обращаться только к одному итератору. Однако операторы for могут быть вложенными. Кроме того, итераторы могут быть реализованы при помощи операторов for и их реализация может быть рекурсивной. Некоторые такие примеры приводятся в разд. 6.3.
6.2.3. Встроенные итераторы
В языке CLU имеется ряд операций итераторов для встроенных типов. Для целых чисел имеется операция intSfrom.to,
from. to == iter(x, у: int) yields (int)
effects Выдает целые числа между x и у включительно в порядке следования.
Имеется также операция int$from_to.by, чей третий аргумент задает величину, на которую увеличивается переменная х на каждой итерации. Для массивов (и последовательностей) имеется два итератора — операция elements, которая выдает элементы массива (или последовательности) от нижней границы до верхней
границы, и операция indexes, которая выдает индексы массива (или последовательности) от нижней границы до верхней границы. И наконец, строки имеют итератор chars, который выдает символы строки от первого до последнего.
Примеры
В этом разделе приводится несколько примеров итераторов. Первый пример, приведенный на рис. 6.7, — это простой «фильтр». Он называется фильтром потому, что удаляет некоторые элементы, выданные другим итератором, и выдает только остальные элементы в процедуру, которая к нему обращается.
filter = iter (s: intset, els: itertype (intset) yields (int),
pred: proctype (int) returns (bool)) yields (int) requires чтобы s не модифицировался в теле цикла. effects Выдает в произвольном порядке все элементы, выданные операцией els (s), которые удовлетворяют условию, заданному в pred',
iter (s: intset, els: itertype (intset) yields (int),
pred: proctype (int) returns (bool)) yields (iiit) for е: int in els (s) do if pred (e) then yield (е) end end end filter
Рис. 6.7. Фильтр.
Например, если операция odd выдает значение true, только если ее целый аргумент является нечетным числом, то фильтр
filter (s, intset^elements, odd)
выдает нечетные числа из набора s. В качестве второго примера добавим операцию
smalLto.big к упорядоченному списку olist.
small_to.big= iter (s, olist [t]) yields (t) requires чтобы набор s не модифицировался в теле цикла. effects Выдает элементы набора s в возрастающем порядке, определяемом при помощи операции t$lt, причем каждый элемент только один раз.
Эта операция могла бы заменить операцию least, описанную ранее. Реализация показана на рис. 6.8. Она рекурсивна — сперва операция small, to_big обращается рекурсивно сама к себе для любого поддерева, затем она выдает значение в узле, и далее она обращается рекурсивно сама к себе для правого поддерева. Заметим, между прочим, что операция smalLto_big является некоторой абстракцией через параметризацию. Она наследует тип параметра t из упорядоченного списка olist.
134 Глава 6
примером рис. 6.9:
является итератор permuta-
node== record [val: t, left, right: olist [t]] rep = variant [some: node, empty: null] small _to_big= Her (s: cvt) yields (t) tagcase s tag empty: return tag some (n: node): for e; t in small_to_big (n.left) do % выдать элементы левого поддерева yield (e) end yield (n.val) for e: t in small_to_big (n.right) do % выдать элементы правого поддерева yield (e) end end end small_to_big Рис. 6.8. Операция small, to_ big для упорядоченного списка.
Нашим следующим tions, приведенный на
si = sequence (int) permutations = iter (seq: si) yields (si)
% если последовательность состоит из одного числа, то оно выдается, и организуется выход if si$size (seq) = I then yield (seq) else for index: int in si$indexes (seq) do % Создать некоторую последовательность, в которой опущено значение
с этим индексом val: int;=seq [index] prefix: si :== si$subseq (seq, I, index — 1) suffix: si :=si$subseq (seq, index+ I, si$size (seq)) % Присоединить перестановки последовательности к этому значению
и выдать
for perm: si in permutations (prefix Ц suffix) do % Операция II является операцией конкатенации yield (si$addl (perm, val)) end % конец для perm end % конец для index end % конец для if end permutations Рис. 6.9. Итератор permutations.
permutations' iter (seq: sequence [int]) yields (sequence [int]) effects Выдает в произвольном порядке все последовательности, построенные при помощи перестановок элементов в наборе seq.
Данная реализация использует итератор последовательностей indexes. При работе этот итератор выдает все перестановки, которые получаются, если зафиксировать на первом месте первый элемент первоначальной последовательности, затем зафиксировать на первом месте второй элемент первоначальной последовательности и т. д. Для того чтобы получить перестановки с зафиксированным на первом месте п-ным элементом, создается некоторая последовательность, содержащая все другие элементы, и
\ракция итерации
_полняются перестановки этой последовательности, обращаясь рекурсивно к самому себе. Затем п-ный элемент добавляется ^ к началу каждой перестановки последовательности и выдается результирующая последовательность.
Нашим последним примером является итератор allprimes, приведенный на рис. 6.10.
ai = array [int] allprimes = iter ( ) yields (int) yield (2) % выдается первое простое число primes: ai :== ai$new ( ) x: int := I while true do
x: = x+2 % следующее рассматриваемое число for p: int in ai$elements (primes) do if x//p == 0 then exit not_found end
% x—не простое число
if piiip^x then break end % x — простое число end except when not_found: continue end % имеем некоторое простое число yield (x) ai$addh (primes, x) end end primes Рис. 6.10. Итератор allprimes.
allprimes =: iter ( ) yields (int) effects Выдаются простые числа в возрастающем порядке.
В нашей реализации все найденные до некоторого момента простые числа Хранятся в массиве primes. В цикле while получаются нечетные числа. Те из них, которые кратны ранее найденным простым числам, отбрасываются в цикле for. Мы останавливаем цикл for, как только достигаем некоторого простого числа, чей квадрат больше текущего рассматриваемого числа, поскольку это рассматриваемое число не может, по-видимому, делиться на большее простое число.
6.4. Вопросы проектирования
Итераторы будут включаться среди других операций в большинство типов данных, особенно в такие типы, как intset и olist, чьи объекты являются совокупностями других объектов. Итераторы часто необходимы для полноты — они позволяют организовать доступ к элементам некоторой совокупности таким способом, который эффективен и удобен.
Тип может иметь несколько итераторов. Например, упорядоченные списки olist могли бы иметь такую операцию:
big.to.small = iter (s: olist [t]) yields (t) requires чтобы набор s не модифицировался в теле цикла. effects Выдает элементы набора s в порядке уменьшения, как это определяется при помощи операции t$lt, причем каждый элемент только один раз.
136 Глава 6
Зта операция дополняла бы операцию sma!l_to_big, обсуждавшуюся выше.
Для изменяемых совокупностей мы постоянно требовали, чтобы совокупность, по которой происходит итерация, не изменялась в теле цикла. Если мы опустим это требование, итератор должен работать по известным правилам даже в том случае, когда сделаны модификации. Например, предположим, что целое число п удалено из набора intset, когда работает операция elements. Должно ли число п выдаваться по операции elements?
Один подход состоит в том, что требуется, чтобы итератор выдавал элементы, содержащиеся в его аргументе совокупности в момент обращения, даже если модификации происходят позднее. Поведение заданного таким образом итератора хорошо определено, но его реализация, вероятно, будет неэффективной. Например, если бы итератор elements был реализован таким образом, то при его реализации пришлось бы при первом к нему обращении копировать весь набор intset — как раз то, против чего мы протестовали при рассмотрении операции eLseg. Поскольку при подходе с ограничениями На тело цикла эти неэффективности исчезают, в большинстве случаев этот подход будет предпочтительным. Связанный с этим вопрос касается того, может ли итератор сам модифицировать совокупность. По общему соглашению такие модификации должны избегаться.
Модификации, сделанные в теле цикла или выполненные итератором, могут иногда быть полезными. Например, рассмотрим программу, которая выполняет описание задачи в некоторой очереди задач.
for t: task in task. queue$al!_ tasks (queue) do
% выполняется операция t. % если операция t генерирует новую задачу nt, % тогда ее надо поставить в очередь, выполнив % операцию taskqueue$enq (queue, nt) end
Абстракция итерации
Когда выполняемая задача генерирует другую задачу, мы просто помещаем ее в очередь, чтобы она позднее выполнялась. Итератор alLtasks в соответствующее время представит ее на выполнение. Однако такие примеры редки, обычно совокупность не изменяется ни в теле цикла, ни итератором.
6.5. Обзор главы
В этой главе рассматривается проблема полноты типов дан" ных, которые являются совокупностью объектов. Поскольку совокупность, вообще говоря, используется для выполнения некоторого действия над ее злементами, нам необходим некоторый способ доступа ко всем элементам. Этот способ должен быть эффек-
тивным в смысле времени и пространства, удобным для использования и не должен разрушать данную совокупность. Кроме того, он должен обеспечивать абстракцию через спецификацию.
Итераторы являются тем механизмом, который решает эту задачу. Поскольку они выдают объекты по одному, не требуется дополнительного пространства для хранения объектов, и процедура может быть остановлена, когда нужный объект будет найден. Итераторы поддерживают абстракцию через спецификацию, инкапсулируя метод выдачи объекта. Этот метод зависит от знания представления этих объектов, но использующие его программы защищены от этого знания.
Итераторы эффективны для исполнения. Выдача из итератора аналогична обращению к процедуре. К телу цикла for «обращаются» из итератора. Возобновление работы итератора аналогично возврату из процедуры. Тело цикла «возвращает» управление в итератор. Следовательно, затраты на использование итераторов состоят максимум из одного обращения к процедуре на выполнение тела цикла. Такие затраты могут быть меньше из-за оптимизаций компилятора.
Итераторы полезны сами по себе, как это имело место в примерах permutations и primes. Однако их основным применением являются операции над типами данных. В конце книги мы рассмотрим другие примеры их использования.
Дополнительная литература
Atkinson Russel R., Barbara H. Liskov, Robert \V. Scheifler, 1978. Aspects of implementing CLU. Proceedings of the ACM 1978 Annual Conference, pp. 123— 129.
Упражнения
6.1. Задайте некоторую процедуру is.prirne, в кочорой определяечся, простое ли это целое число, и за^ем реализуйте ее, используя итератор allprirnes (рис. 6.10).
6.2. Реализуйте для упорядоченного списка olist итератор big.to_small, который был задан в разд. 6.4.
6.3. Для структуры poly (рис. 4.3), которая выдает все ненулевые термы, было бы полезно иметь итератор terms. Задайте спецификацию для этого итератора. Должен ли он быть некоторой операцией структуры poly?
6.4. Реализуйте итератор terms, заданный в упражнении 6.3, или в в"де операции структуры poly, или в другом виде (по вашему усмотрению), зачем используйте итератор terms для того, чтобы реализовать следующее:
diff = proc (р : poly) returns (poly)
effects Возвращается структура poly, которая является результатом дифференцирования набора р.
6.5. Задайте спецификацию абстракции stack, которая обеспечивает доступ к ее элементам в порядке «последним пришел — первым вышел». Стеки изменяемы. Отметим, что для того, чтобы стеки были полезны для каких-либо приложений (например, чтобы реализовать тип в упражнении 6.6.), должен быть некоторый способ доступа ко всем элементам стека. Мы могли бы задать некоторый итератор,
138 Глава 6
symtab=data type [etype: type] is create, enter.scope, leave_scope, add.id, lookup, depth
Описание
Таблица syrntab является некоторой таблицей символов, разработанной для использования а компиляторе с языка программирования с блочной структурой. Она предоставляет операции для входа в область действия и для выхода из нее, а также операции для добавления и поиска информации об идентификаторах. Таблицы sybtab изменяемы.
Операции
create =•• proc ( ) returns (syrntab) etfects Возвращает некоторый новый пустой элемент syrntab, который
готов воспринимать информацию о самой внешней области действия. enter _scope = proc (s: syrntab)
modifies s
effects Модифицирует набор s так, что он подготовлен для восприятия информации о новой области действия.
leave_scope = proc (s: syrntab) signals (outermost _scope)
modifies s
etfects Если набор s установлен на сам^ю внешнюю область действия, то signals выдает outerrnost_scopc. В прогивном случае из набора s удаляется информация о самой внутренней области действия. Затем набор s позиционируется так, чтобы воспринимать информацию об области действия, которая непосредственно содержит удаленную область действия.
add_id = proc (s: syrntab, id: sti ing, info: etype)
signals (duplicate) modifies s
effects Если строка id уже определена в самой внутренней области действия в наборе s, то signals выдает duplicate. В противном случае к самой внутренней области действия набора s добавляется строка id с соответствующей информацией info.
lookup == proc (s: syrntab, id: string) returns (etype)
signals (not .defined)
effects. Если строка id не определена ни в какой области действия набора s, то signals выдает not_defined. В противном случае возвращается информация, связанная с набором s. Если набор s определен в нескольких областях действия, выдается информация из самой внутренней области действия sthx областей действия. depth == proc (s: syrntab) returns (int)
effects Возвращает число областей действия в наборе s. end syrnlab
Рис. 6.11. Спецификация блочно-структурированной таблицы символов.
который выдает элементы стека от самого последнего элемента, помещенного в стек, до самого первого, или мы могли бы задать некоторую операцию fetch, которая возвращает 1-й элемент для i от 1 до числа элементов в стеке. Какой выбор лучше?
6.6. На рис. 6.11 задана некоторая спецификация абстракции блочно-структурированной таблицы символов, которая обеспечивает операции, хорошо подходящие для компилирования программ, написанных на блочно-структурированном языке. Реализуйте эту абстракцию и дайте инвариантное представление и функцию абстракции. (Указание: используйте стеки (см. упражнение 6.5) и карты (см. упражнение 5.5). Добавьте дополнительные операции для стека и карты, если необходимо, и задайте спецификации для любых таких добавленных операций.)
7. Использование языка Паскаль
В этой книге приводится некоторый метод программирования, основанный на абстракциях. До этой главы мы рассматривали три вида абстракций — процедуры, типы данных и итераторы, —• а для задания конкретных примеров использовали язык CLU. Однако важно не смешивать методы программирования с языком программирования, который используется для описания этих методов. Поэтому в этой главе мы рассмотрим, как можно было бы использовать наш подход для языка программирования Паскаль.
Язык CLU был разработан специально для реализации наших методов, так что он содержит синтаксические элементы, соответствующие каждому из трех видов абстракций. В языке Паскаль непосредственно не обеспечиваются ни абстракции данных или итераций, ни абстракции через параметризацию. Тем не менее при проектировании программ, которые будут реализовываться на языке Паскаль, все же можно с успехом применить эти абстракции. Для этого при проектировании надо использовать преимущества тех методов построения программ, которые хорошо обеспечивает язык Паскаль, и избежать таких методов, которые чрезмерно трудно реализовать. Однако нельзя допускать, чтобы язык программирования слишком влиял на проектирование программы. Хорошие программисты на языке ассемблера давным-давно научились использовать все виды абстракций, которые не существуют в их языке программирования. Многие программисты на языке Паскаль тоже это поняли.
В этой главе мы по очереди рассмотрим абстракции процедур, данных и итераций, а затем приведем пример их использования. Для реализации этих абстракций на языке Паскаль имеется несколько возможных способов. Они могут быть в общем виде разделены на методы, в которых объекты абстракций запоминаются в стеке, и на методы, в которых объекты абстракций хранятся в неупорядоченном массиве. Методы, основанные на неупорядоченном массиве, дают большую гибкость за счет некоторой неэффективности. Здесь мы обсудим только один метод, основанный на неупорядоченном массиве, а в гл. 15 рассмотрим другие альтернативы.
140 Глава 7
Мы предполагаем только незначительное знакомство читателя с языком Паскаль и будем пояснять необычные свойства этого языка по мере их использования. В настоящее время используется много диалектов языка Паскаль, В основном мы пытались ограничиться некоторым подмножеством языка Паскаль, которое является общим для большинства диалектов. Однако часть примеров, приведенных в этой главе, нельзя откомпилировать на некоторых диалектах языка Паскаль. Все наши программы были откомпилированы и просчитаны в системе Турбо-Паскаль.
7.1. Абстракции процедур и функций
В языке Паскаль операции, которые возвращают значения, называются функциями, а те операции, которые не возвращают значения, — процедурами. Мы будем использовать слово операция, когда паши рассуждения касаются и функций, и процедур.
Между процедурами языка CLU и операциями языка Паскаль имеется несколько важных различий. Наиболее существенными являются следующие: 1) операции языка Паскаль могут читать и модифицировать переменные, которые являются глобальными по отношению к вызванной операции; 2) формальные параметры языка Паскаль объявляются или типом var, или по значению. В языке CLU механизмы передачи аргументов совершенно различные; 3) функции языка Паскаль могут вернуть только один аргумент, и этот аргумент должен принадлежать к довольно ограниченному набору типов; 4) в языке Паскаль нет механизма обработки исключительных ситуаций.
7.1.1. Глобальные переменные
Существуют ситуации, в которых глобальные переменные полезны. Больше всего это заметно тогда, когда надо сохранить некоторое состояние между вызовами процедуры. (Во многих других языках программирования это может быть использовано с применением собственных переменных. Описание собственных переменных языка CLU приводится в разд. А.8.5 приложения.)
В общем случае нецелесообразно проектировать абстракции, которые зависят от глобальных переменных, поскольку тогда значение такой абстракции будет зависеть от того контекста, в котором она находится. Это может существенно ограничить повторное ее использование. В этой главе мы не будем использовать глобальные переменные.
7.1.2. Передача параметров и возврат значений
В языке Паскаль имеется два механизма передачи параметров — параметры с типом var передаются по ссылке, а параметры не с типом var — по значению. При вызове по значению
Использование языка Паскаль
фактический аргумент является некоторым выражением (например, х + у). Такое выражение вычисляется, и его значение копируется в формальную переменную данной операции. При вызове по значению надо отметить два важных момента. Во-первых, модификация формального параметра не влияет на фактический параметр. Непонимание этого является частым источником ошибок в программах на языке Паскаль, Во-вторых, вызов по значению связан с накладными расходами для больших значений (например, массивов), поскольку надо выполнять их копирование.
При вызове по ссылке фактический параметр должен быть переменной или «псевдопеременной» (такой, как элемент массива). фактический параметр не может быть выражением. Формальный параметр можно представлять просто как другое имя фактического параметра, т. е. использование формального параметра в вызываемой операции эквивалентно использованию фактического параметра в вызывающей программе.
Функции языка Паскаль похожи на процедуры языка CLU тем, что они могут модифицировать свои аргументы и выдавать некоторый результат. Они могут, однако, возвратить только один результат, а на тип результата накладываются ограничения. В большинстве версий языка Паскаль возвращать можно только скалярные переменные и указатели, и это отразится на соглашениях, которые мы используем для реализации абстрактных типов.
7.1.3. Обработка исключительных ситуаций
При проектировании процедур языка CLU мы часто используем механизм обработки исключительных ситуаций этого языка. Иногда для указания того, что произошла некоторая ошибка, мы используем специальную процедуру обработки исключительных ситуаций failure. Чаще, однако, мы используем обработку исключительных ситуаций просто как еще один способ обмена информацией между вызываемой процедурой и вызывающей программой. В языке Паскаль мы используем различные соглашения для того, чтобы реализовать эти два различных способа применения обработки исключительных ситуаций.
Для обработки сбоев в программах на языке Паскаль мы задаем некоторую процедуру, называемую failure. Эта процедура помещается в начало программы, и к ней может обращаться любая другая операция в этой программе. Спецификация данной процедуры следующая:
procedure failure (s: error, msg)
modifies Выходной поток на основном устройстве вывода. effects Печатает следующую строку на основном устройстве вывода: "Сбой. Программа окончилась из-за:" + s, а затем выполнение программы останавливается.
142 Глава 7
(Мы используем знак плюс для обозначения конкатенации строк. Эта операция обозначается одинаково в версиях языка Паскаль, которые поддерживают строки.) К процедуре failure обращаются тогда, когда в языке CLU явно или неявно будет сигнализировано состояние сбоя failure. Аргумент при обращении должен, конечно, идентифицировать проблему и операцию, в которой она произошла. Обычно тип error.msg будет объявляться как некоторый тип строк. Если в используемом языке Паскаль строки не поддерживаются, аргумент может быть представлен как упакованный массив символов.
search ==ргос^а: int_array, х: int) returns (i: int)
signals (not- in)
effects Если переменная х находится в массиве а, то возвращается такой индекс i, что а [i] == х, в противном случае сигнализируется состояние not- in.
Эта спецификация иллюстрирует использование в языке CLU обработки исключительной ситуации, не связанной со сбоем. В языке Паскаль мы достигаем аналогичного эффекта при помощи следующей конструкции:
function searchi (var a: int. array; х: integer; var i: integer):
search I - exceptions modifies i
effects Если переменная х находится в массиве а, то индекс i устанавливается таким, что а [i] =; х, и возвращается значение ok; в противном случае возвращается значение not-in.
где searchl-exceptions является вычисляемым типом type searchLexceptions == (ok, not-in)
Отметим, что функция searchi имеет третий аргумент — параметр i с типом var. Эта переменная используется для получения результата в нормальном случае.
По соглашению все обращения к этой функции мы вставляем в предложение case следующим образом:
case searchi (а, х, i) of ok:a[i):=a[i]+l; not-in: write («Элемент не найден») end
Предложение case должно иметь ветвь, соответствующую каждому значению, которое может быть возвращено функцией. (Если возможны только две альтернативы, как в случае с функцией searchi, результат мор бы иметь булевский тип, а не вычисляемый тип, и оператор if мог бы использоваться вместо предложения case.)
Этот подход имеет несколько недостатков. Неудобно, что функция searchi модифицирует параметр с типом var вместо выдачи
Использование языка Паскаль
индекса, поскольку мы не сможем тогда ее использовать в таких выражениях, как a [search I (a, x)L Более того, функция searchi не так надежна при использовании, как процедура search. Например, предположим, что мы решили добавить еще одно значение в тип searchi-exceptions. В соответствии с нашими соглашениями надо добавить еще одну ветвь к каждому предложению case, в котором имеется обращение к функции searchi. Если, однако, это не будет сделано, то не возникнет ни ошибки во время компиляции, ни ошибок во время счета. (В языке Паскаль, если переключатель в предложении case не соответствует никакой из ветвей, программа продолжает выполнение g предложения, следующего за предложением case.)