[swift-evolution] Custom equality/hash for Sets

Dave Abrahams dabrahams at apple.com
Sun Feb 21 06:13:49 CST 2016


on Thu Feb 18 2016, Jordan Rose <swift-evolution at swift.org> wrote:

>> On Feb 18, 2016, at 16:09 , Dave Abrahams via swift-evolution
>> <swift-evolution at swift.org> wrote:
>> 
>> 
>> on Thu Feb 18 2016, Jacob Bandes-Storch
>
>> <swift-evolution at swift.org
>> <mailto:swift-evolution at swift.org>>
>> wrote:
>> 
>>> Would it make sense for the standard library Set to provide variants (or
>>> parallel versions of the same data structure) that take custom hashValue/==
>>> implementations at init time (functions taking in Elements), rather than
>>> relying on Hashable/Comparable protocols?
>>> 
>>> Use case: I want a set of objects that are compared for equality using ===
>>> rather than ==. This doesn't seem possible today, using Set, without
>>> creating some sort of wrapper object.
>>> 
>>> This particular case would be analogous to using NSHashTable with
>>> NSPointerFunctionsObjectPointerPersonality. (Maybe all I'm asking for is a
>>> Swiftier API for NSHashTable — including ArrayLiteralConvertible, using
>>> generics instead of UnsafePointer<Void>, etc.)
>>> 
>>> Similarly, C++'s unordered_map
>>> <http://en.cppreference.com/w/cpp/container/unordered_map
>>> <http://en.cppreference.com/w/cpp/container/unordered_map>> and
>>> friends have
>>> template parameters specifying the hash function and equality
> comparator,
>>> which use std::hash and == by default.
>> 
>> It might make sense.  How bad is the wrapper solution for user code?
>
> struct CustomHashableFoo: Hashable {
>   var value: Foo
>   func hash() -> Int {
>     // custom hash function here
>   }
> }
> func ==(a: CustomHashableWrapped, b: CustomHashableWrapped) {
>   // custom == here
> }
>
> Really not that bad, although you do have to get 'value' in and out of
> the box. 

But that's the part I'm worried about.

> It's also not reusable code—you have to rewrite the box for every
> type.

That's at least partially solvable: 

protocol CustomHash {
  associatedtype Base
  static func hash(Base) -> Int
  static func equal(Base, Base) -> Bool
}

struct CustomHashed<T: CustomHash> : Hashable {
  let value: T.Base
  func hash() -> Int { return T.hash(value) }
}

func == <T>(
  _ lhs: CustomHashed<T>, 
  _ rhs: CustomHashed<T>
) -> Bool {
  return T.equal(lhs, rhs)
}

// ---

// More compact than CustomHashableFoo at least.
struct MyFooHash : CustomHash {
  static func hash(Foo) -> Int { ... }
  static func equal(Foo, Foo) -> Bool { ... }
}

let s: Set<CustomHashed<MyFooHash>> = [ 
  CustomHashed(myFoo1), CustomHashed(myFoo2)
]

>
> I'd say you usually don't want to allow custom hash/== closures
> because (a) then you have to store them somewhere, and (b) the
> compiler won't usually be able to inline them away. You also end up
> with a Set<Foo> that doesn't behave like a normal Set<Foo>—maybe you
> can insert equal-but-not-identical elements—which is bad if anyone's
> relying on normal Set-like guarantees.

Not to mention that we don't know how to implement equality of closures.

If we were going to parameterize Set somehow, it would have to be

  CustomSet<T, H: CustomHash where H.Base == T>

and we'd want the ability to have a default for H when T conforms to
Hashable, which is currently beyond the language.


-- 
-Dave



More information about the swift-evolution mailing list