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

How to work around Swift not supporting first class meta types?

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


// 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 }
}

和往常一样,Swift对LanguageType协议的使用方式有问题:

根据我的阅读,这与 Swift 不支持 Existentials 有关,这导致协议实际上不是第一种 class 类型。

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

我想实现的是让translations.Key成为anyLanguageType,而不只是一个符合LanguageType的泛型。
因此,例如,这是行不通的:

protocol Translateable {
    typealias Language: LanguageType

    var translations: [Language: [String]] { get set }
}

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

translations.Key to be any LanguageType

我想我需要删除确切的类型,它应该符合 Translateable 中的 LanguageType。 我该怎么做才能解决这个问题?


更新 1: 正如刚刚在 中确定的那样,LanguageType 实际上具有关联的类型要求(使其符合 Equatable)。因此,我将尝试围绕 LanguageType.

创建一个类型擦除的包装器

更新二: 所以我意识到,为 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
}

如果我现在用它代替 LanguageType 它不会做太多,因为 Translateable 仍然需要关联类型:

protocol Translateable {
    typealias T
    var translations: [AnyLanguage<T>: [String]] { get set }
}

解决方案:

我从 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 }
}

不确定我为什么要在更新 2 中引入 T,因为它没有任何作用。但这现在似乎有效。

您不能将协议作为 Dictionary 的密钥,请参阅 Swift Dictionary with Protocol Type as Key。 Swift 需要将字典键绑定到具体类型。

您似乎正试图在同一构造(Translateable 协议)中实现静态多态性和动态多态性,我不确定是否可以实现。

解决方法是将 Translateable 声明为通用结构:

struct Translateable<T: LanguageType> {
    var translations: [T: [String]]
}

也许您可以使用符合 LanguageTypeenum 可以模仿您正在寻找的行为。在这种情况下,您无需在 LanguageType 中明确包含对 hashable 的遵从性,因为枚举是 Hashable.

protocol LanguageType {
    var description: String { get }
    // ...
}

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

enum AnyLanguage : Int, LanguageType {
    case English = 1, German, Swedish
    // implement non-default description
    var description : String {
        return "Language: " + String(self)
    }
}

protocol Translatable {
    var myDict : [AnyLanguage:[String]] { get set }//= [:]
}

class MyFooWordList : Translatable {
    private var myBackendDict : [AnyLanguage:[String]] = [:]
    var myDict : [AnyLanguage:[String]] {
        get {
            return myBackendDict
        }
        set {
            for (k, v) in newValue {
                myBackendDict[k] = v
            }
        }
    }
}

示例:

/* Example */
var myFooWordList = MyFooWordList()
myFooWordList.myDict = [.English: ["Hello", "World"]]
myFooWordList.myDict = [.German: ["Hallo", "Welt"]]

print("Words for '" + AnyLanguage.English.description + "': \(myFooWordList.myDict[.English] ?? ["<Empty>"])")
/* Words for 'Language: English': ["Hello", "World"] */

print("Words for '" + AnyLanguage.German.description + "': \(myFooWordList.myDict[.German] ?? ["<Empty>"])")
/* Words for 'Language: German': ["Hallo", "Welt"] */

print("Words for '" + AnyLanguage.Swedish.description + "': \(myFooWordList.myDict[.Swedish] ?? ["<Empty>"])")
/* Words for 'Language: Swedish': ["<Empty>"] */

另一种解决方法是使用类似 enum 的 class,您可以在其中 "dynamically add members" 到这个虚构的枚举

class LanguageType {

    class AnyLanguage: Hashable {
        let id: Int
        let description: String

        private init(id: Int, description: String) {
            self.id = id
            self.description = description
        }

        var hashValue: Int { return id } 
    }

    class var ENGLISH: AnyLanguage {
        class English: AnyLanguage {
        }
        return English(id: 1, description: "English")
    }

    class var GERMAN: AnyLanguage {
        class German: AnyLanguage {
        }
        return German(id: 2, description: "German")
    }

    class func CUSTOM(id: Int, _ description: String) -> AnyLanguage {
        return AnyLanguage(id: id, description: description)
    }
}

func == (lhs: LanguageType.AnyLanguage, rhs: LanguageType.AnyLanguage) -> Bool {
    return lhs.id == rhs.id
}

protocol Translatable {
    var myDict : [LanguageType.AnyLanguage:[String]] { get set }//= [:]
}

class MyFooWordList : Translatable {
    private var myBackendDict : [LanguageType.AnyLanguage:[String]] = [:]
    var myDict : [LanguageType.AnyLanguage:[String]] {
        get {
            return myBackendDict
        }
        set {
            for (k, v) in newValue {
                myBackendDict[k] = v
            }
        }
    }
}

用法示例

/* Example */
var myFooWordList = MyFooWordList()
myFooWordList.myDict = [LanguageType.ENGLISH: ["Hello", "World"]]
myFooWordList.myDict = [LanguageType.GERMAN: ["Hallo", "Welt"]]
myFooWordList.myDict = [LanguageType.CUSTOM(3, "Swedish"): ["Hej", "Varlden"]]
myFooWordList.myDict = [LanguageType.CUSTOM(4, "Finnish"): ["Hei", "Maailma"]]

print("Words for '" + LanguageType.ENGLISH.description + "': \(myFooWordList.myDict[LanguageType.ENGLISH] ?? ["<Empty>"])")
/* Words for 'English': ["Hello", "World"] */

print("Words for '" + LanguageType.GERMAN.description + "': \(myFooWordList.myDict[LanguageType.GERMAN] ?? ["<Empty>"])")
/* Words for 'Language: German': ["Hallo", "Welt"] */

print("Words for '" + LanguageType.CUSTOM(3, "Swedish").description + "': \(myFooWordList.myDict[LanguageType.CUSTOM(3, "Swedish")] ?? ["<Empty>"])")
/* Words for 'Swedish': ["Hej", "Varlden"] */

解决方案似乎是 type-erased wrapper. Type-erasure fixes the problem of not being able to use protocols with associated types (PATs) 作为 first-class 公民,通过创建一个包装器类型,它只公开它包装的协议定义的属性。

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

protocol LanguageType: Hashable { /*...*/ }

因此它不能用作 Translatable 协议中的 first-class 类型:

protocol Translatable {
    var translations: [LanguageType: [String]] { get set } // error
}

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

protocol Translatable {
    typealias Language: LanguageType

    var translations: [Language: [String]] { get set } // works
}    

struct MyTranslatable<T: LanguageType>: Translatable {
    var translations: [T: [String]] // `T` can only be one specific type

    //...
}

如前所述,解决方案是 type-erased 包装器 AnyLanguage(Apple 对其 type-erased 包装器使用相同的命名约定。例如 AnySequence):

// `AnyLanguage` exposes all of the properties defined by `LanguageType`
// in this case, there's only the `description` property
struct AnyLanguage: LanguageType {
    private(set) var description: String

    // `AnyLanguage` can be initialized with any type conforming to `LanguageType`
    init<T: LanguageType>(_ language: T) { description = language.description }
}

// needed for `AnyLanguage` to conform to `LanguageType`, as the protocol inherits for `Hashable`, which inherits from `Equatable`
func ==(left: AnyLanguage, right: AnyLanguage) -> Bool {
    return left.description == right.description
}

// the use of `AnyLanguage` allows any `LanguageType` to be used as the dictionary's `Key`, as long as it is wrapped as `AnyLanguage`
protocol Translateable {
    var translations: [AnyLanguage: [String]] { get set }
}

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

struct SomethingTranslatable: Translatable {
    var translations: [AnyLanguage: [String]] = [:]
}

func ==(left: SomethingTranslatable, right: SomethingTranslatable) -> Bool { /*return some `Bool`*/ }

struct English: LanguageType { }
struct German: LanguageType { }

var something = SomethingTranslatable()
something.translations[AnyLanguage(English())] = ["Hello", "World"]
let germanWords = something.translations[AnyLanguage(German())]

不同类型,符合LanguageType,现在可以作为Key使用。唯一的语法差异是 AnyLanguage:

的必要初始化
AnyLanguage(English())