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



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

Reply via email to