some View
jest nieprzezroczystym typem wyniku wprowadzonym przez SE-0244 i jest dostępny w Swift 5.1 z Xcode 11. Można to traktować jako ogólny symbol zastępczy „odwrotnej”.
W przeciwieństwie do zwykłego ogólnego symbolu zastępczego, który jest wywoływany przez osobę dzwoniącą:
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.
Nieprzezroczysty typ wyniku to domyślny ogólny symbol zastępczy spełniony przez implementację , więc możesz pomyśleć o tym:
func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}
wygląda tak:
func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}
W rzeczywistości ostatecznym celem tej funkcji jest zezwolenie na odwrotne generyczne w tej bardziej wyraźnej formie, co pozwoliłoby również na dodanie ograniczeń, np -> <T : Collection> T where T.Element == Int
. Zobacz ten post, aby uzyskać więcej informacji .
Najważniejszą rzeczą do usunięcia jest some P
to, że zwracana funkcja to taka, która zwraca wartość określonego pojedynczego konkretnego typu, który jest zgodny P
. Próba zwrócenia różnych zgodnych typów w ramach funkcji powoduje błąd kompilatora:
// 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()
}
}
Ponieważ domyślny ogólny symbol zastępczy nie może być spełniony przez wiele typów.
Jest to w przeciwieństwie do zwracanej funkcji P
, która może być używana do reprezentowania obu S1
i S2
ponieważ reprezentuje dowolną P
zgodną wartość:
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Okej, więc jakie korzyści mają nieprzejrzyste typy wyników w -> some P
porównaniu do typów zwracanych przez protokół -> P
?
1. Nieprzezroczyste typy wyników mogą być używane z PAT
Głównym bieżącym ograniczeniem protokołów jest to, że PAT (protokoły z powiązanymi typami) nie mogą być używane jako rzeczywiste typy. Chociaż jest to ograniczenie, które prawdopodobnie zostanie zniesione w przyszłej wersji języka, ponieważ nieprzezroczyste typy wyników są w rzeczywistości zwykłymi symbolami zastępczymi, można je dziś stosować z PAT.
Oznacza to, że możesz robić takie rzeczy jak:
func giveMeACollection() -> some Collection {
return [1, 2, 3]
}
let collection = giveMeACollection()
print(collection.count) // 3
2. Nieprzezroczyste typy wyników mają tożsamość
Ponieważ nieprzezroczyste typy wyników wymuszają zwrócenie jednego konkretnego typu, kompilator wie, że dwa wywołania tej samej funkcji muszą zwrócić dwie wartości tego samego typu.
Oznacza to, że możesz robić takie rzeczy jak:
// 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.
Jest to legalne, ponieważ kompilator wie o tym x
i y
ma ten sam konkretny typ. Jest to ważny wymóg ==
, w przypadku gdy oba parametry typu Self
.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
Oznacza to, że oczekuje dwóch wartości, które są tego samego typu, co typ zgodny z betonem. Nawet jeśli Equatable
byłyby użyteczne jako typ, nie byłoby możliwe porównanie dwóch dowolnych Equatable
zgodnych wartości, na przykład:
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.
Ponieważ kompilator nie może udowodnić, że dwie dowolne Equatable
wartości mają ten sam podstawowy typ betonu.
W podobny sposób, jeśli wprowadzimy inną funkcję zwracania typu nieprzezroczystego:
// 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.
Przykład staje się nielegalny, ponieważ mimo foo
i oba bar
zwracają some Equatable
, ich „odwrócone” ogólne symbole zastępcze Output1
i Output2
mogą być zaspokojone przez różne typy.
3. Nieprzezroczyste typy wyników składają się z ogólnych symboli zastępczych
W przeciwieństwie do zwykłych wartości typowych dla protokołu, nieprzezroczyste typy wyników dobrze komponują się ze zwykłymi rodzajowymi symbolami zastępczymi, na przykład:
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.
To nie zadziałałoby, gdyby makeP
właśnie wróciło P
, ponieważ dwie P
wartości mogą mieć różne podstawowe typy betonu, na przykład:
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.
Dlaczego warto stosować nieprzejrzysty typ wyniku zamiast rodzaju betonu?
W tym momencie możesz pomyśleć, dlaczego nie napisać kodu jako:
func makeP() -> S {
return S(i: 0)
}
Cóż, użycie nieprzezroczystego typu wyniku pozwala uczynić ten typ S
szczegółem implementacji, odsłaniając tylko interfejs zapewniony przez P
, co daje elastyczność zmiany konkretnego typu później w dół linii bez łamania kodu zależnego od funkcji.
Na przykład możesz zastąpić:
func makeP() -> some P {
return S(i: 0)
}
z:
func makeP() -> some P {
return T(i: 1)
}
bez łamania wywoływanego kodu makeP()
.
Więcej informacji na temat tej funkcji znajduje się w sekcji Typy nieprzezroczyste przewodnika językowego i propozycji Szybkiej ewolucji .