Swift 泛型强制的误解 [英] Swift generic coercion misunderstanding

查看:20
本文介绍了Swift 泛型强制的误解的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用

但是我可以在没有任何编译器错误的情况下编写下一行:

var arrays = Array>()让 arrayOfChild = Array()arrays.append(arrayOfChild)

那么,泛型 Swift Array 和泛型 Signal 之间有什么区别?

解决方案

区别在于Array(和SetDictionary)得到来自编译器的特殊处理,允许协方差(我在在本问答中更详细地介绍了这一点).

然而,任意泛型类型是不变,意思是X 是与 X 完全无关的类型,如果 T != U 之间的任何其他类型关系TU(例如子类型)是无关的.应用于您的情况,SignalSignal 是不相关的类型,即使 ChildClassBaseProtocol 的子类型(另请参阅此问答).

这样做的一个原因是它会完全破坏定义与 T 相关的逆变事物(例如函数参数和属性设置器)的通用引用类型.

例如,如果您将 Signal 实现为:

类信号{变数:T初始化(T:T){自我.t = t}}

如果你可以说:

let signalInt = Signal(t: 5)let signalAny: Signal= 信号输入

然后你可以说:

signalAny.t = "wassup"//将 String 分配给 Signal 的 `t` 属性.

这是完全错误的,因为您不能将 String 分配给 Int 属性.

这种事情对 Array 来说是安全的原因是它是一种值类型——因此当你这样做时:

让 intArray = [2, 3, 4]var anyArray : [Any] = intArrayanyArray.append("wassup")

没有问题,因为anyArrayintArraycopy——因此append(_:) 不是问题.

然而,这不能应用于任意泛型值类型,因为值类型可以包含任意数量的泛型引用类型,这使我们回到了允许对定义逆变事物的泛型引用类型进行非法操作的危险道路.

<小时>

正如 Rob 在他的回答中所说,引用类型的解决方案,如果您需要维护对相同的引用底层实例,是使用类型橡皮擦.

如果我们考虑这个例子:

protocol BaseProtocol {}class ChildClass:BaseProtocol {}类另一个孩子:BaseProtocol {}类信号{变数:T初始化(T:T){自我.t = t}}让 childSignal = Signal(t: ChildClass())让 anotherSignal = Signal(t: AnotherChild())

包装任何 Signal 实例的类型橡皮擦,其中 T 符合 BaseProtocol 可能如下所示:

struct AnyBaseProtocolSignal {私人让 _t: () ->基本协议var t: BaseProtocol { return _t() }init(_base:信号){_t = { base.t }}}//...让信号 = [AnyBaseProtocolSignal(childSignal), AnyBaseProtocolSignal(anotherSignal)]

现在让我们讨论 Signal 的异构类型,其中 T 是某种符合 BaseProtocol 的类型.

然而,这个包装器的一个问题是我们仅限于谈论 BaseProtocol.如果我们有 AnotherProtocol 并且想要一个类型橡皮擦用于 T 符合 AnotherProtocolSignal 实例怎么办?>

对此的一种解决方案是将 transform 函数传递给类型擦除器,从而允许我们执行任意向上转换.

struct AnySignal{私人让 _t: () ->吨var t: T { 返回 _t() }init<U>(_ base: Signal<U>, transform: @escaping (U) -> T) {_t = { 变换(base.t)}}}

现在我们可以讨论 Signal 的异构类型,其中 T 是一些可以转换为一些 U 的类型,它在类型橡皮擦的创建.

让信号:[AnySignal] = [AnySignal(childSignal, 变换: { $0 }),AnySignal(anotherSignal, 转换: { $0 })//或 AnySignal(childSignal, transform: { $0 as BaseProtocol })//要明确.]

然而,将相同的 transform 函数传递给每个初始化器有点笨拙.

在 Swift 3.1(适用于 Xcode 8.3 beta)中,您可以通过在扩展中专门为 BaseProtocol 定义自己的初始化程序来减轻调用者的负担:

extension AnySignal where T == BaseProtocol {init(_base:信号){self.init(base, 转换: { $0 })}}

(并重复您要转换为的任何其他协议类型)

现在你可以说:

让信号:[AnySignal] = [AnySignal(childSignal),AnySignal(anotherSignal)]

(您实际上可以在此处删除数组的显式类型注释,编译器会将其推断为 [AnySignal<BaseProtocol>] – 但如果您要允许为了更方便的初始化程序,我会保持明确)

<小时>

对于值类型或引用类型的解决方案,您希望专门创建一个新实例,以从 Signal<执行转换/code>(其中 T 符合 BaseProtocol)到 Signal.

在 Swift 3.1 中,您可以通过在 Signal 类型的扩展中定义(方便的)初始化器来实现,其中 T == BaseProtocol:

extension Signal where T == BaseProtocol {方便初始化(其他:信号){self.init(t: other.t)}}//...让信号:[信号<BaseProtocol>] = [信号(其他:childSignal),信号(其他:anotherSignal)]

在 Swift 3.1 之前,这可以通过实例方法来实现:

extension Signal where T : BaseProtocol {func asBaseProtocol() ->Signal<BaseProtocol>{返回信号(t:t)}}//...让信号:[信号<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, meaning that X<T> is a completely unrelated type to X<U> if T != U – any other typing relation between T and U (such as subtyping) is irrelevant. Applied to your case, Signal<ChildClass> and Signal<BaseProtocol> are unrelated types, even though ChildClass is a subtype of BaseProtocol (see also this Q&A).

One reason for this is it would completely break generic reference types that define contravariant things (such as function 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" // assigning a String to a Signal<Int>'s `t` property.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆