I agree. This also isn't the only case where a 'deriving' keyword would come in handy. I'm mulling a proposal for a compiler-derived enum, and I worried the magic-ness of it would face strong opposition. Having a 'deriving' keyword is the perfect solution, since it becomes explicit.
> On May 26, 2016, at 11:18 AM, T.J. Usiyan via swift-evolution > <swift-evolution@swift.org> wrote: > > +1 to a `deriving` keyword > > On Thu, May 26, 2016 at 3:58 AM, Michael Peternell via swift-evolution > <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: > Can we just copy&paste the solution from Haskell instead of creating our own? > It's just better in every aspect. Deriving `Equatable` and `Hashable` would > become > > struct Polygon deriving Equatable, Hashable { > ... > } > > This has several advantages: > - you don't have to guess wether `Equatable` or `Hashable` should be > automatically derived or not. > - Deriving becomes an explicit choice. > - If you need a custom `Equatable` implementation (for whatever reason), you > can still do it. > - It doesn't break any code that is unaware of the change > - It can be extended in future versions of Swift, without introducing any new > incompatibilities. For example, `CustomStringConvertible` could be derived > just as easily. > - It is compatible with generics. E.g. `struct Shape<T> deriving Equatable` > will make every `Shape<X>` equatable if `X` is equatable. But if `X` is not > equatable, `Shape<X>` can be used as well. (Unless `X` is not used, in which > case every `Shape<T>` would be equatable. Unless something in the definition > of `Shape` makes deriving `Equatable` impossible => this produces an error.) > - It is proven to work in production. > > -Michael > > > Am 26.05.2016 um 03:48 schrieb Mark Sands via swift-evolution > > <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>: > > > > Thanks so much for putting this together, Tony! Glad I was able to be some > > inspiration. :^) > > > > > > On Wed, May 25, 2016 at 1:28 PM, Tony Allevato via swift-evolution > > <swift-evolution@swift.org <mailto: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/ > > <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. > > > > 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 <mailto:swift-evolution@swift.org> > > https://lists.swift.org/mailman/listinfo/swift-evolution > > <https://lists.swift.org/mailman/listinfo/swift-evolution> > > > > > > _______________________________________________ > > swift-evolution mailing list > > swift-evolution@swift.org <mailto:swift-evolution@swift.org> > > https://lists.swift.org/mailman/listinfo/swift-evolution > > <https://lists.swift.org/mailman/listinfo/swift-evolution> > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org <mailto:swift-evolution@swift.org> > https://lists.swift.org/mailman/listinfo/swift-evolution > <https://lists.swift.org/mailman/listinfo/swift-evolution> > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org > https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution