146 15.2. Отношение наследования
что всякий терм типа S можно безопасно использовать в контексте, где ожидается тип T. Такую точку
зрения на наследование часто называют принципом безопасной подстановки.
Более простое интуитивное объяснение — считать, что S<:T означает «каждое значение, описы-
ваемое термином S, описывается также термином T», то есть, «элементы S являются подмножеством
элементов T». В §15.6 мы увидим, что иногда полезны другие, более сложные, интерпретации наследо-
вания, но такая семантика подмножеств достаточна в большинстве ситуаций.
Связь между отношением типизации и нашим новым отношением наследования обеспечивается
новым правилом типизации — так называемым правилом включения:
Γ t : S S <: T
Γ t : T
(T-Sub)
Это правило говорит, что, если S<:T, то всякий элемент S является также элементом T. Например,
если мы определим отношение наследования так, чтобы {x:Nat,y:Nat} <: {x:Nat}, то при помощи
правила T-Sub мы сможем вывести {x=0,y=1} : {x:Nat}, и, таким образом, присвоить тип нашему
исходному примеру.
15.2. Отношение наследования
Отношение наслодования формально описывается через набор правил вывода, заключением кото-
рых служат утверждения вида S<:T, что читается «S — подтип T» (или «T — надтип S»). Мы рассмотрим
каждую разновидность типов (типы функций, типы записей и т. д.) отдельно; для каждой из них мы
сформулируем правила, формализующие ситуации, когда можно разрешить использовать элементы
одного типа данной разновидности, при том, что ожидается другой тип.
Прежде, чем мы приступим к правилам для конкретных разновидностей типов, мы сделаем два
общих предположения: во-первых, отношение наследования должно быть рефлексивным:
S<:S (S-Refl)
а во-вторых, оно должно быть транзитивным:
S<:U U<:T
S<:T
(S-Trans)
Эти правила прямо следуют из нашей интуиции о безопасности подстановки.
Для типов записей мы уже убедились, что хотелось бы считать тип S { k
1
:S
1
...k
m
:S
m
} подти-
пом T { l
1
:T
1
...l
n
:T
n
}, если T содержит меньше полей, чем S. В частности, безопасно «забывать»
некоторые поля в конце типов записей. Эта интуиция отражена в так называемом правиле наследова-
ния в ширину:
{l
i
:T
i
i 1..n k
} <: {l
i
:T
i
i 1..n
} (S-RcdWidth)
Может показаться странным, что «меньший» тип — подтип – содержит больше полей. Проще всего
это усвоить, если принять более либеральную точку зрения на типы записей, чем в §11.8, и считать,
что тип {x:Nat} описывает «множество записей, имеющих, по крайней мере, поле x типа Nat». Эле-
ментами этого типа являются значения вроде {x=3} и {x=5}, но, кроме того, еще и значения вроде
{x=3,y=100} и {x=3,a=true,b=true}. Подобным образом, тип {x:Nat,y:Nat} описывает записи, име-
ющие, по крайней мере, поля x и y, оба типа Nat. Значениями этого типа являются записи вроде
{x=3,y=100} и {x=3,y=100,z=true}, но не {x=3} и не {x=3,a=true,b=true}. Таким образом, множе-
ство значений, принадлежащих второму типу, является строгим подмножеством множества значений,
принадлежащих первому типу. Более длинный тип-запись представляет собой более жесткую — т. е.,
более информативную, — спецификацию, и, таким образом, описывает меньшее множество значений.
Правило наследования в ширину применимо только к типам записей, где типы общих полей совпа-
дают. Можно также позволить различаться типам отдельных полей, если типы каждого соответству-
ющего поля находятся в отношении наследования. Эту интуицию выражает правило наследования в
глубину:
для каждого i, S
i
<:T
i
{l
i
:S
i
i 1..n
} <: {l
i
:T
i
i 1..n
}
(S-RcdDepth)
Следующее дерево вывода наследования показывает, с использованием правил S-RcdWidth
и S-RcdDepth, что тип вложенных записей {x:{a:Nat,b:Nat},y:{m:Nat}} является подтипом
rev. 104