> On May 29, 2016, at 9:22 AM, Matthew Johnson <matt...@anandabits.com> wrote: > > > > Sent from my iPad > >> On May 29, 2016, at 9:12 AM, Vladimir.S via swift-evolution >> <swift-evolution@swift.org> 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 >>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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.
I strongly disagree; IMHO the members considered for hashing *should* be a subset of those considered for equality (and “be the same” is a safe default). As an example, I find I quite frequently wind up with types effectively like so: struct ArticleContent { let articleID: NSUUID let veryLongFullArticleContent: NSData // potentially huge, possibly slow } …for which, at least for the intended uses (and the intended data, etc.), hashing `articleID` is enough to get a well-behaved hash, and incorporating hashes of the other field(s) doesn’t have any apparent benefit. > >> >> >>> >>> 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 >>>> <laurent.mihalko...@gmail.com <mailto:laurent.mihalko...@gmail.com>> 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 >>>> <swift-evolution@swift.org <mailto: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/> >>>>> 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 >>>>>> >>>>>> >>>>>> _______________________________________________ >>>>>> swift-evolution mailing list >>>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>>> 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 >>>>> >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>>>> 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 >>> >>> >>> >>> _______________________________________________ >>> 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 > _______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution