> On May 27, 2016, at 5:07 PM, plx via swift-evolution > <swift-evolution@swift.org> wrote: > > >> On May 27, 2016, at 3:22 PM, Ricardo Parada via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >> >> Inline >> >> >> On May 27, 2016, at 2:52 PM, Matthew Johnson <matt...@anandabits.com >> <mailto:matt...@anandabits.com>> wrote: >> >>> >>>> On May 27, 2016, at 12:48 PM, Ricardo Parada <rpar...@mac.com >>>> <mailto:rpar...@mac.com>> 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).
It’s a bit of a tangent, but you might want to take a look at John Lakos’ discussion of rational equality in the talk Dave A posted a link to. There are subtle differences in behavior (overflow, etc) between 1/2 and 2/4. They are not actually substitutable when you have bounded integer types. John makes some interesting observations on this topic. > > 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 >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>> >>>>>> >>>>>> On May 26, 2016, at 10:18 AM, T.J. Usiyan via swift-evolution >>>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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 >>>>>> <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 <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 <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