+1 for "deriving", "synthesizes", or some other keyword. How are we going to tell the compiler what protocols can participate? Something like "@memberwise" is all I can think of, but I'm too tired for deep thought at the moment.
- Dave Sweeris > On May 26, 2016, at 21:57, Ricardo Parada via swift-evolution > <[email protected]> wrote: > > > > I wonder if synthesizes would be a better choice than deriving. > > > >> On May 26, 2016, at 5:58 AM, Michael Peternell via swift-evolution >> <[email protected]> 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 >>> <[email protected]>: >>> >>> 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 >>> <[email protected]> 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. >>> >>> 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 >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> >>> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] >> https://lists.swift.org/mailman/listinfo/swift-evolution > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
