【问题标题】:Why can my simple protocol only be used as a generic constraint?为什么我的简单协议只能用作通用约束?
【发布时间】:2015-08-06 14:15:18
【问题描述】:

我正在尝试做一些用于依赖注入的协议组合,但我遇到了一个问题,我怀疑可能没有我想要的解决方案,但我看不到逻辑原因:

protocol DM1 {
    func sayHi() -> Void
}

protocol DM2 {
    func sayHello() -> Void
}

protocol VM1 {
    typealias T: DM1
    var dm: T { get }
}

protocol VM2 {
    typealias T: DM2
    var dm: T { get }
}

protocol RT: VM1, VM2 {

}

class James {
    let rt: RT

    init(rt: RT) {
        self.rt = rt
    }
}

以上代码在rt 实例变量和实例化参数上导致错误“Protocol 'RT' can only be used as a generic constraint because it has Self or associated type requirementsJames。我真的不明白为什么我不能在我的 James 类中使用这个通用要求。

我最初做了如下的事情:

protocol DM1 {
    func sayHi() -> Void
}

protocol DM2 {
    func sayHello() -> Void
}

protocol VM1 {
    var dm: DM1 { get }
}

protocol VM2 {
    var dm: DM2 { get }
}

protocol RT: VM1, VM2 {

}

struct Fred: DM1, DM2 {
    func sayHi() {
        println("hi")
    }
    func sayHello() {
        println("hello")
    }
}

struct Bob: RT {
    let dm: Fred
}

class James {
    let rt: RT

    init(rt: RT) {
        self.rt = rt
    }
}

但这失败了,因为“类型'Bob'不符合协议'VM1'”(和VM2)我可以理解,因为我的协议要求变量是特定的协议类型,而不是符合该协议的某些实例类型。因此,上述版本旨在解决这个问题。

是否有人对我想做的事情有解决方案(能够通过将符合dm 属性的具体结构作为DM1DM2 来制作符合RT 的具体结构)?

【问题讨论】:

    标签: swift generics swift-protocols


    【解决方案1】:

    protocol RT 继承自 protocol VM1protocol VM2,两者都具有 typealias 要求。

    具有typealias 要求的协议只能用作类型约束,不能用作类型。

    即使是这样的简单协议...

    protocol MyProtocol {
        typealias Empty
    }
    

    ...(完全没用)只能用作类型约束。

    this link 上的一些信息可能会有所帮助。

    编辑:

    我会尽力解释为什么具有typealias 要求的协议只能用作类型限制。

    将协议视为合同。该协议承诺提供一个名为 promise 类型为 Int 的可变值:

    protocol PromiseIntType {
        var promise: Int { get set }
    }
    

    PromiseIntType 制定的契约对于将其用作类型所需了解的所有内容都是明确的关于它所做的承诺。 所以如果你有一个像这……

    class A {
        var contract: PromiseIntType
    }
    

    ...你知道如果你写这个...

    let intValue = A().contract.promise
    

    ...intValue 将是 Int。如果你想设置类型的promise 属性的值,你知道你需要为新值提供一个Int

    let a = A()
    a.contract.promise = 100
    

    合同的所有条款都是预先知道的,并且您预先知道正在做出什么样的承诺以及您正在使用什么类型。

    PromiseIntType 可以像定义为实际类型一样使用,如下所示:

    struct PromiseInt {
        var promise: Int
    }
    

    现在添加一个typealias 要求:

    protocol PromiseSomeType {
        typealias Surprise
        var promise: Surprise { get set }
    }
    

    PromiseSomeType 做了什么承诺?它说它将提供一个名为promise 的可变值,但它没有告诉您该值将是什么类型。它告诉你的只是它提供的任何东西都是Surprise。并非合同的所有条款都是事先知道的。部分内容稍后填写。

    但这使得使用PromiseSomeType 作为类型是不可能的。看看这个类,问问自己你可以用contract 属性做什么:

    class B {
        var contract: PromiseSomeType
    }
    

    例如,您将如何设置它?

    let b = B()
    b.contract.promise = <What type goes here?>
    

    如果您尝试访问promise 属性,您会得到什么类型的值?

    let someValue = b.contract.promise // What type is someValue?
    

    [附加编辑:

    你会如何使用someValue?如果是Int,那么你可以这样做:

    let newValue = someValue + 12
    

    但是您无法在编译时知道someValue 是否为Int。 Swift 坚持在编译时知道每个常量、变量和对象的类型,以便它可以检查您对该类型执行的操作是否合法。如果将这些决定推迟到运行时,非法操作会使整个程序崩溃,我们将失去类型安全提供的好处。

    /附加编辑]

    您在创建履行合同的实际类型时填写PromiseSomeType合同的详细信息:

    struct PromiseInt: PromiseSomeType {
        var promise: Int
    }
    

    PromiseInt 表示它将履行PromiseSomeType 合同,并填写promise 属性将是什么类型的缺失详细信息。

    使用PromiseSomeType 作为类型限制似乎只是将歧义推到了底线:

    class C<T: PromiseSomeType> {
        var contract: T
    }
    

    在这种情况下,在创建泛型类型的实例并指定您正在使用的实际类型时会填写合同的详细信息:

    let c = C<PromiseInt>(contract: PromiseInt(promise: 0))
    c.contract.promise = 100   // You know that promise is of type Int
    

    无论哪种方式,在您实际使用对象之前,都必须了解其类型的所有细节。

    我想关键是 Swift 是一种类型安全的语言。您不能创建模棱两可的类型。使用typealias 的协议是不明确的,因此不能用作类型,而只能用作类型约束。

    【讨论】:

    • 感谢您的回复,该链接确实很有用。然而,虽然我知道我不能将我的协议用作类型,因为它有一个typealias,但我仍然不明白为什么会这样——这个限制有什么意义?你有什么方法可以做我想做的事(大概不使用typealias)?
    • 查看我编辑的答案。我希望它有所帮助 - 区别是抽象的,但这一切都归结为类型安全。
    • 感谢您的深入解释。我可以看到有一个可变变量可能是一个问题,它是一个具有typealias 要求的协议,因为当声明建议我们可以设置它时,具有该属性的具体类将期望只设置一种类型到任何符合该协议的实例。但是对于不可变的属性,我认为这不是问题,因为我们只能取回使用的任何具体实例。我只是假设允许它用于不可变的属性太难了。
    • 问题是,即使是不可变的值,Swift 坚持在编译时知道类型,而不是在运行时。你说你会得到“无论使用什么具体实例”。但这要到运行时才能知道。在编译时,您不会知道哪些操作是合法的。例如,如果 Surprise 是 Int,则可以写 promise + 12,但如果是 [String],则不能。如果这是在运行时确定的,那么如果您执行了该类型不支持的操作,它将崩溃。类型安全的全部意义在于消除这些类型的错误并使代码安全。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-15
    • 2020-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-16
    相关资源
    最近更新 更多