Почему я не могу разыграть (String) -> String (Any) -> Any в Swift? - PullRequest
0 голосов
/ 06 июня 2019

Поскольку Any может содержать любой тип, а String - более низкий тип, почему я не могу привести функцию (String) -> String к функции (Any) -> Any?

func lower(_ s: String) -> String {
    return s.lowercased()
}

func upper(_ s: String) -> String {
    return s.uppercased()
}

func foo(_ s: @escaping (Any) -> Any) -> (Any) -> Any {
    return s
}


let f = foo(lower as! (Any) -> Any)  // error: Execution was interrupted, reason: signal SIGABRT.

f("ABC")

1 Ответ

1 голос
/ 06 июня 2019

Поскольку 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, иначе результат небезопасен.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...