[swift-evolution] [Draft] Automatically deriving Equatable and Hashable for certain value types

Matthew Johnson matthew at anandabits.com
Sun May 29 09:20:24 CDT 2016



Sent from my iPad

> On May 29, 2016, at 8:41 AM, Vladimir.S via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Saw `class` in examples and want to clarify.. Are we discussing auto-deriving for *value types only*. Or for classes also?
> As I understand, there could be some additional questions/issues for this feature because of class inheritance. But probably this feature could be discussed for `final` classes also?

Classes usually have reference semantics.  In that case reference identity is the appropriate definition of equality.  Other kinds of equivalence tests are possible but should not have the name ==.

Immutable classes can have value semantics.  In that case a memberwise equality is as likely to be correct as it is for structs.  So it is possible we could allow synthesis to be requested for classes, it would just result in an error more frequently than with structs.


> 
>> On 27.05.2016 22:41, plx via swift-evolution wrote:
>> 
>>> On May 27, 2016, at 10:48 AM, Matthew Johnson <matthew at anandabits.com
>>> <mailto:matthew at anandabits.com>> wrote:
>>> 
>>>> 
>>>> On May 27, 2016, at 10:37 AM, plx via swift-evolution
>>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> 
>>>> 
>>>>> On May 26, 2016, at 1:00 PM, T.J. Usiyan via swift-evolution
>>>>> <swift-evolution at swift.org <mailto:swift-evolution at 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.
>>>> 
>>>> 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”.
>>> 
>>> I don’t see why this must necessarily be the case.  Annotations such as
>>> you describe below could be taken into account by `deriving`.  `deriving`
>>> is just a way to invoke the synthesis mechanism.
>> 
>> Different people are using it differently I think; I agree with you if it’s
>> just the name of the invocation, but I think at least some people are using
>> it as a shorthand for the “naive” implementation (all fields equatable =>
>> equatable).
>> 
>> That is, I meant "naive deriving” to refer to something like this (quoting
>> Patrick):
>> 
>>> It would fail if not all members were Equatable or Hashable. If it was
>>> automatic, you wouldn’t get any warning or sign at all. If you have to
>>> explicitly conform to the protocols, then your intention is clear, and if
>>> an automatic implementation cannot be made (because not all members were
>>> Equatable or Hashable), then you will get an error that you need to
>>> implement the protocol yourself like you do now (i.e. implement == and
>>> hashValue).
>> 
>> …but I could’ve been clearer!
>> 
>>> 
>>>> 
>>>> 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…).
>>> 
>>> If you don’t trust the compiler to optimize this well and therefore want
>>> control over order of comparisons you should probably just implement it
>>> manually.  As you note below, this is a convenience feature that needs to
>>> strike a fine balance.
>> 
>> I agree, but at the same time i think that scenarios like this:
>> 
>>  struct RevisionInfo {
>>    let contentID: NSUUID
>>    let revisionID: NSUUID
>>    let contentData: NSData
>>  }
>> 
>> …aren’t going to be all that uncommon in practice; I think a good “layered”
>> implementation of the derivation/synthesis logic would suffice (e.g. we
>> wouldn't *need* special-case handling for ordering, potentially…).
>> 
>>> 
>>> IMO there are two issues involved:
>>> 
>>> 1. How do we invoke the automatic synthesis.
>>> 2. How do we have some degree of control over the synthesis that happens.
>>> 
>>> `deriving` addresses issue 1 and says nothing about issue 2.
>> 
>> Agreed here; 2 is the interesting question. If you look at my initial
>> response in this thread I tried to suggest a “layered” approach:
>> 
>> Layer A: have some way of directly invoking the synthesis mechanism itself
>> (e.g. as a special-purpose macro-like construct); it should be powerful
>> enough to make `==` easy to write, but have some flexibility (implemented
>> or planned-for-future).
>> 
>> Layer B: add a way to synthesize `==` (etc.) via the construct from Layer A.
>> 
>> That’s my 2c on this topic; given it’s a Swift 4 topic at the very earliest
>> there’s a lot of time to figure it out.
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list