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()
.
См. Раздел «Непрозрачные типы» в руководстве по языку и предложении Swift Evolution для получения дополнительной информации об этой функции.