Testing enum cases with associated values


(Andy Chou) #1

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
    case success
    case alert(Alert)
    case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
    case success
    case alert(Alert)
    case reauthenticate

    var isSuccess: Bool {
        if case .success = self {
            return true
        } else {
            return false
        }
    }

    var isReauthenticate: Bool {
        if case .reauthenticate = self {
            return true
        } else {
            return false
        }
    }

    var isAlert: Bool {
        if case .alert(_) = self {
            return true
        } else {
            return false
        }
    }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy


(Robert Widmann) #2

Automatic “equatability” of aggregates that contain equatable members has been discussed on this list quite a few times. (I think I had a branch at one point that was exploring this kind of deriving mechanism… It seems to be lost to the sands of time). Everybody seems to agree that it’s a worthwhile feature, but there needs to be thought put into how it is exposed to the end user. e.g. Should we continue with silently implementing these protocols if we can, or should there be some kind of annotation to tell the compiler to only synthesize what the user wants?

···

On Jan 17, 2017, at 7:15 PM, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
   case success
   case alert(Alert)
   case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
   case success
   case alert(Alert)
   case reauthenticate

   var isSuccess: Bool {
       if case .success = self {
           return true
       } else {
           return false
       }
   }

   var isReauthenticate: Bool {
       if case .reauthenticate = self {
           return true
       } else {
           return false
       }
   }

   var isAlert: Bool {
       if case .alert(_) = self {
           return true
       } else {
           return false
       }
   }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy

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


(Rien) #3

A guy named Matthias recently commented this on my blog:

func == (left: Enum3, right: Enum3) -> Bool {
switch (left, right) {
case (.ONE, .ONE):
return true
case (.TWO(let str1), .TWO(let str2)):
return str1 == str2
default:
return false
}
}

http://swiftrien.blogspot.nl/2015/05/swift-enum-compare-design-pattern.html

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Swiftrien
Project: http://swiftfire.nl

···

On 18 Jan 2017, at 01:15, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
   case success
   case alert(Alert)
   case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
   case success
   case alert(Alert)
   case reauthenticate

   var isSuccess: Bool {
       if case .success = self {
           return true
       } else {
           return false
       }
   }

   var isReauthenticate: Bool {
       if case .reauthenticate = self {
           return true
       } else {
           return false
       }
   }

   var isAlert: Bool {
       if case .alert(_) = self {
           return true
       } else {
           return false
       }
   }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy

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


(Andy Chou) #4

Yes, here's a reference to the conditional conformance proposal which was accepted:

https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md

But as I mention in my post, there are cases it doesn't handle. Specifically, when the associated types for an enum aren't equatable themselves, it's still possible to define equality on the enum cases without associated values.

Andy

···

On Jan 17, 2017, at 5:45 PM, Robert Widmann <devteam.codafi@gmail.com> wrote:

Automatic “equatability” of aggregates that contain equatable members has been discussed on this list quite a few times. (I think I had a branch at one point that was exploring this kind of deriving mechanism… It seems to be lost to the sands of time). Everybody seems to agree that it’s a worthwhile feature, but there needs to be thought put into how it is exposed to the end user. e.g. Should we continue with silently implementing these protocols if we can, or should there be some kind of annotation to tell the compiler to only synthesize what the user wants?

On Jan 17, 2017, at 7:15 PM, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate

  var isSuccess: Bool {
      if case .success = self {
          return true
      } else {
          return false
      }
  }

  var isReauthenticate: Bool {
      if case .reauthenticate = self {
          return true
      } else {
          return false
      }
  }

  var isAlert: Bool {
      if case .alert(_) = self {
          return true
      } else {
          return false
      }
  }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy

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


(Robert Widmann) #5

This is distinct from conditional conformances (we could add this feature today).

~Robert Widmann

···

On Jan 17, 2017, at 8:59 PM, Andy Chou <acchou2@gmail.com> wrote:

Yes, here's a reference to the conditional conformance proposal which was accepted:

https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md

But as I mention in my post, there are cases it doesn't handle. Specifically, when the associated types for an enum aren't equatable themselves, it's still possible to define equality on the enum cases without associated values.

Andy

On Jan 17, 2017, at 5:45 PM, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

Automatic “equatability” of aggregates that contain equatable members has been discussed on this list quite a few times. (I think I had a branch at one point that was exploring this kind of deriving mechanism… It seems to be lost to the sands of time). Everybody seems to agree that it’s a worthwhile feature, but there needs to be thought put into how it is exposed to the end user. e.g. Should we continue with silently implementing these protocols if we can, or should there be some kind of annotation to tell the compiler to only synthesize what the user wants?

On Jan 17, 2017, at 7:15 PM, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate

  var isSuccess: Bool {
      if case .success = self {
          return true
      } else {
          return false
      }
  }

  var isReauthenticate: Bool {
      if case .reauthenticate = self {
          return true
      } else {
          return false
      }
  }

  var isAlert: Bool {
      if case .alert(_) = self {
          return true
      } else {
          return false
      }
  }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy

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


(Tony Allevato) #6

Conditional conformances doesn't solve enum equality though, because there
likely won't be a way to utter "enum Foo : Equatable where <all types
across all associated value payloads are Equatable>" with the generics
syntax that's available, and conformance alone wouldn't be able to derive
the implementation. It'll require some work on the compiler's part to
generate the right implementation—I mentioned a draft proposal I wrote a
while back for auto-equality of enums and structs where all the components
are equatable <
https://gist.github.com/allevato/2fd10290bfa84accfbe977d8ac07daad> in
another thread, but as Robert mentioned, the missing piece is how users opt
in/out of it.

If you just want to check the case of an enum in a test, what about this?

    enum Foo {
      case bar
      case baz(Int)
    }
    let foo = Foo.baz(5)
    guard case .baz = foo else { XCTFail("expected baz"); return }

The "return" being required isn't ideal because XCTFail doesn't return
Never, but other than that it's not *horrible*. You can do whatever pattern
matching you need to use or ignore associated values as part of your
comparison.

···

On Tue, Jan 17, 2017 at 5:59 PM Andy Chou via swift-evolution < swift-evolution@swift.org> wrote:

Yes, here's a reference to the conditional conformance proposal which was
accepted:

https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md

But as I mention in my post, there are cases it doesn't handle.
Specifically, when the associated types for an enum aren't equatable
themselves, it's still possible to define equality on the enum cases
without associated values.

Andy

On Jan 17, 2017, at 5:45 PM, Robert Widmann <devteam.codafi@gmail.com> > wrote:

Automatic “equatability” of aggregates that contain equatable members has
been discussed on this list quite a few times. (I think I had a branch at
one point that was exploring this kind of deriving mechanism… It seems to
be lost to the sands of time). Everybody seems to agree that it’s a
worthwhile feature, but there needs to be thought put into how it is
exposed to the end user. e.g. Should we continue with silently
implementing these protocols if we can, or should there be some kind of
annotation to tell the compiler to only synthesize what the user wants?

On Jan 17, 2017, at 7:15 PM, Andy Chou via swift-evolution < > swift-evolution@swift.org> wrote:

Enums with associated values can be very useful in Swift, but once you add
associated values you lose some properties, especially equality:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern
match syntax:

if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases
where we want a Bool, such as assert(). There are also common situations
with lists and libraries like RxSwift where a filtering function uses a
Bool valued closure. In these situations the best we can do is write
functions like:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate

  var isSuccess: Bool {
      if case .success = self {
          return true
      } else {
          return false
      }
  }

  var isReauthenticate: Bool {
      if case .reauthenticate = self {
          return true
      } else {
          return false
      }
  }

  var isAlert: Bool {
      if case .alert(_) = self {
          return true
      } else {
          return false
      }
  }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of
this issue, but not completely. If Alert isn’t Equatable, it is still
useful to test whether the result is .success. For example:

assert(response == .success)

This is perfectly intelligible and I would argue that equality should be
defined for enums with associated values omitted:

assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the
enum case is the same.

Andy

_______________________________________________
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


(Andy Chou) #7

This is nice, but it doesn’t solve the issue at hand because there is only one enum value. I would like to have something like:

  if value == .TWO { … }

I can’t use this == operator for this because I’d have to create:

  if value == .TWO(str) { … }

But I don’t have a specific str, and I want the condition to be true no matter what the value of str is.

Andy

···

On Jan 17, 2017, at 10:53 PM, Rien <Rien@Balancingrock.nl> wrote:

A guy named Matthias recently commented this on my blog:

func == (left: Enum3, right: Enum3) -> Bool {
switch (left, right) {
case (.ONE, .ONE):
return true
case (.TWO(let str1), .TWO(let str2)):
return str1 == str2
default:
return false
}
}

http://swiftrien.blogspot.nl/2015/05/swift-enum-compare-design-pattern.html

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Swiftrien
Project: http://swiftfire.nl

On 18 Jan 2017, at 01:15, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate

  var isSuccess: Bool {
      if case .success = self {
          return true
      } else {
          return false
      }
  }

  var isReauthenticate: Bool {
      if case .reauthenticate = self {
          return true
      } else {
          return false
      }
  }

  var isAlert: Bool {
      if case .alert(_) = self {
          return true
      } else {
          return false
      }
  }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy

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


(Andy Chou) #8

I see your point about conformances. In my example, AuthenticationResponse isn't a generic type, so the conformances spec won't apply.

I'll go out on a limb and say that 80%+ of the use cases for equality will be to distinguish enum cases, not the associated values.

I do like your proposal though. In the thread talking about it someone mentioned a 'derived' keyword to specify conformance and derivation simultaneously. I like the idea of being able to say:

  enum AuthenticationResponse: derived Equatable { ... }

Still, even with derived conformance, it can still be very useful to be able to test against specific enum cases when the associated values aren't Equatable. Maybe this example would make it clearer:

enum OpaqueResponse {
	case success(OpaqueResult)
	case failure
}

If OpaqueResult is from a library module and its implementation uses private variables, it may not be easy to make it conform to Equatable. Yet, it would be nice to be able to say:

let result: OpaqueResponse = request()
if request == .failure  { ... }

A more realistic example comes from the world of Rx, where the original issue I have came from:

let result: Observable<OpaqueResponse> = request()

result.filter { $0 == .failure } ...
result.filter { $0 == .success } ...

// The best we can do today looks more like:
result.filter { if case .failure = $0 { return true} else { return false } }
result.filter { $0.isFailure }    // but we have to define isFailure ourselves

// There's a similar issue with assertions
if case .failure = result { assert(false) }
assert({ if case .failure = result { return true } else { return false } }())

I agree it's less of an issue with test cases. The issue arises when we want a Bool valued expression...

Andy

···

On Jan 17, 2017, at 6:38 PM, Tony Allevato <tony.allevato@gmail.com> wrote:

Conditional conformances doesn't solve enum equality though, because there likely won't be a way to utter "enum Foo : Equatable where <all types across all associated value payloads are Equatable>" with the generics syntax that's available, and conformance alone wouldn't be able to derive the implementation. It'll require some work on the compiler's part to generate the right implementation—I mentioned a draft proposal I wrote a while back for auto-equality of enums and structs where all the components are equatable <https://gist.github.com/allevato/2fd10290bfa84accfbe977d8ac07daad> in another thread, but as Robert mentioned, the missing piece is how users opt in/out of it.

If you just want to check the case of an enum in a test, what about this?

    enum Foo {
      case bar
      case baz(Int)
    }
    let foo = Foo.baz(5)
    guard case .baz = foo else { XCTFail("expected baz"); return }

The "return" being required isn't ideal because XCTFail doesn't return Never, but other than that it's not *horrible*. You can do whatever pattern matching you need to use or ignore associated values as part of your comparison.

On Tue, Jan 17, 2017 at 5:59 PM Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Yes, here's a reference to the conditional conformance proposal which was accepted:

https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md

But as I mention in my post, there are cases it doesn't handle. Specifically, when the associated types for an enum aren't equatable themselves, it's still possible to define equality on the enum cases without associated values.

Andy

On Jan 17, 2017, at 5:45 PM, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

Automatic “equatability” of aggregates that contain equatable members has been discussed on this list quite a few times. (I think I had a branch at one point that was exploring this kind of deriving mechanism… It seems to be lost to the sands of time). Everybody seems to agree that it’s a worthwhile feature, but there needs to be thought put into how it is exposed to the end user. e.g. Should we continue with silently implementing these protocols if we can, or should there be some kind of annotation to tell the compiler to only synthesize what the user wants?

On Jan 17, 2017, at 7:15 PM, Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate

  var isSuccess: Bool {
      if case .success = self {
          return true
      } else {
          return false
      }
  }

  var isReauthenticate: Bool {
      if case .reauthenticate = self {
          return true
      } else {
          return false
      }
  }

  var isAlert: Bool {
      if case .alert(_) = self {
          return true
      } else {
          return false
      }
  }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy

_______________________________________________
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


(Andy Chou) #9

if request == .failure { ... }

Typo. It should read

if result == .failure { ... }


(Anton Zhilin) #10

AFAICS, Andy needs not default implementations of Equatable, but
cases-as-optional-properties—this topic has also been discussed on the list.

enum Result {
    case success(Int)
    case failure(String)
}

let r: Result = foo()

let x: Int? = r.success
let y: String? = r.failure

assert(r.success == Optional(42))
assert(r.failure == nil)


(Pierre Monod-Broca) #11

The last time I was in this situation, I resolved it by having a shadow enum with the same cases but without any associated value.

I also created shadow enums for error enums. The shadow enum wouldn't have any associated value, and Int as RawValue, and I would also use it as an error code when converting to NSError.

But it's not really fun to maintain.

Pierre

···

Le 18 janv. 2017 à 05:51, Andy Chou via swift-evolution <swift-evolution@swift.org> a écrit :

I see your point about conformances. In my example, AuthenticationResponse isn't a generic type, so the conformances spec won't apply.

I'll go out on a limb and say that 80%+ of the use cases for equality will be to distinguish enum cases, not the associated values.

I do like your proposal though. In the thread talking about it someone mentioned a 'derived' keyword to specify conformance and derivation simultaneously. I like the idea of being able to say:

  enum AuthenticationResponse: derived Equatable { ... }

Still, even with derived conformance, it can still be very useful to be able to test against specific enum cases when the associated values aren't Equatable. Maybe this example would make it clearer:

enum OpaqueResponse {
	case success(OpaqueResult)
	case failure
}

If OpaqueResult is from a library module and its implementation uses private variables, it may not be easy to make it conform to Equatable. Yet, it would be nice to be able to say:

let result: OpaqueResponse = request()
if request == .failure  { ... }

A more realistic example comes from the world of Rx, where the original issue I have came from:

let result: Observable<OpaqueResponse> = request()

result.filter { $0 == .failure } ...
result.filter { $0 == .success } ...

// The best we can do today looks more like:
result.filter { if case .failure = $0 { return true} else { return false } }
result.filter { $0.isFailure }    // but we have to define isFailure ourselves

// There's a similar issue with assertions
if case .failure = result { assert(false) }
assert({ if case .failure = result { return true } else { return false } }())

I agree it's less of an issue with test cases. The issue arises when we want a Bool valued expression...

Andy

On Jan 17, 2017, at 6:38 PM, Tony Allevato <tony.allevato@gmail.com> wrote:

Conditional conformances doesn't solve enum equality though, because there likely won't be a way to utter "enum Foo : Equatable where <all types across all associated value payloads are Equatable>" with the generics syntax that's available, and conformance alone wouldn't be able to derive the implementation. It'll require some work on the compiler's part to generate the right implementation—I mentioned a draft proposal I wrote a while back for auto-equality of enums and structs where all the components are equatable <https://gist.github.com/allevato/2fd10290bfa84accfbe977d8ac07daad> in another thread, but as Robert mentioned, the missing piece is how users opt in/out of it.

If you just want to check the case of an enum in a test, what about this?

    enum Foo {
      case bar
      case baz(Int)
    }
    let foo = Foo.baz(5)
    guard case .baz = foo else { XCTFail("expected baz"); return }

The "return" being required isn't ideal because XCTFail doesn't return Never, but other than that it's not *horrible*. You can do whatever pattern matching you need to use or ignore associated values as part of your comparison.

On Tue, Jan 17, 2017 at 5:59 PM Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:
Yes, here's a reference to the conditional conformance proposal which was accepted:

https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md

But as I mention in my post, there are cases it doesn't handle. Specifically, when the associated types for an enum aren't equatable themselves, it's still possible to define equality on the enum cases without associated values.

Andy

On Jan 17, 2017, at 5:45 PM, Robert Widmann <devteam.codafi@gmail.com> wrote:

Automatic “equatability” of aggregates that contain equatable members has been discussed on this list quite a few times. (I think I had a branch at one point that was exploring this kind of deriving mechanism… It seems to be lost to the sands of time). Everybody seems to agree that it’s a worthwhile feature, but there needs to be thought put into how it is exposed to the end user. e.g. Should we continue with silently implementing these protocols if we can, or should there be some kind of annotation to tell the compiler to only synthesize what the user wants?

On Jan 17, 2017, at 7:15 PM, Andy Chou via swift-evolution <swift-evolution@swift.org> wrote:

Enums with associated values can be very useful in Swift, but once you add associated values you lose some properties, especially equality:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate
}

Testing for a specific case requires a switch statement or the if pattern match syntax:

  if case .success = response { … }

But while this works well for control flow, it doesn’t work well for cases where we want a Bool, such as assert(). There are also common situations with lists and libraries like RxSwift where a filtering function uses a Bool valued closure. In these situations the best we can do is write functions like:

enum AuthenticationResponse {
  case success
  case alert(Alert)
  case reauthenticate

  var isSuccess: Bool {
      if case .success = self {
          return true
      } else {
          return false
      }
  }

  var isReauthenticate: Bool {
      if case .reauthenticate = self {
          return true
      } else {
          return false
      }
  }

  var isAlert: Bool {
      if case .alert(_) = self {
          return true
      } else {
          return false
      }
  }
}

Any suggestions better than writing out each of these functions explicitly?

The conditional conformances proposal coming in Swift 4 solves some of this issue, but not completely. If Alert isn’t Equatable, it is still useful to test whether the result is .success. For example:

  assert(response == .success)

This is perfectly intelligible and I would argue that equality should be defined for enums with associated values omitted:

  assert(response == .alert)

Here we are ignoring the associated values, and merely checking if the enum case is the same.

Andy

_______________________________________________
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


(Andy Chou) #12

That’s an interesting proposal. Here’s a link for reference: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160926/027287.html

The one thing is the proposed syntax doesn’t handle enum cases without payloads. I think that could be handled by an optional Void, so this comment in the original proposal:

Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

Would be changed to allow for empty payloads turning into Void?. For example:

enum Result {
    case success(Int)
    case failure
}

let r: Result = foo()

let x: Int? = r.success
let y: Void? = r.failure

assert(r.success == Optional(42))
assert(r.failure == nil)

I think it’s a reasonable compromise, though I still think it’s a bit awkward for the common case. Looks like this is being postponed for now, so we’ll have to live with the alternatives.

Andy

···

On Jan 18, 2017, at 12:20 AM, Anton Zhilin <antonyzhilin@gmail.com> wrote:

AFAICS, Andy needs not default implementations of Equatable, but cases-as-optional-properties—this topic has also been discussed on the list.

enum Result {
    case success(Int)
    case failure(String)
}

let r: Result = foo()

let x: Int? = r.success
let y: String? = r.failure

assert(r.success == Optional(42))
assert(r.failure == nil)


(Tony Allevato) #13

FWIW, I'm not convinced that making enum values look like structs with
optional properties for the union of all their cases is a good idea. It's
certainly not something I would want added to all of my enums by default,
and it feels IMO like it's just an awkward hack around the fact that
pattern matching is a bit verbose in some scenarios because Swift doesn't
provide a case-expression to let you test/bind as part of a larger
expression.

···

On Wed, Jan 18, 2017 at 10:08 AM Andy Chou via swift-evolution < swift-evolution@swift.org> wrote:

That’s an interesting proposal. Here’s a link for reference:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160926/027287.html

The one thing is the proposed syntax doesn’t handle enum cases without
payloads. I think that could be handled by an optional Void, so this
comment in the original proposal:

> Only enum cases with a payload can be used with this syntax (it would
make no sens for cases without a payload).

Would be changed to allow for empty payloads turning into Void?. For
example:

enum Result {
    case success(Int)
    case failure
}

let r: Result = foo()

let x: Int? = r.success
let y: Void? = r.failure

assert(r.success == Optional(42))
assert(r.failure == nil)

I think it’s a reasonable compromise, though I still think it’s a bit
awkward for the common case. Looks like this is being postponed for now, so
we’ll have to live with the alternatives.

Andy

On Jan 18, 2017, at 12:20 AM, Anton Zhilin <antonyzhilin@gmail.com> wrote:

AFAICS, Andy needs not default implementations of Equatable, but
cases-as-optional-properties—this topic has also been discussed on the list.

enum Result {
    case success(Int)
    case failure(String)
}

let r: Result = foo()

let x: Int? = r.success
let y: String? = r.failure

assert(r.success == Optional(42))
assert(r.failure == nil)

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


(Andy Chou) #14

I agree it’s overkill for what I’m really looking for, which is a simple way to get a Bool result from testing an enum against a specific case, without looking at the associated values.

Andy

···

On Jan 18, 2017, at 10:15 AM, Tony Allevato <tony.allevato@gmail.com> wrote:

FWIW, I'm not convinced that making enum values look like structs with optional properties for the union of all their cases is a good idea. It's certainly not something I would want added to all of my enums by default, and it feels IMO like it's just an awkward hack around the fact that pattern matching is a bit verbose in some scenarios because Swift doesn't provide a case-expression to let you test/bind as part of a larger expression.

On Wed, Jan 18, 2017 at 10:08 AM Andy Chou via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
That’s an interesting proposal. Here’s a link for reference: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160926/027287.html

The one thing is the proposed syntax doesn’t handle enum cases without payloads. I think that could be handled by an optional Void, so this comment in the original proposal:

> Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

Would be changed to allow for empty payloads turning into Void?. For example:

enum Result {
    case success(Int)
    case failure
}

let r: Result = foo()

let x: Int? = r.success
let y: Void? = r.failure

assert(r.success == Optional(42))
assert(r.failure == nil)

I think it’s a reasonable compromise, though I still think it’s a bit awkward for the common case. Looks like this is being postponed for now, so we’ll have to live with the alternatives.

Andy

On Jan 18, 2017, at 12:20 AM, Anton Zhilin <antonyzhilin@gmail.com <mailto:antonyzhilin@gmail.com>> wrote:

AFAICS, Andy needs not default implementations of Equatable, but cases-as-optional-properties—this topic has also been discussed on the list.

enum Result {
    case success(Int)
    case failure(String)
}

let r: Result = foo()

let x: Int? = r.success
let y: String? = r.failure

assert(r.success == Optional(42))
assert(r.failure == nil)

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