[swift-evolution] Custom equality/hash for Sets

Dave Abrahams dabrahams at apple.com
Sun Feb 21 06:15:24 CST 2016


on Fri Feb 19 2016, Nicola Salmoria <swift-evolution at swift.org> wrote:

> Would it make sense to abstract the hash functions into a protocol?
>
> The limit of the Hashable protocol is that it forces a type to commit to a specific ‘canonical’ hash implementation, while there might be more efficient implementations depending on the application.
>
> To truly support composition patterns, it would seem appropriate for the standard library to support decoupling the hashing functionality from the type of the container element.
>
> E.g.
>
> protocol Hash {
>     typealias Element
>
>     static func equal(lhs: Element, _ rhs: Element) -> Bool
>     static func hashValue(x: Element) -> Int
> }
>
> // now we can define a generic box
> struct HashableBox<T, H: Hash where H.Element == T> : Hashable {
>     let value: T
>
>     var hashValue: Int {
>         return H.hashValue(value)
>     }
> }
> func ==<T, H>(lhs: HashableBox<T, H>, rhs: HashableBox<T, H>) -> Bool {
>     return H.equal(lhs.value, rhs.value)
> }

Wow, this looks mighty familiar.  Sorry I didn't read your post first :-)

> // now we can define a wrapper around Set, using an explicit Hash
> struct CustomHashSet<Element, H: Hash where H.Element == Element> {
>     // this is an implementation detail; if CustomHashSet was part of the standard library,
>     // the .gyb could produce a more efficient implementation
>     private var set: Set<HashableBox<Element, H>>
>
>     init() {}
>     init<S: SequenceType where S.Generator.Element == Element>(_ sequence: S) {
>         set = Set(sequence.lazy.map { HashableBox<Element, H>(value: $0) })
>     }
>     // etc.
> }

> // Example usage
> struct NaiveCollectionHash<C: CollectionType where C.Generator.Element: Hashable>: Hash {
>     typealias Element = C
>
>     static func equal(lhs: Element, _ rhs: Element) -> Bool {
>         guard lhs.count == rhs.count else { return false }
>         return zip(lhs, rhs).lazy.reduce(true) { $0 && ($1.0 == $1.1) }
>     }
>     static func hashValue(x: Element) -> Int {
>         return x.reduce(0) { $0 ^ $1.hashValue }    // a bad hash just as an example
>     }
> }
>
> let arrays = [[1],[2]]
>
> // This doesn't work because Array is not Hashable (yet)
> var s1 = Set(arrays) // Error
>
> // This works but we need to explicitly specify all the types
> var s2 = CustomHashSet<Array<Int>, NaiveCollectionHash<Array<Int>>>(arrays) // ok
>
> // Generic typealiases would help reducing the boilerplate, e.g.
> // it would be useful if we could do
> typealias NaiveHashSet<T> = CustomHashSet<T, NaiveCollectionHash<T>>    // Error
> // then Swift's type inference should be able to automatically handle this:
> var s3 = NaiveHashSet(arrays)   // Error
>
> Nicola
>
>> Cc:swift-evolution<swift-evolution at swift.org>
>> Subject:[swift-evolution] Custom equality/hash for Sets
>> Date:19 February 2016 at 05:04:51 GMT+1
>> 
>> 
>> 
>> > On Feb 18, 2016, at 16:09 , Dave Abrahams via
>> > swift-evolution<swift-evolution at swift.org(mailto: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>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. It's also not reusable code—you have to rewrite the box
>> for every type.
>> 
>> I'd say you usuallydon'twant 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.
>> 
>> -1 from me until we can put functions in generics, if ever.
>> 
>> Jordan_______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
>> 
>> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-- 
-Dave



More information about the swift-evolution mailing list