Перечитал тему и «много думал»...
Хочу высказать пару мыслей, никого конкретно не желая обидеть или задеть...
С одной стороны спрашивают: «зачем убрали перечисления»? С другой отвечают что-то про расширения. Первые сопоставляют описания просто констант и описания типов перечислений и говорят про индексы массивов... Вторые – показывают, как можно обёртки делать, «эмулирующие» перечисления...
Но я просто хочу остановиться и спокойно порассуждать.
Что есть тип перечисления? ЧТО мы описываем элементами перечисления в тех языках, которые позволяют это делать? И – при чём здесь множества и адресация элементов массивов?
Начну с последнего.
Вы не замечали, что практически все языки, предоставляющие возможность описывать перечисления, позволяют также назначать конкретные значения перечислителям? Например, как в Си:
Код:
enum AAA { One = -113, Two = 0, Three = -1000000, Four = 16, Five, Six, Seven = 0 };
Значит, идя от общего случая, совершенно не очевидно, что перечислители будут иметь возрастающие значения, и уж тем более – различаться строго на единицу! Можем ли мы, в этом случае, предъявлять претензии к оберонам в том, что вот, мол они, не позволяют нам использовать значения переменных типа перечисления в качестве индексов массивов, когда сами такие «удобства» являются всего лишь ограниченным частным случаем их использования? Более того, на мой взгляд, как раз отсутствие возможности явного задания значений перечислителей при описании перечисления в классическом паскале, - вот был настоящий недостаток! :о)
Отсюда же – и априорная несопоставимость и неравномочность операций pred-succ и inc-dec относительно перечислений и целых чисел. (Хотя, например, уже в Модуле-2 для перехода по значениям перечислителей служили процедуры INC и DEC)
Давайте сделаем «шаг назад» и внимательней рассмотрим перечисления...
Что они собой представляют?
Если хорошенько присмотреться, то можно увидеть, что перечисления – это довольно ранний, частный способ описания объектов литералами. В некотором роде, любое перечисление – есть описание допустимого множества объектов, в которых «идентификатор» («ключ») объектов и «значение» объектов – совпадают. То есть, главное и единственное свойство элементов перечисления – способность быть отличимыми друг от друга в рамках одного (ограниченного!) множества. Естественным следствием такой постановки задачи явилось решение – использовать в качестве «ключа» целые числа, просто как самое удобное и подходящее множество (находящееся в ЭВМ «всегда под руками»)...
Заметьте, что, изначально, перечисления именно и служили в роли «ярлыков» для, например, полей признаков в записях с вариантами... Способность же использовать значения перечислителей в качестве индексов массивов – вторичное следствие представления значения перечислителей целыми числами и выполнения условия «последовательной счётности» этих значений.
Естественно, что нужны некоторые операции для работы с перечислениями. Про succ-pred я уже упомянул. Нужно ещё сравнение – для анализа и выбора варианта действия с носителем поля типа перечисления. Для последней операции можно использовать «многочисленнорастянутый» IF, но это было бы слишком громоздко. Во многих языках? В том числе и оберонах, есть вещь лучше – CASE. Причём, в оберонах у него ещё и «глубокий сакральный» смысл :о), напрямую связанный с вопросом так называемого «расширения перечисления»!
Пример:
Предположим, у нас есть некий библиотечный модуль...
Код:
MODULE A;
...
CONST
enumAAA_1* = 1;
enumAAA_2* = 12;
enumAAA_3* = 987654321;
...
END A.
И есть ещё одна библиотека, содержащая модуль BA, импортирующий модуль А из первой библиотеки...
Код:
MODULE BA;
IMPORT A;
TYPE
AnyType = POINTER TO EXTENSIBLE RECORD ... END;
...
PROCEDURE (this: AnyType) AnalizeEnumAAAValue* ( enumAAAValue : INTEGER );
BEGIN
...
CASE enumAAAValue OF
| enumAAA_1 : DoSomethingOnenumAAA_1;
| enumAAA_2 : DoSomethingOnenumAAA_2;
| enumAAA_3 : DoSomethingOnenumAAA_3;
END;
...
END AnalizeEnumAAAValue;
...
END BA.
Причём, исходники BA нам не доступны! То, что я написал в виде кода некоего модуля второй библиотеки - «раскрытие семантики», просто то, что нам известно об A и BA.
Какое-то время мы совершенно успешно пользуемся и первой, и второй библиотекой.
Потом автор модуля A добавляет в свой модуль дополнительную константу:
Код:
MODULE A;
...
CONST
...
enumAAA_4* = -1;
...
END A.
Является ли это для нас каким-то неудобством, в случае сохранения значения старых enumAAA_* ? Нет! Мы просто допишем дополнительную реакцию на добавленные значения.
Код:
MODULE OurModule;
IMPORT A, BA;
TYPE AnyTypeInherited = POINTER TO RECORD ( AnyType ) ... END;
...
PROCEDURE (this: AnyTypeInherited) AnalizeEnumAAAValue* ( enumAAAValue : INTEGER );
BEGIN
...
CASE enumAAAValue OF
| enumAAA_4 : DoSomethingOnenumAAA_4;
| enumAAA_1, enumAAA_2, enumAAA_3 : AnalizeEnumAAAValue^( enumAAAValue );
ELSE (* !!!!!!!!!!!!!!!!!!!!!!! *)
END;
...
END AnalizeEnumAAAValue;
...
END OurModule.
Более того, я вижу дополнительные преимущества. Если бы у нас всё-таки была возможность объявлять в оберонах «расширяемые» перечисления, то наша система просто перестала бы компилироваться, так, как САМ ТИП перечисления в модуле A стал уже ДРУГИМ.
А так мы просто расширили «набор ярлыков», способных обрабатываться в нашей программе.
Кроме того, та самая обероновская вкусность здесь и проявляется. В месте, где стоят в комментариях многочисленные восклицательные знаки. По сути дела, способность CASE в обероне прекращать аварийно программу в случае несовпадения проверяемого значения с описанными в вариантах проверок и показывают нам на недоработку проекта. В других языках, не смотря на то, что есть чёткое определение типа перечисления, нерассмотрение всех возможных значений ни к чему такому не приводит, есть там ELSE, или нет его...
В принципе, здесь получаются «сами по себе» хорошие проектные решения. Сторонняя библиотека с модулем BA работала «в рамках» тех знаний, что предоставлялись ей модулем A на момент её описания. Если библиотека с модулем A написана грамотно и аккуратно (сохранение заявленных и опубликованных изначально интерфейсов и расширяемая запись AnyType или аналогичная экспортируемая процедура-не метод обработки значений «перечисления»), то мы, как раз и имеем расширяемую систему. Просто модуль BA так и функционирует во «вселенной» понятий, существовавшей на момент его написания (известные интерфейсы), а мы уже расширяем функциональность нашей системы, дополняя её способностью обрабатывать случаи, не известные модулю BA, БЕЗ ЕГО ПЕРЕКОМПИЛЯЦИИ.
Однако, может так случиться, автор второй библиотеки не предоставил в открытый доступ процедуры (метода) обработки «перечисления». И в этом случае – мы «не в накладе» - просто потому, что мы и так не знали, что такая обработка имела место, а значит и ничего переделывать или добавлять не придётся... :о)