无法在Swift中将协议作为关联类型用于另一个协议

14

我有一个协议,Address,它继承自另一个协议,Validator,并且Address在扩展中满足了Validator的要求。

还有另一个协议FromRepresentable,它有一个associatedTypeValueWrapper)的要求,应该是Validator

现在,如果我尝试将Address用作associatedType,那么它无法编译。它说:

推断类型“Address”(通过匹配要求“valueForDetail”)无效:不符合“Validator”。

这种用法是否非法?我们不应该能够将Address用作Validator吗,因为所有的Addresses都是Validator

下面是我正在尝试的代码片段。

enum ValidationResult {
    case Success
    case Failure(String)
}

protocol Validator {
    func validate() -> ValidationResult
}

//Address inherits Validator
protocol Address: Validator {
    var addressLine1: String {get set}
    var city: String {get set}
    var country: String {get set}
}

////Fulfill Validator protocol requirements in extension
extension Address {
    func validate() -> ValidationResult {
        if addressLine1.isEmpty {
            return .Failure("Address can not be empty")
        }
        return .Success
    }
}

protocol FormRepresentable {
    associatedtype ValueWrapper: Validator
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}


// Shipping Address conforming to Address protocol. 
// It should also implicitly conform to Validator since
// Address inherits from Validator?
struct ShippingAddress: Address {
    var addressLine1 = "CA"
    var city = "HYD"
    var country = "India"
}


// While compiling, it says:
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform
// to 'Validator'.
// But Address confroms to Validator.
enum AddressFrom: Int, FormRepresentable {
    case Address1
    case City
    case Country

    func valueForDetail(valueWrapper: Address) -> String {
        switch self {
        case .Address1:
            return valueWrapper.addressLine1
        case .City:
            return valueWrapper.city
        case .Country:
            return valueWrapper.country
        }
    }
}

更新: 提交了一个错误报告。

2个回答

9
问题在于,一旦将协议的 associatedtype 约束为特定的(非 @objc)协议,您必须使用具体类型来满足该要求。正如 David 已经提到 的那样。
这是因为 协议不能符合自己 - 因此意味着您无法使用 Address 来满足协议的关联类型要求,这个关联类型要求是一个符合 Validator 的类型,因为 Address 不是符合 Validator 的类型。
正如我在 这里的答案 中展示的那样,请考虑以下反例:
protocol Validator {
    init()
}
protocol Address : Validator {}

protocol FormRepresentable {
    associatedtype ValueWrapper: Validator
}

extension FormRepresentable {
    static func foo() {
        // if ValueWrapper were allowed to be an Address or Validator,
        // what instance should we be constructing here?
        // we cannot create an instance of a protocol.
        print(ValueWrapper.init())
    }
}

// therefore, we cannot say:
enum AddressFrom : FormRepresentable {
    typealias ValueWrapper = Address
}

最简单的解决方案是放弃在ValueWrapper关联类型上使用Validator协议约束,从而允许您在方法参数中使用抽象类型。
protocol FormRepresentable {
    associatedtype ValueWrapper
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom : Int, FormRepresentable {

    // ...

    func valueForDetail(valueWrapper: Address) -> String {
        // ...
    }
}

如果您需要相关类型的约束,并且每个 AddressFrom 实例只期望一个具体的 Address 实现作为输入 - 您可以使用泛型,以便在您的方法中使用给定的具体地址类型初始化 AddressFrom
protocol FormRepresentable {
    associatedtype ValueWrapper : Validator
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom<T : Address> : Int, FormRepresentable {

    // ...

    func valueForDetail(valueWrapper: T) -> String {
        // ...
    }
}

// replace ShippingAddress with whatever concrete type you want AddressFrom to use
let addressFrom = AddressFrom<ShippingAddress>.Address1

然而,如果您需要相关类型约束和每个AddressFrom实例必须能够处理任何类型的Address输入 - 您需要使用类型擦除来将任意Address包装在具体类型中。
protocol FormRepresentable {
    associatedtype ValueWrapper : Validator
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}

struct AnyAddress : Address {

    private var _base: Address

    var addressLine1: String {
        get {return _base.addressLine1}
        set {_base.addressLine1 = newValue}
    }
    var country: String {
        get {return _base.country}
        set {_base.country = newValue}
    }
    var city: String {
        get {return _base.city}
        set {_base.city = newValue}
    }

    init(_ base: Address) {
        _base = base
    }
}

enum AddressFrom : Int, FormRepresentable {

    // ...

    func valueForDetail(valueWrapper: AnyAddress) -> String {
        // ...
    }
}

let addressFrom = AddressFrom.Address1

let address = ShippingAddress(addressLine1: "", city: "", country: "")

addressFrom.valueForDetail(AnyAddress(address))

好的,我明白了,但是我们为什么必须在协议约束中使用concreteType作为associatedType呢?如果我们将Address用作期望Validator的函数的参数,编译器不会发出任何错误。 - Vishal Singh
我不明白为什么我们必须使用具体类型,如果associatedType有协议约束。这是否与不变行为或内存分配有关?您有任何参考资料可以提供更多信息吗? - Vishal Singh
@VishalSingh,恐怕我没有更好的理由可以解释为什么就是这样 - 我当然无法想出任何原因,说明为什么不可能,因为无限制版本运行良好。我似乎在Swift进化邮件列表或错误跟踪器上找不到关于此的任何内容。也许值得在错误报告上提交一个错误报告,看看他们对此的看法。 - Hamish
谢谢,我会这样做,并且如果我得到任何消息,我会更新这篇文章。在那之前,我会把这个当作一条规则记住 :) - Vishal Singh
非常乐意帮忙,我肯定会对回复感兴趣 :) - Hamish

0
你有几个问题:
首先,你实际上没有声明Address实现了Validator。
//Address inherits Validator
protocol Address : Validator {
    var addressLine1: String {get set}
    var city: String {get set}
    var country: String {get set}
}

而且您没有为ValueWrapper声明关联类型:

typealias ValueWrapper = ShippingAddress

你似乎真正想要的是让AddressFrom.valueForDetail接受一个ShippingAddress

func valueForDetail(valueWrapper: ShippingAddress) -> String {
    switch self {
    case .Address1:
        return valueWrapper.addressLine1
    case .City:
        return valueWrapper.city
    case .Country:
        return valueWrapper.country
    }
}

总的来说,它看起来像这样:

enum ValidationResult {
    case Success
    case Failure(String)
}

protocol Validator {
    func validate() -> ValidationResult
}

//Address inherits Validator
protocol Address : Validator {
    var addressLine1: String {get set}
    var city: String {get set}
    var country: String {get set}
}

////Fulfill Validator protocol requirements in extension
extension Address {
    func validate() -> ValidationResult {
        if addressLine1.isEmpty {
            return .Failure("Address can not be empty")
        }
        return .Success
    }
}

protocol FormRepresentable {
    associatedtype ValueWrapper: Validator
    func valueForDetail(valueWrapper: ValueWrapper) -> String
}


// Shipping Address conforming to Address protocol.
// It should also implicity conform to Validator since
// Address inherits from Validator?
struct ShippingAddress: Address {
    var addressLine1 = "CA"
    var city = "HYD"
    var country = "India"
}


// While compiling, it says:
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform
// to 'Validator'.
// But Address confroms to Validator.
enum AddressFrom: Int, FormRepresentable {
    case Address1
    case City
    case Country

    // define associated type for FormRepresentable
    typealias ValueWrapper = ShippingAddress
    func valueForDetail(valueWrapper: ShippingAddress) -> String {
        switch self {
        case .Address1:
            return valueWrapper.addressLine1
        case .City:
            return valueWrapper.city
        case .Country:
            return valueWrapper.country
        }
    }
}

抱歉,我在尝试解决问题时就发布了这个问题。我编辑了代码片段。Address实际上是从Validator继承而来的。关于typealias,我认为它应该能够从valueForDetail(_:Address)函数中推断出ValueWrapper。我不希望它成为concretetype。 - Vishal Singh
请注意,您实际上不必在实现中显式定义协议的“关联类型”(通过“typealias”),Swift可以从方法参数类型推断出它。 - Hamish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接