some View
是SE-0244引入的不透明结果类型,在带有Xcode 11的Swift 5.1中可用。您可以将其视为“反向”通用占位符。
与调用方可以满足的常规通用占位符不同:
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()
}
}
因为隐式通用占位符不能由多种类型满足。
这与returning函数相反P
,后者可以用来表示两者, S1
并且S2
因为它表示任意P
符合的值:
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
好的,不透明结果类型-> some P
比协议返回类型有什么好处-> P
?
1.不透明的结果类型可以与PAT一起使用
当前协议的主要限制是PAT(具有关联类型的协议)不能用作实际类型。尽管此限制在将来的语言版本中可能会取消,但由于不透明的结果类型实际上只是通用的占位符,因此它们现在可以与PATs一起使用。
这意味着您可以执行以下操作:
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演变建议。