检查哈希一致性

乔尔斯

我有一些结构符合的基本协议(模型)。它们也符合Hashable

protocol Model {}
struct Contact: Model, Hashable {
    var hashValue: Int { return ... }
    static func ==(lhs: Contact, rhs: Contact) -> Bool { return ... } 
}
struct Address: Model, Hashable {
    var hashValue: Int { return ... }
    static func ==(lhs: Address, rhs: Address) -> Bool { return ... } 
}

我有一个函数,该函数接受符合模型([Model])的对象数组。如何在不使“模型可哈希化”的情况下将[模型]传递给需要可哈希化的函数?

func complete(with models: [Model]) {
    doSomethingWithHashable(models) //can't do this
}
func doSomethingWithHashable <T:Hashable>(_ objects: [T]) {
    //
}

我正在努力避免这种情况

protocol Model: Hashable {}
func complete<T:Model>(with models: [T]) {
    runComparison(models)
}

因为执行此操作时出现“无法将模型用作一般约束...”

protocol SomethingElse {
    var data: [Model] { get }
}
哈米什

您的代码的问题在于您使用的方式进行交谈Model,这一致性没有任何保证Hashable正如您所指出的那样,告诉编译器有关此问题(即Model派生Hashable)的问题是,您然后失去了就符合的异构类型进行交谈的能力Model

如果您根本不关心Model一致性,则可以将标准库的AnyHashable类型擦除包装器用于完全任意的 Hashable一致性实例。

但是,假设您确实在乎Model一致性,则必须为同时符合和的实例构建自己的类型擦除包装器这里的答案中,我演示了如何为符合类型的类型构建类型擦除器那里的逻辑可以很容易地扩展–我们只需要存储一个额外的函数来返回实例的。ModelHashableEquatableHashablehashValue

例如:

struct AnyHashableModel : Model, Hashable {

    static func ==(lhs: AnyHashableModel, rhs: AnyHashableModel) -> Bool {

        // forward to both lhs's and rhs's _isEqual in order to determine equality.
        // the reason that both must be called is to preserve symmetry for when a
        // superclass is being compared with a subclass.
        // if you know you're always working with value types, you can omit one of them.
        return lhs._isEqual(rhs) || rhs._isEqual(lhs)
    }

    private let base: Model

    private let _isEqual: (_ to: AnyHashableModel) -> Bool
    private let _hashValue: () -> Int

    init<T : Model>(_ base: T) where T : Hashable {

        self.base = base

        _isEqual = {
            // attempt to cast the passed instance to the concrete type that
            // AnyHashableModel was initialised with, returning the result of that
            // type's == implementation, or false otherwise.
            if let other = $0.base as? T {
                return base == other
            } else {
                return false
            }
        }

        // simply assign a closure that captures base and returns its hashValue
        _hashValue = { base.hashValue }
    }

    var hashValue: Int { return _hashValue() }
}

然后,您将使用它,如下所示:

func complete(with models: [AnyHashableModel]) {
    doSomethingWithHashable(models)
}

func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
    //
}

let models = [AnyHashableModel(Contact()), AnyHashableModel(Address())]
complete(with: models)

在这里,我假设您还希望将其用作Model的需求的包装器(假设有一些需求)。或者,您可以公开base属性并Model从其AnyHashableModel自身中删除一致性,使调用者可以访问base基础Model一致性实例的:

struct AnyHashableModel : Hashable {
    // ...
    let base: Model
    // ...
}

但是,您将注意到上述类型擦除的包装器仅适用于Hashable都为的类型Model如果我们想讨论符合条件的实例所在的其他协议Hashable怎么办?

正如我在本问答中所展示的一种更通用的解决方案是改为接受既Hashable符合又符合某些其他协议的类型-其类型由通用占位符表示。

由于Swift目前无法表达必须符合另一个通用占位符给出的协议的通用占位符;此关系必须由调用transform方定义,并带有闭包以执行必要的向上转换。但是,由于Swift 3.1在扩展中接受了具体的同类型要求,我们可以定义一个便捷的初始化程序来删除此样板Model(对于其他协议类型,可以重复此操作)。

例如:

/// Type-erased wrapper for a type that conforms to Hashable,
/// but inherits from/conforms to a type T that doesn't necessarily require
/// Hashable conformance. In almost all cases, T should be a protocol type.
struct AnySpecificHashable<T> : Hashable {

    static func ==(lhs: AnySpecificHashable, rhs: AnySpecificHashable) -> Bool {
        return lhs._isEqual(rhs) || rhs._isEqual(lhs)
    }

    let base: T

    private let _isEqual: (_ to: AnySpecificHashable) -> Bool
    private let _hashValue: () -> Int

    init<U : Hashable>(_ base: U, upcast: (U) -> T) {

        self.base = upcast(base)

        _isEqual = {
            if let other = $0.base as? U {
                return base == other
            } else {
                return false
            }
        }

        _hashValue = { base.hashValue }
    }
    var hashValue: Int { return _hashValue() }
}

// extension for convenience initialiser for when T is Model.
extension AnySpecificHashable where T == Model {
    init<U : Model>(_ base: U) where U : Hashable {
        self.init(base, upcast: { $0 })
    }
}

您现在想将实例包装在中AnySpecificHashable<Model>

func complete(with models: [AnySpecificHashable<Model>]) {
    doSomethingWithHashable(models)
}

func doSomethingWithHashable<T : Hashable>(_ objects: [T]) {
    //
}

let models: [AnySpecificHashable<Model>] = [
    AnySpecificHashable(Contact()),
    AnySpecificHashable(Address())
]

complete(with: models)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章