检查Hashable一致性 [英] Checking Hashable conformance

查看:96
本文介绍了检查Hashable一致性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

 协议模型{} 
结构体联系人:模型,哈希{
var hashValue :Int {return ...}
static func ==(lhs:Contact,rhs:Contact) - > Bool {return ...}
}
结构地址:Model,Hashable {
var hashValue:Int {return ...}
static func ==(lhs:Address, rhs:地址) - > Bool {return ...}
}

我有一个函数需要一个数组符合Model([Model])的对象。
如何将[Model]传递给一个需要Hashables而不需要Model Hashable的函数?

  func complete与模型:[模型]){
doSomethingWithHashable(模型)//不能这样做

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

我试图避免这种情况

 协议模型:Hashable {} 
func complete< T:Model>(with models:[T]){
runComparison(models)
}

因为我得到了Model不能用作通用约束...当我这样做的时候

  protocol SomethingElse {
var data:[Model] {get}


解决方案

'以模型的形式进行交谈,该模型允许关于 Hashable 一致性的 nothing 。正如你指出的那样,告诉编译器这个问题(即从 Hashable 导出 Model )然后就会丢失以符合 Model 的不同类型进行交谈的能力。



如果您甚至不关心模型一致性,您可以使用标准库的 AnyHashable 完全是任意的 Hashable 符合实例。



但是,假设您关心 Model 一致性,您必须构建自己的类型擦除包装器符合模型 Hashable 。在我的答案中,我演示了如何为 Equatable 符合类型。这里的逻辑可以很容易地扩展为 Hashable - 我们只需要存储一个额外的函数来返回 hashValue



例如:

  struct AnyHashableModel:Model, Hashable {

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

//转发给lhs和rhs的_isEqual以确定相等性。
//两者必须被调用的原因是保持
//超类与子类进行比较时的对称性。
//如果你知道你总是使用值类型,你可以省略其中的一个。
返回lhs._isEqual(rhs)|| rhs._isEqual(lhs)
}

private let base:Model

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

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

self.base = base

_isEqual = {
//尝试将传递的实例转换为
// AnyHashableModel被初始化的具体类型,返回该
//类型的==实现的结果,否则返回false。
如果让其他= $ 0.base为? T {
return base == other
} else {
return false
}
}

//简单地分配一个闭包捕获并返回它的hashValue
_hashValue = {base.hashValue}
}
$ b $ var var hashValue:Int {return _hashValue()}
}

然后你可以像这样使用它:

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

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

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

在这里,我假设您还想将它用作<$ c的包装$ c> Model 的要求(假设有一些)。或者,您可以公开 base 属性,并从 AnyHashableModel 中删除 Model 一致性c $ c>本身,使调用者可以访问基础 Model 符合实例的 base

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






然而你会注意到上面的类型擦除包装仅适用于 Hashable 模型类型。如果我们想讨论一些符合实例 Hashable



的其他协议,正如我在本Q& A中演示 所述,是接受 Hashable code>并符合其他协议 - 其类型由一个通用的占位符表示。



由于Swift目前没有办法表达一个通用的占位符必须符合另一个通用占位符给出的协议;这个关系必须由调用者用 transform 闭包来定义,以执行必要的upcast。但是,由于Swift 3.1接受扩展中具体的相同类型的需求,我们可以定义一个方便的初始化程序来移除 Model 的这个样板(这可以重复其他协议类型)。



例如:

  ///类型擦除封装符合Hashable,
///的类型,但是继承/符合不需要
/// Hashable一致性的类型T.在几乎所有情况下,T应该是协议类型。
struct AnySpecificHashable< T> :Hashable {

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

let base:T

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

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

self.base = upcast(base)

_isEqual = {
如果让其他= $ 0.base为? U {
return base == other
} else {
return false
}
}

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

//当T是Model时,方便初始化的扩展。
extension AnySpecificHashable where T == Model {
init< U:Model>(_ base:U)where U:hashable {
self.init(base,upcast:{$ 0})


$ / code>

你现在想把你的实例包装在一个 b
$ b $ func complete(使用模型:[AnySpecificHashable< Model>]< / code>< Model> ;]){
doSomethingWithHashable(models)
}

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

让模型:[AnySpecificHashable< Model> = [
AnySpecificHashable(Contact()),
AnySpecificHashable(Address())
]

完成(带:模型)


I have a base protocol (Model) that some structs conform to. They also conform to 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 ... } 
}

I have a function that takes an array of objects conforming to Model ([Model]). How can I pass the [Model] to a function that requires Hashables without making Model Hashable?

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

I'm trying to avoid this

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

Because I get "Model cannot be used as a generic constraint..." when I do this

protocol SomethingElse {
    var data: [Model] { get }
}

解决方案

The problem with your code is that you're talking in terms of Model, which promises nothing about Hashable conformance. As you point out, the problem with telling the compiler about this (i.e deriving Model from Hashable) is you then lose the ability to talk in terms of heterogenous types that conform to Model.

If you don't even care about Model conformance in the first place, you can just use the standard library's AnyHashable type-erased wrapper for completely arbitrary Hashable conforming instances.

However, assuming you do care about Model conformance, you'll have to build your own type-erased wrapper for instances that conform to both Model and Hashable. In my answer here, I demonstrate how a type eraser can be built for Equatable conforming types. The logic there can be very easily extended for Hashable – we just need to store an extra function to return the hashValue of the instance.

For example:

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

You would then use it like so:

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

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

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

Here I'm assuming that you'll also want to use it as a wrapper for Model's requirements (assuming there are some). Alternatively, you can expose the base property and remove the Model conformance from AnyHashableModel itself, making callers access the base for the underlying Model conforming instance:

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


You will however note that the above type-erased wrapper is only applicable to types that are both Hashable and a Model. What if we want to talk about some other protocol where the conforming instances are Hashable?

A more general solution, as I demonstrate in this Q&A, is to instead accept types that are both Hashable and conform to some other protocol – the type of which is expressed by a generic placeholder.

As there's currently no way in Swift to express a generic placeholder that must conform to a protocol given by another generic placeholder; this relationship must be defined by the caller with a transform closure to perform the necessary upcast. However, thanks to Swift 3.1's acceptance of concrete same-type requirements in extensions, we can define a convenience initialiser to remove this boilerplate for Model (and this can be repeated for other protocol types).

For example:

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

You would now want to wrap your instances in a 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)

这篇关于检查Hashable一致性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆