Однократные процедуры решают эту проблему лучше:
check_setup is
-- Настроить терминал, если это еще не сделано.
once
terminal_setup -- Фактические действия по настройке.
end
Теперь каждая экранная функция должна начинаться с обращения к check_setup, первый вызов которой приведет к настройке параметров, а остальные не сделают ничего. Заметьте, что check_setup не должна экспортироваться клиентам.
Однократная процедура - это важный прием, упрощающий применение библиотек и других программных пакетов.
Параметры
Однократные процедуры и функции могут иметь параметры, необходимые, по определению, лишь при первом вызове.
Однократные функции, закрепление и универсальность
В этом разделе мы обсудим конкретную техническую проблему, поэтому при первом чтении книги его можно пропустить.
Однократные функции, тип которых не является встроенным, вносят потенциальную несовместимость с механизмом закрепления типов и универсальностью.
Начнем с универсальности. Пусть в родовом классе EXAMPLE [G] есть однократная функция, чей тип родовой параметр:
f: G is once ... end
Рассмотрим пример ее использования:
character_example: EXAMPLE [CHARACTER]
...
print (character_example.f)
Пока все в порядке. Но если попытаться получить константу с другим родовым параметром:
integer_example: EXAMPLE [INTEGER]
...
print (integer_example.f + 1)
В последней инструкции мы складываем два числа. Первое значение, результат вызова f, к сожалению, уже найдено, поскольку f - однократная функция, причем символьного, а не числового типа. Сложение окажется недопустимым.
Проблема заключается в попытке разделения значения разными формами родового порождения, ожидающими значения, тип которого определяется родовым параметром. Аналогичная ситуация возникает и с закреплением типов. Представим себе класс B, добавляющий еще один атрибут к компонентам своего родителя A:
class B inherit A feature
attribute_of_B: INTEGER
end
Пусть A имеет однократную функцию f, возвращающую результат закрепленного типа:
f: like Current is once create Result make end
и пусть первый вызов функции f имеет вид:
a2 := a1.f
где a1 и a2 имеют тип A. Вычисление f создаст экземпляр A и присоединит его к сущности a2. Все прекрасно. Но предположим, далее следует:
b2 := b1.f
где b1 и b2 имеют тип B. Не будь f однократной функцией, никакой проблемы бы не возникло. Вызов f породил бы экземпляр класса B и вернул его в качестве результата. Но функция является однократной, а ее результат был уже найден при первом вызове. И это - экземпляр A, но не B. Поэтому инструкция вида:
print (b2.attribute_of_B)
попытается обратиться к несуществующему полю объекта A.
Проблема в том, что закрепление вызывает неявное переопределение типов. Если бы f была переопределена явно, с применением в классе B объявления
f: B is once create Resultl make end
при условии, что исходный вариант f в классе A возвращает результат типа A (а не like Current), все было бы замечательно: экземпляры A обращались бы к версии f для A, экземпляры B - к версии f для B. Однако закрепление типов было введено как раз для того, чтобы избавить нас от таких явных переопределений.
Эти примеры - свидетельства несовместимости семантики однократных функций (с процедурами все прекрасно) с результатами применения закрепленных типов и формальных родовых параметров. Одно из решений проблемы в том, чтобы трактовать такие случаи как явные переопределения, приняв за правило то, что результат однократной функции совместно используется лишь в пределах одной формы родовой порождения, а при закреплении результата - лишь среди экземпляров своего класса. Недостатком такого подхода, впрочем, является, что он не отвечает интуитивной семантике однократных функций, которые, с позиции клиента, должны быть эквивалентны разделяемым атрибутам. Во избежание недоразумений и возможных ошибок можно пойти на более суровые меры, наложив полный запрет на сценарии подобного рода:
Правило для однократной функции
Тип результата однократной функции не может быть закреплен и не может включать любой родовой параметр.
Константы строковых типов
В начале этой лекции были введены символьные константы, значением которых является символ. Например:
Backslash: CHARACTER is '\'
Однако нередко классам требуются строковые константы, использующие, как обычно, для записи константы двойные кавычки:
[S1]
Message: STRING is "Syntax error" -- "Синтаксическая ошибка"
Вспомните, что STRING - не простой тип. Это - библиотечный класс, поэтому значение, связанное с сущностью Message во время работы программы, является объектом, то есть экземпляром STRING. Как вы могли догадаться, такое описание является сокращенной формой объявления однократной функции вида:
[S2]
Message: STRING is
-- Строка из 12 символов
once
create Result.make (12)
Result.put ('S', 1)
Result.put ('y', 2)
...
Result.put ('r', 12)
end
Строковые значения являются не константами, а ссылками на разделяемые объекты. Любой класс, имеющий доступ к Message, может изменить значение одного или нескольких символов строки. Строковые константы можно использовать и как выражения при передаче параметров или присваивании: