> On May 27, 2016, at 3:22 PM, Ricardo Parada via swift-evolution > <[email protected]> wrote: > > Inline > > > On May 27, 2016, at 2:52 PM, Matthew Johnson <[email protected] > <mailto:[email protected]>> wrote: > >> >>> On May 27, 2016, at 12:48 PM, Ricardo Parada <[email protected] >>> <mailto:[email protected]>> wrote: >>> >>> >>> What if we get the error when trying to use it? For example, if a struct >>> uses a value that is not Equatable / Hashable then it would not be >>> Equatable / Hashable and you would not find out until you tried to use it. >>> Would that be bad? >> >> Yes. It would also be bad if implicit synthesis resulted in an >> unintentional and incorrect definition of equality. By requiring synthesis >> to be requested with `deriving` the programmer is at least prompted to >> consider the meaning of equality for their type. > > Incorrect definition of equality? Hmm... :-) > > I guess I have been running under the wrong assumption that if a struct uses > values that are all Equatable then the default implementation for the struct > which will compare the values against the values in the other struct will > ALWAYS be correct. But I guess I can come up with an example where some of > the values stored in the struct do not play a role in the definition of > equality even if those values are Equatable. Then the default implementation > would be incorrect.
A recent one for me was a rational type, e.g. you’d want things like `1/2 == 2/4` (and in this case I didn’t want an implementation that *would* always automatically use fully-reduced internal representations). I *do* think Swift is missing a “bit-by-bit/physical" equality operator (for which 1/2 and 2/4 would be distinct, here), and Swift should probably get one at some point, but that’s IMHO another (but related) discussion. > But I am not convince that is bad because that can happen regardless of > whether equatable is an opt-in thing or automatic. For example, let's say you > opt-in by saying that it implements Equatable or by using the derived / > synthesizes keyword that we have mentioned. The developer may not realize > until later that the default implementation would be wrong for your > fancy/unusual struct. It is likely that opting in may raise a flag in your > brain that says "hey, is the default implementation going to do the right > thing? Do you need to customize it for your struct?" But it's not a guarantee > either. And if it's not a guarantee then should it be automatic then? Most > developer will go with the default implementation when they opt-in and then > realize later that they may need to customize when things are not working > quite the way the expected. > > >>> >>> >>>> On May 26, 2016, at 11:35 AM, Matthew Johnson via swift-evolution >>>> <[email protected] <mailto:[email protected]>> wrote: >>>> >>>>> >>>>> On May 26, 2016, at 10:18 AM, T.J. Usiyan via swift-evolution >>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>> >>>>> +1 to a `deriving` keyword >>>> >>>> + 1. I like it as well. It makes the feature opt-in, declaring >>>> conformance and requesting synthesis at the same time. The syntactic >>>> difference from a simple conformance declaration means manual conformance >>>> can still be checked properly with no ambiguity about whether you were >>>> requesting synthesis or not. This approach also generalizes well. >>>> >>>> This bullet makes me uncomfortable though: >>>> >>>>> - 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. >>>> >>>> >>>> You should not be able to just say `struct Shape<T> deriving Equatable`. >>>> You should have to do this: >>>> >>>> extension Shape deriving Equatable where T: Equatable {} >>>> >>>> Or some equivalent syntax that makes it clear that you only intend to >>>> derive equatable when T meets the stated conditions. >>>> >>>>> >>>>> On Thu, May 26, 2016 at 3:58 AM, Michael Peternell via swift-evolution >>>>> <[email protected] <mailto:[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] <mailto:[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] <mailto:[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/ >>>>> > <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] <mailto:[email protected]> >>>>> > https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> > <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> > >>>>> > >>>>> > _______________________________________________ >>>>> > swift-evolution mailing list >>>>> > [email protected] <mailto:[email protected]> >>>>> > https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> > <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> [email protected] <mailto:[email protected]> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> [email protected] <mailto:[email protected]> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> [email protected] <mailto:[email protected]> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> <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
