在 Swift 中限制一个泛型与另一个泛型
Constraining one generic with another in Swift
我 运行 遇到了一些协议问题:
protocol Baz {
func bar<T>(input:T)
}
函数 bar
是通用的,因为我不希望协议本身具有 Self
(它需要在集合中可用)。我有一个协议的实现定义为:
class Foo<S>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T>(input:T) {
value = input
}
}
这会出错,因为编译器不知道 S
和 T
是同一类型。理想情况下,我应该能够写出如下内容:
func bar<T where T==S>(input:T) {
value = input
}
或
func bar<T:S>(input:T) {
value = input
}
第一种形式给出了 "Same-type requirement makes generic parameter 'S' and 'T' equivalent" 错误(这正是我想要做的,所以不确定为什么会出错)。第二种形式给了我一个"Inheritance from non-protocol, non-class type 'S'"。
关于如何让它工作或 Swift 中更好的设计模式的任何想法?
更新:正如@luk2302 所指出的,我忘记让Foo
遵守Baz
协议
EDITED( 的解决方法“...一个错误,因为编译器不知道 S 和 T 是同一类型。”)
首先:除了 Robs 出色的答案外,这只是一个单独的注释(也许是对我之前的答案的赎回尝试,结果是我自己追逐自己的尾巴来计算大量冗余代码) .
以下解决方法可能会让您的实现 protocol Foo ...
/ class Bas : Foo ...
模仿您最初要求的行为,因为 class 方法 bar(...)
会知道是否泛型 S
和 T
实际上是同一类型,而 Foo 在 S
是 not 的情况下仍然符合协议输入 T
.
protocol Baz {
func bar<T>(input:T)
}
class Foo<S>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T>(input:T) {
if input is S {
value = input as! S
}
else {
print("Incompatible types. Throw...")
}
}
}
// ok
var a = Foo(value: 1.0) // Foo<Double>
print(a.value) // 1.0
a.bar(2.0)
print(a.value) // 2.0
let myInt = 1
a.bar(myInt) // Incompatible types. Throw...
print(a.value) // 2.0
// perhaps not a loophole we indended
let myAny : Any = 3.0
a.bar(myAny)
print(a.value) // 3.0
这里的 Any
和 AnyObject
漏洞可以通过创建一个虚拟类型约束来弥补,你可以将所有类型(你希望使用泛型)扩展到,但不扩展 Any
和 AnyObject
.
protocol NotAnyType {}
extension Int : NotAnyType {}
extension Double : NotAnyType {}
extension Optional : NotAnyType {}
// ...
protocol Baz {
func bar<T: NotAnyType>(input:T)
}
class Foo<S: NotAnyType>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T: NotAnyType>(input:T) {
if input is S {
value = input as! S
}
else {
print("Incompatible types. Throw...")
}
}
}
// ok
var a = Foo(value: 1.0) // Foo<Double>
// ...
// no longer a loophole
let myAny : Any = 3.0
a.bar(myAny) // compile time error
let myAnyObject : AnyObject = 3.0
a.bar(myAnyObject) // compile time error
然而,这从通用中完全排除了 Any
和 AnyObject
(不仅是 "loophole casting"),这也许是不是追捧的行为。
@luk2302 在评论中暗示了很多,但只是为了让未来的搜索者明确。
protocol Baz {
func bar<T>(input:T)
}
这个协议几乎可以肯定是没有用的。它实际上与以下协议相同(也几乎完全无用):
protocol Baz {
func bar(input:Any)
}
您的意思很可能(并暗示您的意思):
protocol Baz {
typealias T
func bar(input: T)
}
如您所见,这使协议成为 PAT(具有关联类型的协议),这意味着您不能将其直接 放入集合中。正如您所注意到的,如果您真的需要它们的集合,通常的解决方案是 type eraser。如果 Swift 会自动为您编写橡皮擦,那就太好了,将来可能会这样做,并且会消除问题。也就是说,虽然有点乏味,但写橡皮擦是非常简单的。
现在虽然不能将 PAT 直接放入集合中,但可以将一般约束的 PAT 放入集合中。所以只要把集合包装成约束T的类型,还是没问题的。
如果这些变得复杂,约束代码就会变得乏味且非常重复。然而,这可以通过多种技术来改进。
可以使用具有静态方法的通用结构来避免重复提供对自由函数的约束。
协议可以转换为通用结构(这将类型擦除器形式化为主要类型,而不是 "as needed")。
协议在很多情况下可以用函数代替。例如,鉴于此:
protocol Bar {
typealias T
func bar(input: T)
}
struct Foo : Bar {
func bar(input: Int) {}
}
你不能这样做:
let bars: [Bar] = [Foo()] // error: protocol 'Bar' can only be used as a generic constraint because it has Self or associated type requirements
但是你可以很容易地做到这一点,这同样好:
let bars = [(Int) -> Void] = [Foo().bar]
这对于单一方法协议特别强大。
协议、泛型和函数的混合比试图将所有内容都强加到协议框中要强大得多,至少在协议添加一些缺失的功能以实现其承诺之前是这样。
(针对具体问题给出具体建议会更简单,没有一个答案可以解决所有问题。)
我 运行 遇到了一些协议问题:
protocol Baz {
func bar<T>(input:T)
}
函数 bar
是通用的,因为我不希望协议本身具有 Self
(它需要在集合中可用)。我有一个协议的实现定义为:
class Foo<S>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T>(input:T) {
value = input
}
}
这会出错,因为编译器不知道 S
和 T
是同一类型。理想情况下,我应该能够写出如下内容:
func bar<T where T==S>(input:T) {
value = input
}
或
func bar<T:S>(input:T) {
value = input
}
第一种形式给出了 "Same-type requirement makes generic parameter 'S' and 'T' equivalent" 错误(这正是我想要做的,所以不确定为什么会出错)。第二种形式给了我一个"Inheritance from non-protocol, non-class type 'S'"。
关于如何让它工作或 Swift 中更好的设计模式的任何想法?
更新:正如@luk2302 所指出的,我忘记让Foo
遵守Baz
协议
EDITED( 的解决方法“...一个错误,因为编译器不知道 S 和 T 是同一类型。”)
首先:除了 Robs 出色的答案外,这只是一个单独的注释(也许是对我之前的答案的赎回尝试,结果是我自己追逐自己的尾巴来计算大量冗余代码) .
以下解决方法可能会让您的实现 protocol Foo ...
/ class Bas : Foo ...
模仿您最初要求的行为,因为 class 方法 bar(...)
会知道是否泛型 S
和 T
实际上是同一类型,而 Foo 在 S
是 not 的情况下仍然符合协议输入 T
.
protocol Baz {
func bar<T>(input:T)
}
class Foo<S>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T>(input:T) {
if input is S {
value = input as! S
}
else {
print("Incompatible types. Throw...")
}
}
}
// ok
var a = Foo(value: 1.0) // Foo<Double>
print(a.value) // 1.0
a.bar(2.0)
print(a.value) // 2.0
let myInt = 1
a.bar(myInt) // Incompatible types. Throw...
print(a.value) // 2.0
// perhaps not a loophole we indended
let myAny : Any = 3.0
a.bar(myAny)
print(a.value) // 3.0
这里的 Any
和 AnyObject
漏洞可以通过创建一个虚拟类型约束来弥补,你可以将所有类型(你希望使用泛型)扩展到,但不扩展 Any
和 AnyObject
.
protocol NotAnyType {}
extension Int : NotAnyType {}
extension Double : NotAnyType {}
extension Optional : NotAnyType {}
// ...
protocol Baz {
func bar<T: NotAnyType>(input:T)
}
class Foo<S: NotAnyType>: Baz {
var value:S
init(value:S) {
self.value = value
}
func bar<T: NotAnyType>(input:T) {
if input is S {
value = input as! S
}
else {
print("Incompatible types. Throw...")
}
}
}
// ok
var a = Foo(value: 1.0) // Foo<Double>
// ...
// no longer a loophole
let myAny : Any = 3.0
a.bar(myAny) // compile time error
let myAnyObject : AnyObject = 3.0
a.bar(myAnyObject) // compile time error
然而,这从通用中完全排除了 Any
和 AnyObject
(不仅是 "loophole casting"),这也许是不是追捧的行为。
@luk2302 在评论中暗示了很多,但只是为了让未来的搜索者明确。
protocol Baz {
func bar<T>(input:T)
}
这个协议几乎可以肯定是没有用的。它实际上与以下协议相同(也几乎完全无用):
protocol Baz {
func bar(input:Any)
}
您的意思很可能(并暗示您的意思):
protocol Baz {
typealias T
func bar(input: T)
}
如您所见,这使协议成为 PAT(具有关联类型的协议),这意味着您不能将其直接 放入集合中。正如您所注意到的,如果您真的需要它们的集合,通常的解决方案是 type eraser。如果 Swift 会自动为您编写橡皮擦,那就太好了,将来可能会这样做,并且会消除问题。也就是说,虽然有点乏味,但写橡皮擦是非常简单的。
现在虽然不能将 PAT 直接放入集合中,但可以将一般约束的 PAT 放入集合中。所以只要把集合包装成约束T的类型,还是没问题的。
如果这些变得复杂,约束代码就会变得乏味且非常重复。然而,这可以通过多种技术来改进。
可以使用具有静态方法的通用结构来避免重复提供对自由函数的约束。
协议可以转换为通用结构(这将类型擦除器形式化为主要类型,而不是 "as needed")。
协议在很多情况下可以用函数代替。例如,鉴于此:
protocol Bar {
typealias T
func bar(input: T)
}
struct Foo : Bar {
func bar(input: Int) {}
}
你不能这样做:
let bars: [Bar] = [Foo()] // error: protocol 'Bar' can only be used as a generic constraint because it has Self or associated type requirements
但是你可以很容易地做到这一点,这同样好:
let bars = [(Int) -> Void] = [Foo().bar]
这对于单一方法协议特别强大。
协议、泛型和函数的混合比试图将所有内容都强加到协议框中要强大得多,至少在协议添加一些缺失的功能以实现其承诺之前是这样。
(针对具体问题给出具体建议会更简单,没有一个答案可以解决所有问题。)