Swift泛型强制误解 [英] Swift generic coercion misunderstanding
问题描述
我使用
但是我可以在没有任何编译器错误的情况下编写下一行:
var arrays = Array< Array< BaseProtocol>>()
let arrayOfChild = Array< ChildClass>()
arrays.append(arrayOfChild)
那么,通用Swift数组和泛型Signal有什么区别呢?
Set
)和 Dictionary
)从t中获得特殊待遇他编译器,允许协变(我在这个Q& A的 中稍微详细地介绍了这一点)。 然而,任意的泛型类型是不变的 - 因此在类型系统中, Signal< ChildClass>
和<$ c即使 ChildClass
可转换为<$ c> $ c> Signal< BaseProtocol> 完全不相关的类型$ c> BaseProtocol (另请参阅本Q& A )。
这样做的一个原因是它会完全破坏泛型引用类型,它们定义了关于 T
。
例如,如果您已将 Signal
执行为:
class Signal< T> {
var t:T
init(t:T){
self.t = t
}
}
如果您可以说:
let signalInt = Signal(t:5)
let signalAny:Signal< Any> = signalInt
您可以这样说:
signalAny.t =wassup
即完全错误,因为您不能将字符串
分配给 Int
属性。
Array 这种东西对于安全的原因在于它是一种值类型 - 因此当您执行:
let intArray = [2,3,4]
var anyArray:[Any] = intArray
anyArray.append(wassup)
没有问题,因为 anyArray
是 intArray
的 copy - 因此 append( _:)
不是问题。
然而,这不能应用于任意的通用值类型,因为值类型可以包含任意数量的通用引用类型,这导致我们退回到允许非法操作定义反变化事物的通用引用类型的危险之路。
正如Rob在他的回答中所说,引用类型的解决方案,如果您需要维护对相同基础的引用例如,是使用类型橡皮擦。
如果我们考虑这个例子:
protocol BaseProtocol {}
class ChildClass:BaseProtocol {}
类AnotherChild:BaseProtocol {}
class Signal< T> {
var t:T
init(t:T){
self.t = t
}
}
让childSignal = Signal(t:ChildClass())
让anotherSignal = Signal(t:AnotherChild())
包装任何 Signal< T>
实例的类型橡皮擦,其中 T
符合 BaseProtocol
可能如下所示:
struct AnyBaseProtocolSignal {
private let _t:() - > BaseProtocol
var t:BaseProtocol {return _t()}
init< T:BaseProtocol>(_ base:Signal< T>){
_t = { base.t}
}
}
// ...
let signals = [AnyBaseProtocolSignal(childSignal),AnyBaseProtocolSignal(anotherSignal)]
现在我们可以通过异信号来讨论Signal
其中 T
是符合 BaseProtocol
的某种类型。
然而,这个包装的一个问题是,我们仅限于使用 BaseProtocol
进行交谈。如果我们有 AnotherProtocol
并且想为 Signal
其中 T
符合
AnotherProtocol
?
解决方法是传递转换
函数到type-eraser,允许我们执行一个任意的upcast。
struct AnySignal< ; T> {
private let _t:() - > T(b):信号< U> ;,变换:@escaping(U) - > T){
_t = {transform(base.t)}
}
}
现在我们可以讨论 Signal的异构类型Signal
其中 T
是某种类型的可转换到一些 U
,这是在创建类型橡皮擦时指定的。
让信号:[AnySignal< BaseProtocol>] = [
AnySignal(childSignal,transform:{$ 0}),
AnySignal(anotherSignal,transform:{$ 0})
//或AnySignal(childSignal,transform:{$ 0 as BaseProtocol})
//是显式的。
]
然而,传递相同的 在Swift 3.1中(可用于Xcode 8.3测试版),您可以通过定义来自调用者的负担来减轻这种负担你自己的初始化程序专门用于 (并且对于要转换的任何其他协议类型重复) p> 现在您可以说: (你实际上可以在这里删除数组的显式类型注释,编译器会推断它是 值类型或引用类型的解决方案,您希望专门创建新实例,执行转换从 transform
BaseProtocol
的扩展:
扩展AnySignal where T == BaseProtocol {
init< U:BaseProtocol>(_ base:Signal< U>){
self.init(base,transform:{$ 0})
}
$ / code>
让信号:[AnySignal< BaseProtocol> ;] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]
[AnySignal< BaseProtocol>]
- 但是如果你打算允许更多的便利初始化者,我会保持它的明确性)
Signal< T>
(其中 T
符合 BaseProtocol $ c $在Swift 3.1中,你可以通过定义一个(方便的)方法来完成这个任务。 )在
Signal 类型的扩展中的初始化,其中
T == BaseProtocol
:
扩展名Signal其中T == BaseProtocol {
convenience init< T:BaseProtocol>(other:Signal< T>){
self.init t:other.t)
}
}
// ...
让信号:[Signal< BaseProtocol>] = [
Signal(other:childSignal),
Signal(other:anotherSignal)
]
Swift 3.1,可以通过实例方法实现:
扩展Signal其中T:BaseProtocol {
func asBaseProtocol() - >信号u BaseProtocol> {
return Signal< BaseProtocol>(t:t)
}
}
// ...
让信号:[ Signal< BaseProtocol>] = [
childSignal.asBaseProtocol(),
anotherSignal.asBaseProtocol()
]
这两种情况下的过程对于 struct
都是相似的。
I'm using Signals library.
Let's say I defined BaseProtocol protocol and ChildClass
which conforms BaseProtocol
.
protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
Now I want to store signals like:
var signals: Array<Signal<BaseProtocol>> = []
let signalOfChild = Signal<ChildClass>()
signals.append(signalOfChild)
I get error:
But I can write next lines without any compiler error:
var arrays = Array<Array<BaseProtocol>>()
let arrayOfChild = Array<ChildClass>()
arrays.append(arrayOfChild)
So, what the difference between generic Swift Array and generic Signal?
The difference is that Array
(and Set
and Dictionary
) get special treatment from the compiler, allowing for covariance (I go into this in slightly more detail in this Q&A).
However, arbitrary generic types are invariant – therefore in the eyes of the type-system, Signal<ChildClass>
and Signal<BaseProtocol>
are seen as completely unrelated types, even though ChildClass
is convertible to BaseProtocol
(see also this Q&A).
One reason for this is it would completely break generic reference types that define contravariant things (such as method parameters and property setters) with respect to T
.
For example, if you had implemented Signal
as:
class Signal<T> {
var t: T
init(t: T) {
self.t = t
}
}
If you were able to say:
let signalInt = Signal(t: 5)
let signalAny: Signal<Any> = signalInt
you could then say:
signalAny.t = "wassup"
which is completely wrong, as you cannot assign a String
to an Int
property.
The reason why this kind of thing is safe for Array
is that it's a value type – thus when you do:
let intArray = [2, 3, 4]
var anyArray : [Any] = intArray
anyArray.append("wassup")
there are no problems, as anyArray
is a copy of intArray
– thus the contravariance of append(_:)
is not a problem.
However, this cannot be applied to arbitrary generic value types, as value types can contain any number of generic reference types, which leads us back down the dangerous road of allowing an illegal operation for generic reference types that define contravariant things.
As Rob says in his answer, the solution for reference types, if you need to maintain a reference to the same underlying instance, is to use a type-eraser.
If we consider the example:
protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
class AnotherChild : BaseProtocol {}
class Signal<T> {
var t: T
init(t: T) {
self.t = t
}
}
let childSignal = Signal(t: ChildClass())
let anotherSignal = Signal(t: AnotherChild())
A type-eraser that wraps any Signal<T>
instance where T
conforms to BaseProtocol
could look like this:
struct AnyBaseProtocolSignal {
private let _t: () -> BaseProtocol
var t: BaseProtocol { return _t() }
init<T : BaseProtocol>(_ base: Signal<T>) {
_t = { base.t }
}
}
// ...
let signals = [AnyBaseProtocolSignal(childSignal), AnyBaseProtocolSignal(anotherSignal)]
This now lets us talk in terms of heterogenous types of Signal
where the T
is some type that conforms to BaseProtocol
.
However one problem with this wrapper is that we're restricted to talking in terms of BaseProtocol
. What if we had AnotherProtocol
and wanted a type-eraser for Signal
instances where T
conforms to AnotherProtocol
?
One solution to this is to pass a transform
function to the type-eraser, allowing us to perform an arbitrary upcast.
struct AnySignal<T> {
private let _t: () -> T
var t: T { return _t() }
init<U>(_ base: Signal<U>, transform: @escaping (U) -> T) {
_t = { transform(base.t) }
}
}
Now we can talk in terms of heterogenous types of Signal
where T
is some type that's convertible to some U
, which is specified at the creation of the type-eraser.
let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal, transform: { $0 }),
AnySignal(anotherSignal, transform: { $0 })
// or AnySignal(childSignal, transform: { $0 as BaseProtocol })
// to be explicit.
]
However, the passing of the same transform
function to each initialiser is a little unwieldy.
In Swift 3.1 (available with Xcode 8.3 beta), you can lift this burden from the caller by defining your own initialiser specifically for BaseProtocol
in an extension:
extension AnySignal where T == BaseProtocol {
init<U : BaseProtocol>(_ base: Signal<U>) {
self.init(base, transform: { $0 })
}
}
(and repeat for any other protocol types you want to convert to)
Now you can just say:
let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]
(You can actually remove the explicit type annotation for the array here, and the compiler will infer it to be [AnySignal<BaseProtocol>]
– but if you're going to allow for more convenience initialisers, I would keep it explicit)
The solution for value types, or reference types where you want to specifically create a new instance, to is perform a conversion from Signal<T>
(where T
conforms to BaseProtocol
) to Signal<BaseProtocol>
.
In Swift 3.1, you can do this by defining a (convenience) initialiser in an extension for Signal
types where T == BaseProtocol
:
extension Signal where T == BaseProtocol {
convenience init<T : BaseProtocol>(other: Signal<T>) {
self.init(t: other.t)
}
}
// ...
let signals: [Signal<BaseProtocol>] = [
Signal(other: childSignal),
Signal(other: anotherSignal)
]
Pre Swift 3.1, this can be achieved with an instance method:
extension Signal where T : BaseProtocol {
func asBaseProtocol() -> Signal<BaseProtocol> {
return Signal<BaseProtocol>(t: t)
}
}
// ...
let signals: [Signal<BaseProtocol>] = [
childSignal.asBaseProtocol(),
anotherSignal.asBaseProtocol()
]
The procedure in both cases would be similar for a struct
.
这篇关于Swift泛型强制误解的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!