220 20.2. Формальные определения
теоремы о типовой безопасности и доказательства остаются неизмененными, поскольку они не
зависят от индукции по типовым выражениям (которая, естественно, больше не работает).
Конечно, реализация эквирекурсивных типов требует некоторой работы, поскольку алгоритмы
проверки типов не могут напрямую работать с бесконечными структурами. Детали того, как это
может быть осуществлено, приводятся в главе 21.
2. С другой стороны, при изорекурсивном подходе рекурсивный тип и результат шага его развертки
считаются различными, но изоморфными типами.
С формальной точки зрения, раскрытием рекурсивного типа µX.T является тип, полученный пу-
тем взятия тела T и замены всех вхождений X на весь рекурсивный тип, т. е., используя стандарт-
ную нотацию для подстановки, X µX.T T. Например, тип NatList, т. е.
µX .< nil : Unit , cons :{ Nat ,X} > ,
раскрывается в
<nil : Unit , cons :{ Nat , µX .< nil : Unit , cons :{ Nat ,X } >} >.
В системе с изорекурсивными типами для каждого рекурсивного типа µX.T мы вводим пару
функций
unfold[µX.T] : µX.T X µX.T T
fold[µX.T] : X µX.T T µX.T
которые «свидетельствуют об изоморфизме» через отображение (в обе стороны) значений между
двумя типами:
µX.T
unfold[µcodeX.T]
''
[X µX.T] T
fold[µX.T]
ii
Отображения fold и unfold реализованы в качестве примитивов языка, как это показано на
Рис. 20.2. То, что они образуют изоморфизм, отражается в правиле вычисления E-UnfldFld,
уничтожающем выражение fold, когда оно оказывается в паре с соответствующим unfold. (Пра-
вило вычисления не требует, чтобы аннотации типов для fold и unfold были одинаковыми, по-
скольку для проверки такого ограничения нам пришлось бы запускать процедуру проверки типов
во время выполнения. Однако в правильно типизированной программе эти две аннотации типов
будут равны при всяком применении E-UnfldFld.)
Оба подхода широко используются как в теоретических исследованиях, так и при проектировании
языков программирования. Эквирекурсивный стиль, пожалуй, легче понять интуитивно, но он более
требователен к процедуре проверки типов, поскольку она должна эффективно находить точки, где
следует добавить аннотации fold и unfold. Более того, взаимодействие между эквирекурсивными ти-
пами и другими нетривиальными типовыми возможностями, такими, как ограниченная квантифиации
и операторы над типами, бывает достаточно сложным и приводит к значительным теоретическим труд-
ностям (см., например, Ghelli, 1993; Colazzo and Ghelli, 1999) или даже к тому, что задача проверки
типов оказывается неразрешимой (Solomon, 1978).
Изорекурсивный стиль несколько тяжеловеснее по части обозначений — он требует, чтобы про-
граммы были оформлены с помощью инструкций fold и unfold в каждой точке, где употребляются
рекурсивные типы. Однако на практике эти аннотации часто можно «спрятать», слив их с аннотациями
другого рода. Например, в языках семейства ML каждое определение datatype неявно вводит рекур-
сивный тип. Каждое использование одного из конструкторов при построении значения конкретного
типа данных неявно включает fold, а конструктор, встречающийся в сопоставлении с образцом, неяв-
но применяет unfold. Подобным образом, в Java каждое определение класса вводит рекурсивный тип,
и выполнение метода объекта включает неявный unfold. Это удачное перекрытие механизмов делает
изорекурсивный стиль достаточно удобным для практического использования.
Например, вот пример NatList в изорекурсивном виде. Удобно будет сначала определить сокраще-
ние для развернутой формы NatList:
rev. 104