Поскольку Any
может содержать любой тип, а String - более низкий тип, почему я не могу привести функцию (String) -> String
к функции (Any) -> Any
?
Потому что эти двое не имеют ничего общего друг с другом.
Да, это правда, что String
<: <code>Any. Это означает, что вы можете разыграть String
до Any
. Но вы не пытаетесь разыграть String
на Any
. Вы пытаетесь разыграть (String) -> String
(Any) -> Any
, то есть двух совершенно разных типов из String
и Any
. (String) -> String
- это не то же самое, что String
, а (Any) -> Any
- это не то же самое, что Any
, поэтому нет абсолютно никаких причин, по которым отношения, которые имеют место для String
и Any
, также должны автоматически сохраняться для (String) -> String
и (Any) -> Any
… и, как вы выяснили, отношения, на самом деле, не сохраняются.
Краткий ответ: функции являются контравариантными по своим типам параметров и ковариантными по своему типу возвращаемого значения. Следовательно, (String) -> String
будет подтипом из (String) -> Any
и супертипом из (Any) -> String
, и это не будет ни подтипом, ни супертипом (Any) -> Any
.
В начале 1970-х годов ученый-компьютерщик по имени Барбара Лисков изобрел новый способ мышления о подтипах в терминах поведенческой замены , который мы теперь называем Принцип замены Лискова (LSP), Мы можем использовать LSP для точного объяснения , почему функции являются контравариантными по своим типам параметров и ковариантными по своему типу возврата. (Примечание: это было хорошо известно еще до Лискова, но LSP дает нам хороший способ объяснить, почему это так.)
Принцип замещения Барбары Лисков говорит нам, что тип S
является подтипом типа T
IFF , любой экземпляр T
может быть заменен экземпляром S
без изменения наблюдаемых желательных свойств программы.
Давайте возьмем простой обобщенный тип, функцию. Функция имеет два параметра типа, один для ввода и один для вывода. (Здесь мы все упрощаем.) (A) -> B
- это функция, которая принимает аргумент типа A
и возвращает результат типа B
.
А теперь мы разыгрываем пару сценариев. У меня есть какая-то операция O , которая хочет работать с функцией от Fruit
s до Mammal
s (да, я знаю, интересные оригинальные примеры!) LSP говорит, что я также должен быть в состоянии передать в подтипе этой функции, и все должно работать. Допустим, функции были ковариантными в A
. Тогда я смогу передать функцию от Apple
с до Mammal
с. Но что происходит, когда O передает Orange
в функцию? Это должно быть разрешено! O смог передать Orange
в (Fruit) -> Mammal
, потому что Orange
является подтипом Fruit
. Но функция из Apple
s не знает, как обращаться с Orange
s, поэтому она взрывается. LSP говорит, что это должно работать, но это означает, что единственный вывод, который мы можем сделать, состоит в том, что наше предположение неверно: (Apple) -> Mammal
не является подтипом (Fruit)-> Mammal
, другими словами, функции не являются ковариантными в A
.
Что если бы это было контравариантно? Что если мы передадим (Food) -> Mammal
в O ? Ну, O снова пытается передать Orange
, и это работает: Orange
- это Food
, поэтому (Food) -> Mammal)
знает, как обращаться с Orange
s. Теперь мы можем заключить, что функции контравариантны на своих входах, то есть вы можете передать функцию, которая принимает более общий тип, в качестве входа для замены функции, которая принимает более ограниченный тип, и все будет работать хорошо.
Теперь давайте посмотрим на тип возвращаемого значения функции. Что произойдет, если функции будут B
контравариантными, как и в A
? Мы передаем (Fruit) -> Animal
в O . Согласно LSP, если мы правы, а функции обратны по типу возврата, ничего плохого не должно произойти. К сожалению, O вызывает метод getMilk
для результата функции, но функция просто вернула ему Chicken
. К сожалению. Поэтому функции не могут быть контравариантными в своих типах возвращаемых значений.
OTOH, что произойдет, если мы передадим (Fruit) -> Cow
? Все еще работает! O звонит getMilk
на возвращенную корову, и она действительно дает молоко. Таким образом, похоже, что функции являются ковариантными в своих выходах.
И это общее правило, которое применяется к дисперсии:
- Безопасно (в смысле LSP) сделать
C<A>
ковариантным в A
IFF A
используется только как выход.
- Безопасно (в смысле LSP) сделать
C<A>
контравариантным в A
IFF A
используется только как вход.
- Если
A
можно использовать либо как вход, либо как выход, то C<A>
должен быть инвариантным в A
, иначе результат небезопасен.