Checking Hashable Conformance

Checking Hashable conformance

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)

Check if Any conforms to Hashable and get hash value

You need to use AnyHashable instead of Hashable, which is the type erased version of the Hashable protocol created to resolve that specific error you are encountering.

if let h = anyValue as? AnyHashable {

Conforming to Hashable protocol?

You're missing the declaration:

struct DateStruct: Hashable {

And your == function is wrong. You should compare the three properties.

static func == (lhs: DateStruct, rhs: DateStruct) -> Bool {
return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day
}

It's possible for two different values to have the same hash value.

Conditional Conformance to Hashable

The conditional compilation statement #if swift(...) checks against the language version you're running with – which can differ from the compiler (and therefore standard library) version.

In your case, it sounds like you're using a Swift 4.2 compiler in Swift 4 compatibility mode, which gives you a language version of 4.1.50. This therefore passes the conditional compilation statement and your extension is compiled, giving you a duplicate conformance.

In order to check for a compiler version less than 4.2, you want the following:

// less than 4.2 in Swift 4 compat mode and (less than 4.2 in 3 compat mode or
// greater than 4).
#if !swift(>=4.1.50) && (!swift(>=3.4) || swift(>=4.0))
extension Array : Hashable where Element : Hashable {
public var hashValue: Int {
let prime = 31
var result = 1
for element in self {
result = prime * result + element.hashValue
}
return result
}
}
#endif

Things will be much better with the new #if compiler directive, which is available in Swift 4.2 from Xcode 10 beta 4 onwards (confirmed by @MartinR). With that, you can directly test for the compiler version, ignoring any compatibility modes it might be running in.

This doesn't help your specific case as you need the code to be understood by both Swift 4.2 and 4.1 compilers (as also pointed out by Martin), but for future compatibility problems, you could for example use #if !compiler(>=5) in order to only compile a block of code if using a 4.2 compiler.

Make a swift protocol conform to Hashable

Deriving the protocol from Hashable and using a type eraser might help here:

protocol SomeLocation: Hashable {
var name: String { get }
var coordinates: Coordinate { get }
}

struct AnyLocation: SomeLocation {
let name: String
let coordinates: Coordinate

init<L: SomeLocation>(_ location: L) {
name = location.name
coordinates = location.coordinates
}
}

You then can simply declare the protocol conformance on the structs, and if Coordinate is already Hashable, then you don't need to write any extra hashing code code, since the compiler can automatically synthesize for you (and so will do for new types as long as all their properties are Hashable:

struct ShopLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}

struct CarLocation: SomeLocation, Decodable {
var name: String
var coordinates: Coordinate
}

If Coordinate is also Codable, then you also can omit writing any code for the encoding/decoding operations, the compile will synthesize the required methods (provided all other properties are already Codable).

You can then use the eraser within the annotation class by forwardingn the initializer constraints:

final class LocationAnnotation: NSObject, MKAnnotation {   
let location: AnyLocation

init<L: SomeLocation>(location: L) {
self.location = AnyLocation(location)
super.init()
}

override var hash: Int {
location.hashValue
}

override func isEqual(_ object: Any?) -> Bool {
(object as? LocationAnnotation)?.location == location
}
}

Swift, how to implement Hashable protocol based on object reference?

If you are working with classes and not structs, you can use the ObjectIdentifier struct. Note that you also have to define == for your class in order to conform to Equatable (Hashable requires it). It would look something like this:

class MyClass: Hashable { }

func ==(lhs: MyClass, rhs: MyClass) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

class MyClass: Hashable {
var hashValue: Int {
return ObjectIdentifier(self).hashValue
}
}

How to make an enum conform to Hashable with the API available in Xcode 10?

You can use autogenerated Hashable conformance, as proposed in the other answer (under condition your type doesn't contains any date of non-Hashable types).

But that's what you can do in the general case(autogenerated code would probably look like that too):

extension MyEnum: Hashable {

func hash(into hasher: inout Hasher) {

switch self {
case .caseOne(let value):
hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
case .caseTwo(let value):
hasher.combine(value) // combine with associated value, if it's not `Hashable` map it to some `Hashable` type and then combine result
case .caseThree:
// you can `combine` with some `Hashable` constant, but here it's ok just to skip
break
}
}
}

Creating a protocol that represents hashable objects that can be on or off

I don't know if I'd necessarily make the OnOffRepresentable protocol inherit from Hashable. It doesn't seem like something that you'd want to be represented as on or off must also be hashable. So in my implementation below, I add the Hashable conformance to the type erasing wrapper only. That way, you can reference OnOffRepresentable items directly whenever possible (without the "can only be used in a generic constraint" warning), and only wrap them inside the HashableOnOffRepresentable type eraser when you need to place them in sets or use them as dictionary keys.

protocol OnOffRepresentable {
func isInOnState() -> Bool
func isInOffState() -> Bool
}

extension UISwitch: OnOffRepresentable {
func isInOnState() -> Bool { return on }
func isInOffState() -> Bool { return !on }
}

extension UIButton: OnOffRepresentable {
func isInOnState() -> Bool { return selected }
func isInOffState() -> Bool { return !selected }
}

struct HashableOnOffRepresentable : OnOffRepresentable, Hashable {

private let wrapped:OnOffRepresentable
private let hashClosure:()->Int
private let equalClosure:Any->Bool

var hashValue: Int {
return hashClosure()
}

func isInOnState() -> Bool {
return wrapped.isInOnState()
}

func isInOffState() -> Bool {
return wrapped.isInOffState()
}

init<T where T:OnOffRepresentable, T:Hashable>(with:T) {
wrapped = with
hashClosure = { return with.hashValue }
equalClosure = { if let other = $0 as? T { return with == other } else { return false } }
}
}

func == (left:HashableOnOffRepresentable, right:HashableOnOffRepresentable) -> Bool {
return left.equalClosure(right.wrapped)
}

func == (left:HashableOnOffRepresentable, right:OnOffRepresentable) -> Bool {
return left.equalClosure(right)
}

var toggleToLabelMapper: [HashableOnOffRepresentable : UILabel] = [:]

let anySwitch = HashableOnOffRepresentable(with:UISwitch())
let anyButton = HashableOnOffRepresentable(with:UIButton())

var switchLabel:UILabel!
var buttonLabel:UILabel!

toggleToLabelMapper[anySwitch] = switchLabel
toggleToLabelMapper[anyButton] = buttonLabel


Related Topics



Leave a reply



Submit