Equality of enum with payload


(Jerome Duquennoy) #1

Hi everyone,

In swift, enums are much much more powerful than they used to be in c, c++ or objective-c.
One of their great feature is to be able to carry a payload.
Sadly, even a simple payload makes comparison much more complicated.

Consider this code :

···

----------------------------------------------------------------------
enum MyEnum {
  case One
  case Two
}

let const1 = MyEnum.One
let const1bis = MyEnum.One

const1 == const1bis // -> true
----------------------------------------------------------------------

It compiles fine, and is pretty concise.

Let’s add a payload to the enum
----------------------------------------------------------------------
enum MyEnumWithPayload {
  case One(payload: String)
  case Two(payload: String)
}

let simplePayload = "test"

let const1 = MyEnumWithPayload.One(payload: simplePayload)
let const1bis = MyEnumWithPayload.One(payload: simplePayload)

const1 == const1bis // error : Binary operator '==' cannot be applied to two MyEnumWithPayload operand
----------------------------------------------------------------------

This does not compile.
An equality operator is needed to handle that case :
----------------------------------------------------------------------
func ==(leftToken: MyEnumWithPayload, rightToken: MyEnumWithPayload) -> Bool {
  var result: Bool = false;
  
  switch(leftToken, rightToken) {
  case (let .One(leftPayload), let .One(rightPayload)):
    result = (leftPayload == rightPayload)
  case (let .Two(leftPayload), let .Two(rightPayload)):
    result = (leftPayload == rightPayload)
  default:
    result = false;
  }
  
  return result;
}
----------------------------------------------------------------------

I have two concerns with that :
- the added code is much bigger than the declaration of the enum, and the added code is not that simple to read. It is pretty simple to understand, but I find it complex to read.
- we have to use a default, otherwise, the compiler complains that the enum is not exhaustive (my compiler is configured to treat warnings as errors). So if we add a new value to the enum, we will not get a compiler warning reminding us that we should deal with equality for the new case.

It would be great to have a default equality operator for enums with a payload, as we have for the simple enum.
The logic of it would be : if the case is the same, and the payload(s) of each operand are equal, then the two operands are equal.

Of course, an equality operator has to be defined for the types of the payloads.

Jerome


(Marc Knaup) #2

It's a similar discussion with simple structs which could automatically be
equatable.
And for Hashable.
It's difficult to draw a line where that makes sense and where not.

In any case I'd prefer to not have recursive equality automatically.

   - It can easily lead to unexpected behavior when you forget to implement
   an own equality operator in order to remove variables from the equation
   which do not affect the equality.
   - You could add another variable to an existing enum (or struct) which
   is now automatically compared for equality without you noticing and which
   might be wrong.
   - You cannot opt out of that behavior.

But I agree that the current implementations of the equality operator for
enums are awful.
Maybe we can make the enum (or struct) conform to a special protocol which
allows it to automatically generate the equality operator (and even default
hashValue).

Something like this:

protocol DefaultEquatable: Equatable {}
protocol DefaultHashable: Hashable {}

enum MyEnumWithPayload: DefaultEquatable, DefaultHashable {
    case One(payload: String)
    case Two(payload: String)
}


(Slava Pestov) #3

This could be implemented by extending the derived conformance logic in Sema. Right now it only derives Equatable for enums without payload cases, but it would be relatively straightforward to synthesize the obvious Equatable conformance if all payloads are themselves Equatable, or tuples of Equatable types. You would then just write

extension MyEnumWithPayload : Equatable {}

Ditto for Hashable.

Slava

···

On Dec 11, 2015, at 8:15 AM, Marc Knaup via swift-evolution <swift-evolution@swift.org> wrote:

It's a similar discussion with simple structs which could automatically be equatable.
And for Hashable.
It's difficult to draw a line where that makes sense and where not.

In any case I'd prefer to not have recursive equality automatically.
It can easily lead to unexpected behavior when you forget to implement an own equality operator in order to remove variables from the equation which do not affect the equality.
You could add another variable to an existing enum (or struct) which is now automatically compared for equality without you noticing and which might be wrong.
You cannot opt out of that behavior.
But I agree that the current implementations of the equality operator for enums are awful.
Maybe we can make the enum (or struct) conform to a special protocol which allows it to automatically generate the equality operator (and even default hashValue).

Something like this:

protocol DefaultEquatable: Equatable {}
protocol DefaultHashable: Hashable {}

enum MyEnumWithPayload: DefaultEquatable, DefaultHashable {
    case One(payload: String)
    case Two(payload: String)
}

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jonathan Hise Kaldma) #4

+1

Would love to see this.

/Jonathan

···

11 dec. 2015 kl. 17:37 skrev Slava Pestov via swift-evolution <swift-evolution@swift.org>:

This could be implemented by extending the derived conformance logic in Sema. Right now it only derives Equatable for enums without payload cases, but it would be relatively straightforward to synthesize the obvious Equatable conformance if all payloads are themselves Equatable, or tuples of Equatable types. You would then just write

extension MyEnumWithPayload : Equatable {}

Ditto for Hashable.

Slava

On Dec 11, 2015, at 8:15 AM, Marc Knaup via swift-evolution <swift-evolution@swift.org> wrote:

It's a similar discussion with simple structs which could automatically be equatable.
And for Hashable.
It's difficult to draw a line where that makes sense and where not.

In any case I'd prefer to not have recursive equality automatically.
It can easily lead to unexpected behavior when you forget to implement an own equality operator in order to remove variables from the equation which do not affect the equality.
You could add another variable to an existing enum (or struct) which is now automatically compared for equality without you noticing and which might be wrong.
You cannot opt out of that behavior.
But I agree that the current implementations of the equality operator for enums are awful.
Maybe we can make the enum (or struct) conform to a special protocol which allows it to automatically generate the equality operator (and even default hashValue).

Something like this:

protocol DefaultEquatable: Equatable {}
protocol DefaultHashable: Hashable {}

enum MyEnumWithPayload: DefaultEquatable, DefaultHashable {
    case One(payload: String)
    case Two(payload: String)
}

_______________________________________________
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


(Marc Knaup) #5

Just stumbled upon the question whether enums/structs should automatically
conform to Equatable when the conformance is declared indirectly through
another protocol. This can be unexpected for the developer when declaring
conformance to a protocol of another developer.

protocol YourProtocol: Equatable {}

// implicitly declares conformance to Equatable - should equality operator
be generated or not?
enum MyEnum: YourProtocol {}

// better require explicit declaration to automatically create equality
operator and avoid unexpected behavior
enum MyEnum: Equatable, YourProtocol {}

// same for Hashable
enum MyEnum: Hashable {} // should raise a error since Equatable isn't
satisfied

// must also be explicit to automatically create equality operator
enum MyEnum: Equatable, Hashable {}

···

On Fri, Dec 11, 2015 at 6:09 PM, Jonathan Hise Kaldma via swift-evolution < swift-evolution@swift.org> wrote:

+1

Would love to see this.

/Jonathan

11 dec. 2015 kl. 17:37 skrev Slava Pestov via swift-evolution <
swift-evolution@swift.org>:

This could be implemented by extending the derived conformance logic in
Sema. Right now it only derives Equatable for enums without payload cases,
but it would be relatively straightforward to synthesize the obvious
Equatable conformance if all payloads are themselves Equatable, or tuples
of Equatable types. You would then just write

extension MyEnumWithPayload : Equatable {}

Ditto for Hashable.

Slava

On Dec 11, 2015, at 8:15 AM, Marc Knaup via swift-evolution < > swift-evolution@swift.org> wrote:

It's a similar discussion with simple structs which could automatically be
equatable.
And for Hashable.
It's difficult to draw a line where that makes sense and where not.

In any case I'd prefer to not have recursive equality automatically.

   - It can easily lead to unexpected behavior when you forget to
   implement an own equality operator in order to remove variables from the
   equation which do not affect the equality.
   - You could add another variable to an existing enum (or struct) which
   is now automatically compared for equality without you noticing and which
   might be wrong.
   - You cannot opt out of that behavior.

But I agree that the current implementations of the equality operator for
enums are awful.
Maybe we can make the enum (or struct) conform to a special protocol which
allows it to automatically generate the equality operator (and even default
hashValue).

Something like this:

protocol DefaultEquatable: Equatable {}
protocol DefaultHashable: Hashable {}

enum MyEnumWithPayload: DefaultEquatable, DefaultHashable {
    case One(payload: String)
    case Two(payload: String)
}

_______________________________________________
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


(Jerome Duquennoy) #6

So to sum up all that, there would be three possibilities :

1 - have an equality operator automatically generated for enum with payloads
That would work only if all types used in the payload have equality operators themselves.
If a custom equality operator is defined, it should not be overridden by a generated version.
That might also end up allowing equality operator for tuples. I have not checked in the code if a payload with multiple variables is considered as a tuple, if so, it would certainly be more coherent to have the same behaviour for both.
The hashValue method should have a similar behaviour (that is, being automatically generated), as there are coherency rules to enforce between this method and the equality operator (same hasValue for equal values)

2 - have a generated equality operator generated if the enum conforms to the Equatable protocol
That would be opt-in, and thus provide more control / more visibility to the developer.
That would work only if all types used in the payload have equality operator themselves.
That would not work for tuples, as a tuple cannot conform to a protocol.
The hashValue method should have a similar behaviour, that is being generated automatically only if the enum conforms to the Hashable protocol.

3 - don’t do anything automatic
IE reject that proposal :-).
The developer is in control, but he (or she) might have to write equality method even for standard behaviour.

What do you think gentlemen, can you vote for one one of those ?
If solution 1 or 2 gets most votes, i’ll write an official proposal and open a pull request.

Thanks !

Jérôme

···

On 11 Dec 2015, at 22:03, Marc Knaup via swift-evolution <swift-evolution@swift.org> wrote:

Just stumbled upon the question whether enums/structs should automatically conform to Equatable when the conformance is declared indirectly through another protocol. This can be unexpected for the developer when declaring conformance to a protocol of another developer.

protocol YourProtocol: Equatable {}

// implicitly declares conformance to Equatable - should equality operator be generated or not?
enum MyEnum: YourProtocol {}

// better require explicit declaration to automatically create equality operator and avoid unexpected behavior
enum MyEnum: Equatable, YourProtocol {}

// same for Hashable
enum MyEnum: Hashable {} // should raise a error since Equatable isn't satisfied

// must also be explicit to automatically create equality operator
enum MyEnum: Equatable, Hashable {}

On Fri, Dec 11, 2015 at 6:09 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1

Would love to see this.

/Jonathan

11 dec. 2015 kl. 17:37 skrev Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

This could be implemented by extending the derived conformance logic in Sema. Right now it only derives Equatable for enums without payload cases, but it would be relatively straightforward to synthesize the obvious Equatable conformance if all payloads are themselves Equatable, or tuples of Equatable types. You would then just write

extension MyEnumWithPayload : Equatable {}

Ditto for Hashable.

Slava

On Dec 11, 2015, at 8:15 AM, Marc Knaup via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It's a similar discussion with simple structs which could automatically be equatable.
And for Hashable.
It's difficult to draw a line where that makes sense and where not.

In any case I'd prefer to not have recursive equality automatically.
It can easily lead to unexpected behavior when you forget to implement an own equality operator in order to remove variables from the equation which do not affect the equality.
You could add another variable to an existing enum (or struct) which is now automatically compared for equality without you noticing and which might be wrong.
You cannot opt out of that behavior.
But I agree that the current implementations of the equality operator for enums are awful.
Maybe we can make the enum (or struct) conform to a special protocol which allows it to automatically generate the equality operator (and even default hashValue).

Something like this:

protocol DefaultEquatable: Equatable {}
protocol DefaultHashable: Hashable {}

enum MyEnumWithPayload: DefaultEquatable, DefaultHashable {
    case One(payload: String)
    case Two(payload: String)
}

_______________________________________________
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


(Slava Pestov) #7

So to sum up all that, there would be three possibilities :

1 - have an equality operator automatically generated for enum with payloads
That would work only if all types used in the payload have equality operators themselves.
If a custom equality operator is defined, it should not be overridden by a generated version.
That might also end up allowing equality operator for tuples. I have not checked in the code if a payload with multiple variables is considered as a tuple, if so, it would certainly be more coherent to have the same behaviour for both.
The hashValue method should have a similar behaviour (that is, being automatically generated), as there are coherency rules to enforce between this method and the equality operator (same hasValue for equal values)

2 - have a generated equality operator generated if the enum conforms to the Equatable protocol

This would be my preferred approach.

That would be opt-in, and thus provide more control / more visibility to the developer.
That would work only if all types used in the payload have equality operator themselves.
That would not work for tuples, as a tuple cannot conform to a protocol.

The logic to derive the Equatable conformance for the enum type could special-case enum payloads by comparing them element-wise.

···

On Dec 15, 2015, at 3:36 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

The hashValue method should have a similar behaviour, that is being generated automatically only if the enum conforms to the Hashable protocol.

3 - don’t do anything automatic
IE reject that proposal :-).
The developer is in control, but he (or she) might have to write equality method even for standard behaviour.

What do you think gentlemen, can you vote for one one of those ?
If solution 1 or 2 gets most votes, i’ll write an official proposal and open a pull request.

Thanks !

Jérôme

On 11 Dec 2015, at 22:03, Marc Knaup via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Just stumbled upon the question whether enums/structs should automatically conform to Equatable when the conformance is declared indirectly through another protocol. This can be unexpected for the developer when declaring conformance to a protocol of another developer.

protocol YourProtocol: Equatable {}

// implicitly declares conformance to Equatable - should equality operator be generated or not?
enum MyEnum: YourProtocol {}

// better require explicit declaration to automatically create equality operator and avoid unexpected behavior
enum MyEnum: Equatable, YourProtocol {}

// same for Hashable
enum MyEnum: Hashable {} // should raise a error since Equatable isn't satisfied

// must also be explicit to automatically create equality operator
enum MyEnum: Equatable, Hashable {}

On Fri, Dec 11, 2015 at 6:09 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1

Would love to see this.

/Jonathan

11 dec. 2015 kl. 17:37 skrev Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

This could be implemented by extending the derived conformance logic in Sema. Right now it only derives Equatable for enums without payload cases, but it would be relatively straightforward to synthesize the obvious Equatable conformance if all payloads are themselves Equatable, or tuples of Equatable types. You would then just write

extension MyEnumWithPayload : Equatable {}

Ditto for Hashable.

Slava

On Dec 11, 2015, at 8:15 AM, Marc Knaup via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It's a similar discussion with simple structs which could automatically be equatable.
And for Hashable.
It's difficult to draw a line where that makes sense and where not.

In any case I'd prefer to not have recursive equality automatically.
It can easily lead to unexpected behavior when you forget to implement an own equality operator in order to remove variables from the equation which do not affect the equality.
You could add another variable to an existing enum (or struct) which is now automatically compared for equality without you noticing and which might be wrong.
You cannot opt out of that behavior.
But I agree that the current implementations of the equality operator for enums are awful.
Maybe we can make the enum (or struct) conform to a special protocol which allows it to automatically generate the equality operator (and even default hashValue).

Something like this:

protocol DefaultEquatable: Equatable {}
protocol DefaultHashable: Hashable {}

enum MyEnumWithPayload: DefaultEquatable, DefaultHashable {
    case One(payload: String)
    case Two(payload: String)
}

_______________________________________________
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