> On May 29, 2016, at 9:22 AM, Matthew Johnson <[email protected]> wrote:
>
>
>
> 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.
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
>>>> <[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