Swift编译器(字典键):“必须符合Hashable”和“不能符合Hashable”。嗯?

问题描述 投票:0回答:2

我觉得 Swift 编译器给我的信息很复杂。
我不明白这是什么问题。

这就是我所拥有的,我希望这些枚举成为字典的键:

protocol ArbitraryKey : CaseIterable, Hashable {}
enum HearNoEvil  : ArbitraryKey { case a, b, c }
enum SeeNoEvil   : ArbitraryKey { case d, e, f }
enum SpeakNoEvil : ArbitraryKey { case g, h, i }

这就是我想要避免

var dictionary : [ AnyHashable : String ] = [:]

这就是我想做的:

var dictionary : [ any ArbitraryKey : String ] = [:]

协议

ArbitraryKey
最终会有我希望能够在枚举值上调用的方法。

然而,iOS 16.4 / Swift 5.7+ 编译器反对我尝试过的一切:


如果我这样做:

protocol ArbitraryKey : CaseIterable, Hashable {}
var dictonary : [ArbitraryKey : String] = [:]
 类型 'any ArbitraryKey' 不能符合 'Hashable'
    使用协议“ArbitraryKey”作为类型必须写成“any ArbitraryKey”
    将 'ArbitraryKey' 替换为 'any ArbitraryKey'

所以我“修复”它:

protocol ArbitraryKey : CaseIterable, Hashable {}
var dictonary : [any ArbitraryKey : String] = [:]  // <-- added 'any'
 类型 'any ArbitraryKey' 不能符合 'Hashable' 

所以我“修复”了:

protocol ArbitraryKey : CaseIterable {}  // <-- removed Hashable
var dictonary : [any ArbitraryKey : String] = [:]
 类型 'any ArbitraryKey' 不符合协议 'Hashable'

总结:

在这一点上,笨蛋被难住了。
有人可以解释发生了什么吗?
对干净直接的不同方法有什么想法吗?

swift dictionary protocols any hashable
2个回答
0
投票

您在标题中提出的基本问题是编译错误“必须符合 Hashable”和“不能符合 Hashable”如何兼容。它们不仅兼容;他们是一样的。

正如我在评论中向您解释的那样,当您将字典声明为

[Foo: Bar]
类型时,您正在解析 Key 和 Value 通用占位符。 只有具体类型的名称才能做到这一点。 对于 Key,您输入的类型名称必须另外是采用 Hashable 的具体类型的名称。

好吧,协议不是那样的。它不是具体类型,也不能符合 Hashable(协议不能符合协议)。事实上,这正是 Swift 5.8 强制你说

any
的原因,这样你才能面对事实。当编译器让你说
any MyProtocol
时,它试图让你明白这是一个 existential,而不是具体类型。

无论如何,你的标题中提出的问题的答案是编译器非常清楚地说,“这里必须是符合 Hashable 的类型,并且协议存在cannot符合 Hashable 所以它不能到这里。 “

关于您的评论:

我想用具体的几种枚举中的任何一种来索引该字典

好吧,在我看来,这似乎是一件愚蠢的事情,但关键是,如果你这样做,你将 not 能够通过对出于我刚刚演示的原因,你的字典的键类型。您需要执行某种类型的擦除。

例如,您可以从字典中隐藏一个事实,即它的所有键枚举都符合某个协议,方法是让这个事实成为you知道的东西,而不是试图让字典知道它。为此,您可以将字典包装在一个守卫门的类中,以便唯一可接受的键是符合您的协议的类型。像这样:

protocol ArbitraryKey : CaseIterable, Hashable {}
enum HearNoEvil  : ArbitraryKey { case a, b, c }
enum SeeNoEvil   : ArbitraryKey { case d, e, f }
enum SpeakNoEvil : ArbitraryKey { case g, h, i }

class DictionaryWrapper {
    var dict = [AnyHashable: String]()
    func setKey<T: ArbitraryKey>(_ key: T, toValue value: String) {
        dict[key] = value
    }
    func getValueForKey<T: ArbitraryKey>(_ key: T) -> String? {
        return dict[key]
    }
}

这可能看起来很愚蠢,确实如此;这只是一个例子。但它确实非常简单地完成了您(出于只有您知道的原因)要求做的事情:我们已经保证给我们代码的调用者(例如

setKey
getValueForKey
这个键是一个类型符合 ArbitraryKey 协议。所以你可以继续为我们的 DictionaryWrapper 编写方法,直到你完成你的最终目标。


[Extra Point:] 我认为这里的部分混淆在于对

protocol ArbitraryKey : Hashable {}
等表达方式的含义的把握。你在评论中问了这个问题,让我觉得你认为这意味着 ArbitraryKey 符合 Hashable。它没有。协议不符合协议。该代码正在谈论将符合 this 协议(ArbitraryKey)的 concrete 类型。它说:“为了符合我的要求,你必须also(除了我可能施加的任何要求之外)符合 Hashable。”它不是关于协议做什么的声明,而是关于符合类型必须做什么才能符合的声明。


0
投票

如果目标是制作一个字典,其中的键仅限于符合特定协议的类型(您的示例中的枚举),我可以考虑为该协议创建一个特定的字典。

protocol P1: Hashable {}

struct P1KeyDict<Value> {
    private var items: [AnyHashable: Value] = [:]
    
    subscript<K:P1>(key: K) -> Value {
        get {
            return items[key as AnyHashable]!
        }
        set {
            items[key as AnyHashable] = newValue
        }
    }
}

使用方法如下:

var dict: P1KeyDict<String> = P1KeyDict()

enum E1: P1 { case a, b, c }
enum E2: P1 { case d, e, f }
enum E3 { case g, h, i } // for Testing a Type not conforming to the protocol


dict[E1.a] = "Hello"
dict[E2.f] = "World"

print(dict[E1.a])
print(dict[E2.f])

dict[E3.h] = "Good"     // Compile-time Error as Expected:
                        // Subscript 'subscript(_:)' requires that 'E3' conform to 'P1'

这也适用于枚举以外的类型,例如结构,但有一个警告: 如果 AnyHashable(obj1) 和 AnyHashable(obj2) 相同,它会导致相同的密钥,这可能不是您想要的。 对于这种情况,除了使用 AnyHashable 之外,您还可以实现散列方法。

© www.soinside.com 2019 - 2024. All rights reserved.