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

Vladimir.S svabox at gmail.com
Sun May 29 08:41:18 CDT 2016


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?

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
>


More information about the swift-evolution mailing list