Вот еще один ответ, потому что, хотя я понимал, как правила подтипов функций имели смысл, я хотел разобраться, почему любая другая комбинация подтипов аргумент / результат не дает.
Правило подтипа:
Это означает, что если условия верхнего подтипа выполнены, то нижнее значение сохраняется.
В определении типа функции аргументы функции являются контравариантными , так как мы изменили отношение подтипа между T1
и S1
. Результаты функции являются ковариантными , поскольку они сохраняют отношение подтипа между T2
и S2
.
С определением вне пути, почему это правило такое? Это хорошо указано в ответе Аарона Фи, и я также нашел определение здесь (поиск по заголовку «Типы функций»):
Альтернативное представление состоит в том, что можно разрешить функцию одного типа
S1 → S2
для использования в контексте, где ожидается другой тип T1 → T2
пока ни один из аргументов, которые могут быть переданы функции в этом контексте, не удивит ее (T1 <: S1
), и ни один из возвращаемых результатов не удивит контекст (S2 <: T2
).
Опять же, это имело смысл для меня, но я хотел понять, почему никакая другая комбинация правил ввода не имеет смысла. Для этого я рассмотрел простую функцию высшего порядка и несколько примеров типов записей.
Для всех приведенных ниже примеров:
S1 := {x, y}
T1 := {x, y, z}
T2 := {a}
S2 := {a, b}
Пример с контравариантными типами аргументов и ковариантными типами возвращаемых данных
Пусть:
f1
имеют тип S1 → S2 ⟹ {x, y} → {a, b}
f2
имеют тип T1 → T2 ⟹ {x, y, z} → {a}
Теперь предположим, что type(f1) <: type(f2)
. Мы знаем это из приведенного выше правила, но давайте представим, что нет, и просто посмотрим, почему это имеет смысл.
Мы бежим map( f2 : {x, y, z} → {a}, L : [ {x, y, z} ] ) : [ {a} ]
Если мы заменим f2
на f1
, мы получим:
map( f1 : {x, y} → {a, b}, L : [ {x, y, z} ] ) : [ {a, b} ]
Это хорошо работает, потому что:
- Независимо от того, что функция
f1
делает со своим аргументом, она может игнорировать дополнительное поле записи z
и не иметь проблем.
- Независимо от того, что контекст
map
делает с результатами, он может игнорировать
дополнительное поле записи b
и проблем нет.
Заключительное:
{x, y} → {a, b} ⟹ {x, y, z} → {a} ✔
Пример с ковариантными типами аргументов и ковариантными типами возвращаемых данных
Пусть:
f1
имеют тип T1 → S2 ⟹ {x, y, z} → {a, b}
f2
имеют тип S1 → T2 ⟹ {x, y} → {a}
Предположим, type(f1) <: type(f2)
Мы бежим map( f2 : {x, y} → {a}, L : [ {x, y} ] ) : [ {a} ]
Если мы заменим f2
на f1
, мы получим:
map( f1 : {x, y, z} → {a, b}, L : [ {x, y} ] ) : [ {a, b} ]
Здесь мы можем столкнуться с проблемой, потому что f1
ожидает и может работать с полем записи z
, и такого поля нет ни в одной записи в списке L
. ⚡
Пример с контравариантными типами аргументов и контравариантными типами возвращаемых данных
Пусть:
f1
имеют тип S1 → T2 ⟹ {x, y} → {a}
f2
имеют тип T1 → S2 ⟹ {x, y, z} → {a, b}
Предположим type(f1) <: type(f2)
Мы бежим map( f2 : {x, y, z} → {a, b}, L : [ {x, y, z} ] ) : [ {a, b} ]
Если мы заменим f2
на f1
, мы получим:
map( f1 : {x, y} → {a}, L : [ {x, y, z} ] ) : [ {a} ]
У нас нет проблем с игнорированием поля записи z
при передаче в f1
, но если контекст, вызывающий map
, ожидает список записей с полем b
, мы увидим ошибку. ⚡
Пример с ковариантными типами аргументов и контравариантным возвратом
Посмотрите на приведенные выше примеры для двух мест, где это может пойти не так.
Заключение
Это очень длинный и подробный ответ, но мне пришлось записать этот материал, чтобы выяснить, почему другие типы аргументов и возвращаемых параметров недопустимы. Поскольку у меня это было несколько записано, я решил, почему бы не опубликовать это здесь.