如何解决 Swift 不支持第一类元类型的问题?

Mar*_*sel 5 protocols existential-type swift

所以我正在实施以下内容:

  • 一个简单的LanguageType协议,符合Hashable
  • 一个协议,它应该允许您使用 a作为键从字典中Translateable获取(和设置)a[String]LanguageType

// MARK: - LanguageType

protocol LanguageType: Hashable {
    var description: String { get }
}

extension LanguageType {
    var description: String { return "\(Self.self)" }
    var hashValue: Int { return "\(Self.self)".hashValue }
}

func ==<T: LanguageType, U: LanguageType>(left: T, right: U) -> Bool {
    return left.description == right.description
}

// MARK: - Translateable

protocol Translateable {
    var translations: [LanguageType: [String]] { get set }
}
Run Code Online (Sandbox Code Playgroud)

LanguageType与往常一样,Swift 在协议的使用方式上存在问题:

错误

据我所知,这与 Swift 不支持Existentials有关,这导致协议实际上并不是第一类类型。

在泛型的上下文中,这个问题通常可以通过类型擦除的包装器来解决。
就我而言,没有泛型或关联类型。

我想要实现的是必须translations.Key任何 LanguageType符合的通用类型,而不仅仅是一种通用类型LanguageType
例如,这是行不通的:

protocol Translateable {
    typealias Language: LanguageType

    var translations: [Language: [String]] { get set }
}
Run Code Online (Sandbox Code Playgroud)

出于某种原因,我就是想不出一种方法来实现这一目标。我发现听起来我需要某种类型擦除的包装,正如我想要的那样

translations.Key成为任何 LanguageType

我想我需要删除确切的类型,它应该LanguageType符合Translateable. 我可以做什么来解决这个问题?


更新1: 正如刚刚在这个问题中确定的那样,LanguageType实际上具有关联的类型要求(遵守它的一致性Equatable)。因此,我将尝试创建一个围绕LanguageType.

更新 2: 所以我意识到,创建类型擦除的包装器实际上LanguageType并不能解决问题。我创建了AnyLanguage

struct AnyLanguage<T>: LanguageType {
    private let _description: String
    var description: String { return _description }
    init<U: LanguageType>(_ language: U) { _description = language.description }
}

func ==<T, U>(left: AnyLanguage<T>, right: AnyLanguage<U>) -> Bool {
    return left.description == right.description
}
Run Code Online (Sandbox Code Playgroud)

如果我现在用它来代替LanguageType它不会有多大作用,因为Translateable仍然需要关联的类型:

protocol Translateable {
    typealias T
    var translations: [AnyLanguage<T>: [String]] { get set }
}
Run Code Online (Sandbox Code Playgroud)

解决方案:

我从以下位置删除了泛型AnyLanguage

struct AnyLanguage: LanguageType {
    private(set) var description: String
    init<T: LanguageType>(_ language: T) { description = language.description }
}

func ==(left: AnyLanguage, right: AnyLanguage) -> Bool {
    return left.description == right.description
}

protocol Translateable {
    var translations: [AnyLanguage: [String]] { get set }
}
Run Code Online (Sandbox Code Playgroud)

不知道为什么我T在 Update 2 中引入,因为它没有做任何事情。但这现在似乎有效。

Mar*_*sel 1

解决方案似乎是一个类型擦除的包装器类型擦除通过创建包装类型来修复无法将具有关联类型 (PAT) 的协议作为一等公民使用的问题,该包装类型仅公开其包装的协议定义的属性。

\n\n

在这种情况下,LanguageTypePAT,因为它采用了Equatable(它符合,因为它采用了Hashable)

\n\n
protocol LanguageType: Hashable { /*...*/ }\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此它不能用作协议中的第一类类型Translatable

\n\n
protocol Translatable {\n    var translations: [LanguageType: [String]] { get set } // error\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

为 定义关联类型并Translatable不能解决问题,因为这会将 限制LanguageType为一种特定类型:

\n\n
protocol Translatable {\n    typealias Language: LanguageType\n\n    var translations: [Language: [String]] { get set } // works\n}    \n\nstruct MyTranslatable<T: LanguageType>: Translatable {\n    var translations: [T: [String]] // `T` can only be one specific type\n\n    //...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如前所述,解决方案是类型擦除包装器AnyLanguage(Apple 对类型擦除包装器使用相同的命名约定。例如AnySequence):

\n\n
// `AnyLanguage` exposes all of the properties defined by `LanguageType`\n// in this case, there\'s only the `description` property\nstruct AnyLanguage: LanguageType {\n    private(set) var description: String\n\n    // `AnyLanguage` can be initialized with any type conforming to `LanguageType`\n    init<T: LanguageType>(_ language: T) { description = language.description }\n}\n\n// needed for `AnyLanguage` to conform to `LanguageType`, as the protocol inherits for `Hashable`, which inherits from `Equatable`\nfunc ==(left: AnyLanguage, right: AnyLanguage) -> Bool {\n    return left.description == right.description\n}\n\n// the use of `AnyLanguage` allows any `LanguageType` to be used as the dictionary\'s `Key`, as long as it is wrapped as `AnyLanguage`\nprotocol Translateable {\n    var translations: [AnyLanguage: [String]] { get set }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

此实现现在允许以下操作:

\n\n
struct SomethingTranslatable: Translatable\xc2\xa0{\n    var translations: [AnyLanguage: [String]] = [:]\n}\n\nfunc ==(left: SomethingTranslatable, right: SomethingTranslatable) -> Bool { /*return some `Bool`*/ }\n\nstruct English: LanguageType { }\nstruct German: LanguageType { }\n\nvar something = SomethingTranslatable()\nsomething.translations[AnyLanguage(English())] = ["Hello", "World"]\nlet germanWords = something.translations[AnyLanguage(German())]\n
Run Code Online (Sandbox Code Playgroud)\n\n

符合 的不同类型LanguageType现在可以用作Key. 唯一的语法差异是必要的初始化AnyLanguage

\n\n
AnyLanguage(English())\n
Run Code Online (Sandbox Code Playgroud)\n