some View
é um tipo de resultado opaco, conforme introduzido pela SE-0244 e está disponível no Swift 5.1 com Xcode 11. Você pode pensar nisso como um espaço reservado genérico "reverso".
Ao contrário de um espaço reservado genérico regular que é atendido pelo chamador:
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.
Um tipo de resultado opaco é um espaço reservado genérico implícito satisfeito com a implementação , para que você possa pensar sobre isso:
func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}
como esta:
func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}
De fato, o objetivo final com esse recurso é permitir genéricos reversos dessa forma mais explícita, o que também permitiria adicionar restrições, por exemplo -> <T : Collection> T where T.Element == Int
. Veja este post para mais informações .
A principal coisa a tirar de tudo isto é que uma função que retorna some P
é aquele que retorna um valor de um específico único tipo concreto que está em conformidade com P
. Tentar retornar diferentes tipos conformes dentro da função gera um erro do compilador:
// 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()
}
}
Como o espaço reservado genérico implícito não pode ser satisfeito por vários tipos.
Isso contrasta com uma função retornada P
, que pode ser usada para representar ambos S1
e S2
porque representa um P
valor conforme arbitrário :
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Certo, quais benefícios os tipos de resultados opacos -> some P
têm sobre os tipos de retorno de protocolo -> P
?
1. Tipos de resultado opacos podem ser usados com PATs
Uma grande limitação atual dos protocolos é que os PATs (protocolos com tipos associados) não podem ser usados como tipos reais. Embora seja uma restrição que provavelmente será levantada em uma versão futura do idioma, porque os tipos de resultados opacos são efetivamente apenas espaços reservados genéricos, eles podem ser usados com PATs hoje.
Isso significa que você pode fazer coisas como:
func giveMeACollection() -> some Collection {
return [1, 2, 3]
}
let collection = giveMeACollection()
print(collection.count) // 3
2. Tipos de resultado opacos têm identidade
Como os tipos de resultados opacos impõem que um único tipo concreto seja retornado, o compilador sabe que duas chamadas para a mesma função devem retornar dois valores do mesmo tipo.
Isso significa que você pode fazer coisas como:
// 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.
Isso é legal porque o compilador conhece os dois x
e y
tem o mesmo tipo concreto. Este é um requisito importante para ==
, onde ambos os parâmetros do tipo Self
.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
Isso significa que ele espera dois valores que são do mesmo tipo que o tipo de conformidade concreto. Mesmo se Equatable
fosse utilizável como um tipo, você não seria capaz de comparar dois Equatable
valores de conformidade arbitrários entre si, por exemplo:
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.
Como o compilador não pode provar que dois Equatable
valores arbitrários têm o mesmo tipo concreto subjacente.
De maneira semelhante, se introduzirmos outra função de retorno de 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.
O exemplo se torna ilegal porque, embora ambos foo
e bar
retornem some Equatable
, seus espaços reservados genéricos "reversos" Output1
e Output2
podem ser satisfeitos por diferentes tipos.
3. Tipos de resultado opacos são compostos por espaços reservados genéricos
Ao contrário dos valores regulares do tipo de protocolo, os tipos de resultados opacos são bem compostos com espaços reservados genéricos regulares, por exemplo:
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.
Isso não teria funcionado se makeP
tivesse acabado de retornar P
, pois dois P
valores podem ter tipos concretos subjacentes diferentes, por exemplo:
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.
Por que usar um tipo de resultado opaco sobre o tipo de concreto?
Nesse ponto, você pode estar pensando por que não escrever o código como:
func makeP() -> S {
return S(i: 0)
}
Bem, o uso de um tipo de resultado opaco permite tornar o tipo S
um detalhe de implementação, expondo apenas a interface fornecida P
, oferecendo flexibilidade de alterar o tipo de concreto posteriormente na linha sem quebrar nenhum código que dependa da função.
Por exemplo, você pode substituir:
func makeP() -> some P {
return S(i: 0)
}
com:
func makeP() -> some P {
return T(i: 1)
}
sem quebrar nenhum código que chama makeP()
.
Consulte a seção Tipos opacos do guia de idiomas e a proposta de evolução Swift para obter mais informações sobre esse recurso.