Sent from my iPad
> On May 29, 2016, at 9:12 AM, Vladimir.S via swift-evolution > <[email protected]> wrote: > >> On 27.05.2016 18:37, plx via swift-evolution wrote: >> >>> On May 26, 2016, at 1:00 PM, T.J. Usiyan via swift-evolution >>> <[email protected] <mailto:[email protected]>> wrote: >>> >>> A `deriving` keyword, at the very least, is pretty explicitly *not* an >>> all-or-nothing situation. If you want to define equality/hashability for >>> your type manually, don't use `deriving`. This should leave the simplest >>> cases to auto generation and anything more complex should be handled by >>> the developer. >> >> It’s all-or-nothing in the sense you can’t use a naive `deriving` >> implementation to assist in any case where what you need is *almost* the >> trivial implementation, but not quite. > > I support that we need a way to exclude some fields from participate in > auto-derived code. It is not handy if we'll have just one-two excluded > properties (of 10 for example) and we'll need to implement the boilerplate > code because of this. Probably, this should be another proposal for this > feature. > > Just some thoughts: such a method to decorate some fields as > `nonequatable`/`nonhashable` has no meaning *if* Equatable/Hashable is later > implemented manually. So, to remove confusion, it seems like such 'method' to > exclude should be disallowed(by compiler) if protocols are implemented > manually by coder. > I.e., I don't want to see a code where we have *both* explicit > implementations of protocols *and* some decorators/special functions to > exclude some fields as this will no any sense. > > Also for me it seems like we need to be able to define such attribute near > the field itself, to prevent later errors when you define new field but > forgot to add it to some 'special list' of excluded field somewhere in code. > So, probably I'd prefer some kind of @nonequatable and @nonhashable : > @nonequatable var field = 0 The same members should be considered by both Equatable and Hashable so there would be no need for two separate annotations. A more general annotation (maybe "nonessential"?) is what we want. > > >> >> Consider a case like this: >> >> class QuxEvaluator { >> >> let foo: Foo // Equatable >> let bar: Bar // Equatable >> let baz: Baz // Equatable >> >> private var quxCache: [QuxIdentifier:Qux] // [Equatable:Equatable] = [:] >> >> // pure function of `foo`, `bar`, `baz`, and `identifier` >> // expensive, and uses `quxCache` for memoization >> func qux(for identifier: QuxIdentifier) -> Qux >> >> } >> >> …if it weren’t for `quxCache` we could easily synthesize `==` for >> `QuxEvaluator`, but the trivial synthesis will yield invalid results due to >> `[QuxIdentifier:Qux]` also being `Equatable` (really: it *will* also be >> equatable once conditional conformances are in place). >> >> So we’re back to e.g. writing this: >> >> extension QuxEvaluator : Equatable { >> >> } >> >> func ==(lhs: QuxEvaluator, rhs: QuxEvaluator) -> Bool { >> return (lhs === rhs) || (lhs.foo == rhs.foo && lhs.bar == rhs.bar && >> lhs.baz == rhs.baz) >> } >> >> …just to omit a single field from the `==` consideration; this is another >> sense in which you can say deriving is an all-or-none; there’s just no way >> to invoke the synthesis mechanism other than for "all fields”. >> >> On the one hand, it’s possible to imagine a finer-grained form of this >> synthesis that’d allow you to e.g. indicate a certain field should be >> omitted (and also perhaps specialize how fields are compared, customize the >> synthesized comparison ordering to put cheaper comparisons earlier, and an >> endless list of other possible requests…). >> >> On the other hand, the memberwise-init proposal had a very similar >> situation: the naive approach was arguably too narrow, but it proved very >> difficult to find a workable balance between customization and >> implementation complexity (leaving it postponed for the foreseeable >> future); it’d be a pity if synthesis like this fell into the same trap. >> >> But, on the gripping hand, I highly suspect that a naive >> `deriving`/synthesis will wind up being too narrowly-useful to really >> justify; that’s just my opinion, of course. >> >>> >>> On Thu, May 26, 2016 at 11:20 AM, L. Mihalkovic >>> <[email protected] <mailto:[email protected]>> wrote: >>> >>> what i care about is to have a choice about what DEFINES the identity >>> of my values, not just an all-or-nothing situation. >>> >>> On May 26, 2016, at 5:18 PM, T.J. Usiyan via swift-evolution >>> <[email protected] <mailto:[email protected]>> wrote: >>> >>>> +1 to a `deriving` keyword >>>> >>>> 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/> >>>> 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 >>>> > >>>> > >>>> > _______________________________________________ >>>> > swift-evolution mailing list >>>> > [email protected] <mailto:[email protected]> >>>> > 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 >>>> >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> [email protected] <mailto:[email protected]> >>>> 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 >> >> >> >> _______________________________________________ >> 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
