on Wed May 25 2016, Tony Allevato <swift-evolution@swift.org> wrote:

> I was inspired to put together a draft proposal based on an older discussion 
> in
> the Universal Equality, Hashability, and Comparability thread
> <http://thread.gmane.org/gmane.comp.lang.swift.evolution/8919/> that recently
> got necromanced (thanks Mark Sands!).
>
> I'm guessing that this would be a significant enough change that it's not
> possible for the Swift 3 timeline, but it's something that would benefit 
> enough
> people that I want to make sure the discussion stays alive. If there are 
> enough
> good feelings about it, I'll move it from my gist into an actual
> proposal PR.

Hi Tony,

As you might imagine I'm intensely interested in this topic and I have
some strongly held views.  That said, since it's out of scope for Swift
3, you shouldn't expect too much participation from me  at this point.

>
>
> Automatically deriving Equatable andHashable for value types
>
> * Proposal: SE-0000
> * Author(s): Tony Allevato
> * Status: Awaiting review
> * Review manager: TBD
>
> Introduction
>
> Value types are prevalent throughout the Swift language, and we encourage
> developers to think in those terms when writing their own types. Frequently,
> developers find themselves writing large amounts of boilerplate code to 
> support
> equatability and hashability of value types. This proposal offers a way for 
> the
> compiler to automatically derive conformance toEquatable and Hashable to 
> reduce
> this boilerplate, in a subset of scenarios where generating the correct
> implementation is likely to be possible.
>
> Swift-evolution thread: Universal Equatability, Hashability, and Comparability
>
> Motivation
>
> Building robust value types in Swift can involve writing significant 
> boilerplate
> code to support concepts of hashability and equatability. Equality is 
> pervasive
> across many value types, and for each one users must implement the == operator
> such that it performs a fairly rote memberwise equality test. As an example, 
> an
> equality test for a struct looks fairly uninteresting:
>
> func ==(lhs: Foo, rhs: Foo) -> Bool {
>   return lhs.property1 == rhs.property1 &&
>          lhs.property2 == rhs.property2 &&
>          lhs.property3 == rhs.property3 &&
>          ...
> }
>
> What's worse is that this operator must be updated if any properties are 
> added,
> removed, or changed, and since it must be manually written, it's possible to 
> get
> it wrong, either by omission or typographical error.
>
> Likewise, hashability is necessary when one wishes to store a value type in a
> Set or use one as a multi-valuedDictionary key. Writing high-quality,
> well-distributed hash functions is not trivial so developers may not put a 
> great
> deal of thought into them – especially as the number of properties increases –
> not realizing that their performance could potentially suffer as a result. And
> as with equality, writing it manually means there is the potential to get it
> wrong.
>
> In particular, the code that must be written to implement equality for enums 
> is
> quite verbose. One such real-world example (source):
>
> func ==(lhs: HandRank, rhs: HandRank) -> Bool {
>   switch (lhs, rhs) {
>   case (.straightFlush(let lRank, let lSuit), .straightFlush(let rRank , let 
> rSuit)):
>     return lRank == rRank && lSuit == rSuit
>   case (.fourOfAKind(four: let lFour), .fourOfAKind(four: let rFour)):
>     return lFour == rFour
>   case (.fullHouse(three: let lThree), .fullHouse(three: let rThree)):
>     return lThree == rThree
>   case (.flush(let lRank, let lSuit), .flush(let rRank, let rSuit)):
>     return lSuit == rSuit && lRank == rRank
>   case (.straight(high: let lRank), .straight(high: let rRank)):
>     return lRank == rRank
>   case (.threeOfAKind(three: let lRank), .threeOfAKind(three: let rRank)):
>     return lRank == rRank
>   case (.twoPair(high: let lHigh, low: let lLow, highCard: let lCard),
>         .twoPair(high: let rHigh, low: let rLow, highCard: let rCard)):
>     return lHigh == rHigh && lLow == rLow && lCard == rCard
>   case (.onePair(let lPairRank, card1: let lCard1, card2: let lCard2, card3: 
> let lCard3),
>         .onePair(let rPairRank, card1: let rCard1, card2: let rCard2, card3: 
> let rCard3)):
>     return lPairRank == rPairRank && lCard1 == rCard1 && lCard2 == rCard2 && 
> lCard3 == rCard3
>   case (.highCard(let lCard), .highCard(let rCard)):
>     return lCard == rCard
>   default:
>     return false
>   }
> }
>
> Crafting a high-quality hash function for this enum would be similarly
> inconvenient to write, involving another large switchstatement.
>
> Swift already provides implicit protocol conformance in some cases; notably,
> enums with raw values conform toRawRepresentable, Equatable, and Hashable
> without the user explicitly declaring them:
>
> enum Foo: Int {
>   case one = 1
>   case two = 2
> }
>
> let x = (Foo.one == Foo.two)  // works
> let y = Foo.one.hashValue     // also works
> let z = Foo.one.rawValue      // also also works
>
> Since there is precedent for this in Swift, we propose extending this support 
> to
> more value types.
>
> Proposed solution
>
> We propose that a value type be Equatable/Hashable if all of its members are
> Equatable/Hashable, with the result for the outer type being composed from its
> members.
>
> Specifically, we propose the following rules for deriving Equatable:
>
> *   A struct implicitly conforms to Equatable if all of its fields are of 
> types
>   that conform to Equatable – either explicitly, or implicitly by the
>   application of these rules. The compiler will generate an implementation of 
> ==
>   (lhs: T, rhs: T)that returns true if and only if lhs.x == rhs.x for all 
> fields
>   x in T.
>
> *   An enum implicitly conforms to Equatable if all of its associated values
>   across all of its cases are of types that conform to Equatable – either
>   explicitly, or implicitly by the application of these rules. The compiler 
> will
>   generate an implementation of ==(lhs: T, rhs: T) that returns true if and 
> only
>   if lhs and rhs are the same case and have payloads that are 
> memberwise-equal.
>
> Likewise, we propose the following rules for deriving Hashable:
>
> *   A struct implicitly conforms to Hashable if all of its fields are of types
>   that conform to Hashable – either explicitly, or implicitly by the 
> application
>   of these rules. The compiler will generate an implementation of hashValue 
> that
>   uses a pre-defined hash function† to compute the hash value of the struct 
> from
>   the hash values of its members.
>
>   Since order of the terms affects the hash value computation, we recommend
>   ordering the terms in member definition order.
>
> *   An enum implicitly conforms to Hashable if all of its associated values
>   across all of its cases are of types that conform to Hashable – either
>   explicitly, or implicitly by the application of these rules. The compiler 
> will
>   generate an implementation of hashValue that uses a pre-defined hash 
> function†
>   to compute the hash value of an enum value by using the case's ordinal 
> (i.e.,
>   definition order) followed by the hash values of its associated values as 
> its
>   terms, also in definition order.
>
> † We leave the exact definition of the hash function unspecified here; a
> multiplicative hash function such as Kernighan and Ritchie or Bernstein is 
> easy
> to implement, but we do not rule out other possibilities.
>
> Overriding defaults
>
> Any user-provided implementations of == or hashValue should override the 
> default
> implementations that would be provided by the compiler. This is already 
> possible
> today with raw-value enums so the same behavior should be extended to other
> value types that are made to implicitly conform to these protocols.
>
> Open questions
>
> Omission of fields from generated computations
>
> Should it be possible to easily omit certain properties from automatically
> generated equality tests or hash value computation? This could be valuable, 
> for
> example, if a property is merely used as an internal cache and does not 
> actually
> contribute to the "value" of the instance. Under the rules above, if this 
> cached
> value was equatable, a user would have to override == and hashValue and 
> provide
> their own implementations to ignore it. If there is significant evidence that
> this pattern is common and useful, we could consider adding a custom 
> attribute,
> such as @transient, that would omit the property from the generated
> computations.
>
> Explicit or implicit derivation
>
> As with raw-value enums today, should the derived conformance be completely
> explicit, or should users have to explicitly list conformance with Equatable 
> and
> Hashable in order for the compiler to generate the derived implementation?
>
> Impact on existing code
>
> This change will have no impact on existing code because it is purely 
> additive.
> Value types that already provide custom implementations of == or hashValue but
> satisfy the rules above would keep the custom implementation because it would
> override the compiler-provided default.
>
> Alternatives considered
>
> The original discussion thread also included Comparable as a candidate for
> automatic generation. Unlike equatability and hashability, however,
> comparability requires an ordering among the members being compared.
> Automatically using the definition order here might be too surprising for 
> users,
> but worse, it also means that reordering properties in the source code changes
> the code's behavior at runtime. (This is true for hashability as well if a
> multiplicative hash function is used, but hash values are not intended to be
> persistent and reordering the terms does not produce a significant behavioral
> change.)
>
> Acknowledgments
>
> Thanks to Joe Groff for spinning off the original discussion thread, Jose 
> Cheyo
> Jimenez for providing great real-world examples of boilerplate needed to 
> support
> equatability for some value types, and to Mark Sands for necromancing the
> swift-evolution thread that convinced me to write this up.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-- 
Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to