> 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

Reply via email to