some View
це непрозорий тип результатів , представлений SE-0244 і доступний у Swift 5.1 з Xcode 11. Ви можете подумати про це як про "зворотний" загальний заповнювач.
На відміну від звичайного загального заповнювача, який задовольняє абонент:
protocol P {}
struct S1 : P {}
struct S2 : P {}
func foo<T : P>(_ x: T) {}
foo(S1()) // Caller chooses T == S1.
foo(S2()) // Caller chooses T == S2.
Непрозорий тип результату - це неявний загальний заповнювач місця, задоволений реалізацією , тому ви можете подумати про це:
func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}
як виглядає так:
func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}
Насправді, кінцева мета цієї функції полягає в тому, щоб дозволити зворотні дженнерики в цій більш явній формі, що також дозволить вам додати обмеження, наприклад -> <T : Collection> T where T.Element == Int
. Дивіться цю публікацію для отримання додаткової інформації .
Головне , щоб забрати з цього є те, що функція , яка повертає some P
одне , що повертає значення певного одного типу бетону , який відповідає P
. Спроба повернути різні відповідні типи в межах функції призводить до помилки компілятора:
// error: Function declares an opaque return type, but the return
// statements in its body do not have matching underlying types.
func bar(_ x: Int) -> some P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Оскільки неявний загальний заповнювач місця не може бути задоволений кількома типами.
Це на відміну від функції P
, що повертається , яку можна використовувати для представлення обох S1
і S2
тому, що вона представляє довільне P
відповідне значення:
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Гаразд, так які переваги мають непрозорі типи результатів -> some P
над типом повернення протоколу -> P
?
1. Непрозорі типи результатів можна використовувати з PAT
Основним обмеженням поточних протоколів є те, що PAT (протоколи з пов'язаними типами) не можуть використовуватися як фактичні типи. Хоча це обмеження, яке, швидше за все, буде скасовано в майбутній версії мови, оскільки непрозорі типи результатів є фактично просто загальними заповнювачами, їх можна використовувати з PAT сьогодні.
Це означає, що ви можете робити такі речі, як:
func giveMeACollection() -> some Collection {
return [1, 2, 3]
}
let collection = giveMeACollection()
print(collection.count) // 3
2. Непрозорі типи результатів мають тотожність
Оскільки непрозорі типи результатів примушують повернути один конкретний тип, компілятор знає, що два виклики до однієї функції повинні повернути два значення одного типу.
Це означає, що ви можете робити такі речі, як:
// foo() -> <Output : Equatable> Output {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}
let x = foo()
let y = foo()
print(x == y) // Legal both x and y have the return type of foo.
Це законно, оскільки укладач знає, що і те, x
і інше y
мають однаковий тип. Це важлива вимога ==
, коли обидва параметри типу Self
.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
Це означає, що він очікує двох значень, які є однотипними як тип бетону. Навіть якби ви Equatable
використовувались як тип, ви б не змогли порівняти два довільних Equatable
відповідних значення, наприклад:
func foo(_ x: Int) -> Equatable { // Assume this is legal.
if x > 10 {
return 0
} else {
return "hello world"
}
}
let x = foo(20)
let y = foo(5)
print(x == y) // Illegal.
Оскільки компілятор не може довести, що два довільних Equatable
значення мають однаковий базовий конкретний тип.
Аналогічним чином, якщо ми ввели ще одну непрозору функцію повернення типу:
// foo() -> <Output1 : Equatable> Output1 {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}
// bar() -> <Output2 : Equatable> Output2 {
func bar() -> some Equatable {
return "" // The opaque result type is inferred to be String.
}
let x = foo()
let y = bar()
print(x == y) // Illegal, the return type of foo != return type of bar.
Приклад стає незаконним , тому що хоча обидва foo
і bar
повернення some Equatable
, їх «зворотним» загальних наповнювачі Output1
і Output2
може бути задоволені різними типами.
3. Непрості типи результатів складаються із загальними заповнювачами
На відміну від регулярних типових протокольних значень, непрозорі типи результатів добре поєднуються із звичайними загальними заповнювачами, наприклад:
protocol P {
var i: Int { get }
}
struct S : P {
var i: Int
}
func makeP() -> some P { // Opaque result type inferred to be S.
return S(i: .random(in: 0 ..< 10))
}
func bar<T : P>(_ x: T, _ y: T) -> T {
return x.i < y.i ? x : y
}
let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Legal, T is inferred to be the return type of makeP.
Це не спрацювало, якби makeP
щойно повернулися P
, оскільки два P
значення можуть мати різні основні типи конкретних, наприклад:
struct T : P {
var i: Int
}
func makeP() -> P {
if .random() { // 50:50 chance of picking each branch.
return S(i: 0)
} else {
return T(i: 1)
}
}
let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Illegal.
Навіщо використовувати непрозорий тип результату над типом бетону?
У цей момент ви можете думати собі, чому б просто не написати код як:
func makeP() -> S {
return S(i: 0)
}
Добре, використання непрозорого типу результату дозволяє зробити тип S
деталі реалізації, викривши лише інтерфейс, наданий компанією P
, надаючи гнучкість зміни конкретного типу пізніше вниз по лінії, не порушуючи коду, який залежить від функції.
Наприклад, ви можете замінити:
func makeP() -> some P {
return S(i: 0)
}
з:
func makeP() -> some P {
return T(i: 1)
}
не порушуючи жодного коду, який дзвонить makeP()
.
Дивіться розділ Непрозорі типи в мовному посібнику та пропозицію про швидку еволюцію для отримання додаткової інформації про цю функцію.