some View
è un tipo di risultato opaco come introdotto da SE-0244 ed è disponibile in Swift 5.1 con Xcode 11. Puoi considerarlo come un segnaposto generico "inverso".
A differenza di un normale segnaposto generico che è soddisfatto dal chiamante:
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.
Un tipo di risultato opaco è un segnaposto generico implicita soddisfatto dalla realizzazione , in modo da poter pensare a questo:
func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}
come questo:
func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}
In effetti, l'obiettivo finale con questa funzione è consentire generici inversi in questa forma più esplicita, che consentirebbe anche di aggiungere vincoli, ad es. -> <T : Collection> T where T.Element == Int
. Vedi questo post per maggiori informazioni .
La cosa principale da trarre da questo è che una funzione che restituisce some P
è quella che restituisce un valore di uno specifico singolo tipo concreto che è conforme a P
. Il tentativo di restituire diversi tipi conformi all'interno della funzione genera un errore del compilatore:
// 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()
}
}
Poiché il segnaposto generico implicito non può essere soddisfatto da più tipi.
Ciò è in contrasto con una funzione di ritorno P
, che può essere utilizzata per rappresentare entrambi S1
e S2
perché rappresenta un P
valore conforme arbitrario :
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Bene, quindi quali vantaggi -> some P
hanno i tipi di risultati opachi rispetto ai tipi di ritorno del protocollo-> P
?
1. I tipi di risultato opachi possono essere utilizzati con i PAT
Una delle principali limitazioni attuali dei protocolli è che i PAT (protocolli con tipi associati) non possono essere usati come tipi effettivi. Sebbene questa sia una restrizione che verrà probabilmente sollevata in una versione futura della lingua, poiché i tipi di risultati opachi sono effettivamente solo segnaposto generici, possono essere usati oggi con i PAT.
Questo significa che puoi fare cose come:
func giveMeACollection() -> some Collection {
return [1, 2, 3]
}
let collection = giveMeACollection()
print(collection.count) // 3
2. I tipi di risultati opachi hanno identità
Poiché i tipi di risultato opachi impongono il ritorno di un singolo tipo di calcestruzzo, il compilatore sa che due chiamate alla stessa funzione devono restituire due valori dello stesso tipo.
Questo significa che puoi fare cose come:
// 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.
Questo è legale perché il compilatore sa che entrambi x
e y
hanno lo stesso tipo concreto. Questo è un requisito importante per ==
, in cui entrambi i parametri di tipo Self
.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
Ciò significa che prevede due valori che sono entrambi dello stesso tipo del tipo conforme concreto. Anche se Equatable
fosse utilizzabile come tipo, non saresti in grado di confrontare due Equatable
valori arbitrari conformi tra loro, ad esempio:
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.
Dal momento che il compilatore non può dimostrare che due Equatable
valori arbitrari abbiano lo stesso tipo concreto sottostante.
In modo simile, se introducessimo un'altra funzione di ritorno del tipo opaco:
// 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.
L'esempio diventa illegale perché anche se entrambi foo
e bar
ritorno some Equatable
, il loro "reverse" segnaposto generici Output1
e Output2
potrebbero essere soddisfatte da diversi tipi.
3. I tipi di risultati opachi si compongono con segnaposti generici
A differenza dei normali valori tipizzati dal protocollo, i tipi di risultati opachi si compongono bene con segnaposto generici regolari, ad esempio:
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.
Ciò non avrebbe funzionato se makeP
fosse appena tornato P
, poiché due P
valori potrebbero avere diversi tipi di calcestruzzo sottostanti, ad esempio:
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.
Perché utilizzare un tipo di risultato opaco sul tipo di calcestruzzo?
A questo punto potresti pensare a te stesso, perché non scrivere semplicemente il codice come:
func makeP() -> S {
return S(i: 0)
}
Bene, l'uso di un tipo di risultato opaco consente di rendere il tipo S
un dettaglio di implementazione esponendo solo l'interfaccia fornita daP
, dandoti la flessibilità di cambiare il tipo concreto in un secondo momento lungo la linea senza rompere alcun codice che dipende dalla funzione.
Ad esempio, è possibile sostituire:
func makeP() -> some P {
return S(i: 0)
}
con:
func makeP() -> some P {
return T(i: 1)
}
senza rompere alcun codice che chiama makeP()
.
Consulta la sezione Tipi opachi della guida linguistica e la proposta di evoluzione di Swift per ulteriori informazioni su questa funzione.