如何解决Swift不支持一流的元类型?

时间:2016-01-09 11:31:22

标签: swift protocols existential-type

所以我实施了以下内容:

  • 一个简单的LanguageType协议,符合Hashable
  • Translateable协议,该协议允许您使用[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 }
}

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

Error

从我读过的内容来看,这与Swift不支持Existentials有关,这导致协议实际上不是一流的类型。

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

我想要达到的目标是让translations.Key 任何 LanguageType,而不只是一个符合LanguageType的通用类型。
因此,例如,这不会起作用:

protocol Translateable {
    typealias Language: LanguageType

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

出于某种原因,我无法想出实现这一目标的方法。我觉得我觉得我需要某种类型擦除的包装,因为我想要

  

translations.Key 任何 LanguageType

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

更新1: 刚刚在this question中确定,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
}

如果我现在用它代替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,因为它没有做任何事情。但这似乎现在有效。

3 个答案:

答案 0 :(得分:3)

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

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

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

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

答案 1 :(得分:1)

可能您可以使用符合enum的{​​{1}}来模仿您正在寻找的行为。在这种情况下,您无需在LanguageType中明确包含与hashable的一致性,因为枚举为LanguageType

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>"] */ 的类,您可以&#34;动态地将成员&#34; 添加到此虚拟枚举中

enum

使用示例

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

答案 2 :(得分:0)

解决方案似乎是type-erased wrapper。类型擦除通过创建包装类型来解决无法使用protocols with associated types (PATs)作为一等公民的问题,该包装类型仅公开由其包装的协议定义的属性。

在这种情况下,LanguageType是一个 PAT ,因为它采用了Equatable(由于采用了Hashable)而符合它:

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

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

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

    //...
}

如上所述,解决方案是类型擦除的包装器AnyLanguage(Apple对其类型擦除的包装器使用相同的命名约定。例如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())