[proposal] Treat (case .Foo = bar) as a Boolean expression


(Sam Dods) #1

I propose that (case .Foo = bar) should be treated as an expression with a Boolean value, so the result can be set to a variable or returned from a method.

Considering the following enumeration:

enum Bar {
  case foo(name: String)
  case notFoo
  case unknownFoo
}

Instead of having to do the following:

func isBarFoo(bar: Bar) -> Bool {
    if case .Foo = bar {
        return true
    }
    return false
}

We could simply do the following:

func isBarFoo(bar: Bar) -> Bool {
    return (case .Foo = bar)
}

We could also do things like this:

let isBarFoo = (case .Foo = bar)
XCTAssert(isBarFoo)

Of course, this change is only required for enumerations that don't have a raw value type (String, Int, etc).

It assumes the developer does not care what the associated value is, which could lead to issues. But this is already the case with the `if case ... return true/false` syntax. So it is not a degrading feature, just a more concise syntax for something that already exists.

Something to consider is whether `case let ...` could be treated as an expression in the same way. For example:

if (case let .Foo(name) = bar) && name == "Alan" {
  return true
}
return false

The above could be simplified to:

return (case let .Foo(name) = bar) && name == "Alan"

Due to the way AND-ed expression results are computed at runtime, the second expression would not be computed unless the first was true, so `name` must have a value. The compiler would know that when OR-ing expressions, the second expression is only computed if the first expression was false, so `name` definitely doesn't have a value:

return (case let .Foo(name) = bar) || name == "Alan"

I would expect a compiler error similar to `Variable declared in 'guard' condition is not usable in its body`.

What does everyone think of this? It would have no impact on existing code.

alternative, not proposing...

An alternative would be defaulting what equality means for enumerations, such that the `==` operator is automatically defined for enumerations in the following way:

func ==(lhs: Bar, rhs: Bar) -> Bool {
    if case rhs = lhs {
        return true
    }
    return false
}

However, I think that having a default implementation for enum is a bad idea, because it's adding default behaviour that the developer might not know about. And this could lead to a less experienced developer making a mistake when comparing two enum values with associated values. Developers that know the `if case ...` syntax are already expected to understand that they are ignoring the associated value and they can use `if case let ...` if they care about the associated value. So my proposal is in-line with an existing expectation.


(Vladimir) #2

Personally I feel like this construction "case .foo = bar" is alien in Swift.. Is it assignment ? No. Is it equality sign? No, we use '==' when checking for the equality. Is it clear what does this construction mean?
I'd like to see at least something like `bar is case .foo` or `bar in case .foo`

IMO I prefer to change this construction at all, and do not propose extension of its usage. For example, Bar.checkCase(.foo, for: bar) or bar.isCase(.foo)... And actually we need to improve enums in Swift., do we have proposals?

Why don't use in your example:
switch bar {
case .foo : return true
default : return false
}

Probably we should introduce `switch` that is expression, so we can use

let f = switch bar { case .foo : true; default : false }

But not this IMO ugly construction "case .foo = bar"

···

On 10.05.2016 14:33, Sam Dods via swift-evolution wrote:

I propose that *(case .Foo = bar)* should be treated as an expression with
a Boolean value, so the result can be set to a variable or returned from a
method.

Considering the following enumeration:

*enum Bar {*
* case foo(name: String)*
* case notFoo*
* case unknownFoo*
*}*

Instead of having to do the following:

*func isBarFoo(bar: Bar) -> Bool {*
* if case .Foo = bar {*
* return true*
* }*
* return false*
*}*

We could simply do the following:

*func isBarFoo(bar: Bar) -> Bool {*
* return (case .Foo = bar)*
*}*

We could also do things like this:

*let isBarFoo = (case .Foo = bar)*
*XCTAssert(isBarFoo)*

Of course, this change is only required for enumerations that don't have a
raw value type (String, Int, etc).

It assumes the developer does not care what the associated value is, which
could lead to issues. But this is already the case with the `*if case ...
return true/false*` syntax. So it is not a degrading feature, just a more
concise syntax for something that already exists.

Something to consider is whether `*case let ...*` could be treated as an
expression in the same way. For example:

*if (case let .Foo(name) = bar) && name == "Alan" {*
* return true*
*}*
*return false*

The above could be simplified to:

*return (case let .Foo(name) = bar) && name == "Alan"*

Due to the way AND-ed expression results are computed at runtime, the
second expression would not be computed unless the first was true, so
`*name*` must have a value. The compiler would know that when OR-ing
expressions, the second expression is only computed if the first expression
was false, so `*name*` definitely doesn't have a value:

*return (case let .Foo(name) = bar) || name == "Alan"*

I would expect a compiler error similar to `*Variable declared in 'guard'
condition is not usable in its body*`.

What does everyone think of this? It would have no impact on existing code.

*alternative, not proposing...*

An alternative would be defaulting what equality means for enumerations,
such that the `==` operator is automatically defined for enumerations in
the following way:

*func ==(lhs: Bar, rhs: Bar) -> Bool {*
* if case rhs = lhs {*
* return true*
* }*
* return false*
*}*

However, I think that having a default implementation for enum is a bad
idea, because it's adding default behaviour that the developer might not
know about. And this could lead to a less experienced developer making a
mistake when comparing two enum values with associated values. Developers
that know the `*if case ...*` syntax are already expected to understand
that they are ignoring the associated value and they can use `*if case let
...*` if they care about the associated value. So my proposal is in-line
with an existing expectation.

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


(Chris Lattner) #3

I propose that (case .Foo = bar) should be treated as an expression with a Boolean value, so the result can be set to a variable or returned from a method.

I agree that this is an important use case that Swift doesn’t serve well right now, but I don’t think this is the right way to go.

Considering the following enumeration:

enum Bar {
  case foo(name: String)
  case notFoo
  case unknownFoo
}

One of the things we’ve discussed in the past is that we could have enums automatically “synthesize” instance members for projecting cases as optional values or bools. For example, the above enum could be compiled into the equivalent of:

extension Bar {
   func getAsFoo() -> String? { … }
   var isNotFoo : Bool { … }
   var isUnknownFoo : Bool { … }
}

Then you could just use:

  if someBar.isUnknownFoo { … }
  if someBar.isFoo != nil { … }
  if let name = someBar. getAsFoo() {... }
  someBar. getAsFoo()?.doThing()

etc. There is a question of naming, and getting the details right, of course.

-Chris

···

On May 10, 2016, at 4:33 AM, Sam Dods via swift-evolution <swift-evolution@swift.org> wrote:


(Patrick Smith) #4

I like the idea of a boolean expression! I think it is cleaner; the required if statement is a pain.

For the second idea, I’ve wondered if there could be some way of unwrapping the associated values:

case .foo(?) = bar

It would produce an optional. This would let you do:

let isAlan = (case .foo(?) = bar) == “Alan”

There’s also force unwrap:

let name = (case .foo(!) = bar)

Even better would be a way of unwrapping multiple values of the same type:

enum Bar {
  case foo(name: String)
  case goo(kind: GooKind, name: String)

  var name: String {
    return case .foo(!), .goo(_, !) = bar
  }
}

···

On 10 May 2016, at 9:33 PM, Sam Dods via swift-evolution <swift-evolution@swift.org> wrote:

I propose that (case .Foo = bar) should be treated as an expression with a Boolean value, so the result can be set to a variable or returned from a method.

Considering the following enumeration:

enum Bar {
  case foo(name: String)
  case notFoo
  case unknownFoo
}

Instead of having to do the following:

func isBarFoo(bar: Bar) -> Bool {
    if case .Foo = bar {
        return true
    }
    return false
}

We could simply do the following:

func isBarFoo(bar: Bar) -> Bool {
    return (case .Foo = bar)
}

We could also do things like this:

let isBarFoo = (case .Foo = bar)
XCTAssert(isBarFoo)

Of course, this change is only required for enumerations that don't have a raw value type (String, Int, etc).

It assumes the developer does not care what the associated value is, which could lead to issues. But this is already the case with the `if case ... return true/false` syntax. So it is not a degrading feature, just a more concise syntax for something that already exists.

Something to consider is whether `case let ...` could be treated as an expression in the same way. For example:

if (case let .Foo(name) = bar) && name == "Alan" {
  return true
}
return false

The above could be simplified to:

return (case let .Foo(name) = bar) && name == "Alan"

Due to the way AND-ed expression results are computed at runtime, the second expression would not be computed unless the first was true, so `name` must have a value. The compiler would know that when OR-ing expressions, the second expression is only computed if the first expression was false, so `name` definitely doesn't have a value:

return (case let .Foo(name) = bar) || name == "Alan"

I would expect a compiler error similar to `Variable declared in 'guard' condition is not usable in its body`.

What does everyone think of this? It would have no impact on existing code.

alternative, not proposing...

An alternative would be defaulting what equality means for enumerations, such that the `==` operator is automatically defined for enumerations in the following way:

func ==(lhs: Bar, rhs: Bar) -> Bool {
    if case rhs = lhs {
        return true
    }
    return false
}

However, I think that having a default implementation for enum is a bad idea, because it's adding default behaviour that the developer might not know about. And this could lead to a less experienced developer making a mistake when comparing two enum values with associated values. Developers that know the `if case ...` syntax are already expected to understand that they are ignoring the associated value and they can use `if case let ...` if they care about the associated value. So my proposal is in-line with an existing expectation.

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


(Eduardo Mourey Lopez Ne) #5

Hi Chris

I do agree that it is incosistent that case works in an if but you can’t assign it to a bool

enum Bar {
    case foo(name: String)
    case notFoo
    case unknownFoo
}

var xx = Bar.foo(name: "Hello")

if case Bar.foo(let name) = xx where name == "Hello” { //This work ok
    print("Hola")
}
var bool = case Bar.foo(let name) = xx where name == “Hello” //But this doesn’t ???

//The other problem that I see is that the var “xx” seems to get lost int case by having it in the middle
//I think it will be better to have an alternative keyword for a single case, like ‘match’ that allows the variable
//to be at the begging

//This looks much better
var bool = xx match Bar.foo(let name) where name == “Hello”

Thanks

···

On May 10, 2016, at 10:31 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On May 10, 2016, at 4:33 AM, Sam Dods via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I propose that (case .Foo = bar) should be treated as an expression with a Boolean value, so the result can be set to a variable or returned from a method.

I agree that this is an important use case that Swift doesn’t serve well right now, but I don’t think this is the right way to go.

Considering the following enumeration:

enum Bar {
  case foo(name: String)
  case notFoo
  case unknownFoo
}

One of the things we’ve discussed in the past is that we could have enums automatically “synthesize” instance members for projecting cases as optional values or bools. For example, the above enum could be compiled into the equivalent of:

extension Bar {
   func getAsFoo() -> String? { … }
   var isNotFoo : Bool { … }
   var isUnknownFoo : Bool { … }
}

Then you could just use:

  if someBar.isUnknownFoo { … }
  if someBar.isFoo != nil { … }
  if let name = someBar. getAsFoo() {... }
  someBar. getAsFoo()?.doThing()

etc. There is a question of naming, and getting the details right, of course.

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


(Vladimir) #6

Btw, do we have now any proposal now for enums improvement in Swift3 ?

···

On 11.05.2016 6:31, Chris Lattner via swift-evolution wrote:

On May 10, 2016, at 4:33 AM, Sam Dods via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I propose that *(case .Foo = bar)* should be treated as an expression
with a Boolean value, so the result can be set to a variable or returned
from a method.

I agree that this is an important use case that Swift doesn’t serve well
right now, but I don’t think this is the right way to go.

Considering the following enumeration:

*enum Bar {*
* case foo(name: String)*
* case notFoo*
* case unknownFoo*
*}*

One of the things we’ve discussed in the past is that we could have enums
automatically “synthesize” instance members for projecting cases as
optional values or bools. For example, the above enum could be compiled
into the equivalent of:

extension Bar {
   func getAsFoo() -> String? { … }
   var isNotFoo : Bool { … }
   var isUnknownFoo : Bool { … }
}

Then you could just use:

  if someBar.isUnknownFoo { … }
  if someBar.isFoo != nil { … }
  if let name = someBar. getAsFoo() {... }
  someBar. getAsFoo()?.doThing()

etc. There is a question of naming, and getting the details right, of course.

-Chris

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


(Chris Lattner) #7

Hi Chris

I do agree that it is incosistent that case works in an if but you can’t assign it to a bool.

Not to me. I can understand why you would think about it that way, but the let in “if let” is not assignable to a bool either. Nor is #available.

-Chris

···

On May 10, 2016, at 8:46 PM, Eduardo Mourey Lopez Ne <edmourey@icloud.com> wrote:

enum Bar {
    case foo(name: String)
    case notFoo
    case unknownFoo
}

var xx = Bar.foo(name: "Hello")

if case Bar.foo(let name) = xx where name == "Hello” { //This work ok
    print("Hola")
}
var bool = case Bar.foo(let name) = xx where name == “Hello” //But this doesn’t ???

//The other problem that I see is that the var “xx” seems to get lost int case by having it in the middle
//I think it will be better to have an alternative keyword for a single case, like ‘match’ that allows the variable
//to be at the begging

//This looks much better
var bool = xx match Bar.foo(let name) where name == “Hello”

Thanks

On May 10, 2016, at 10:31 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On May 10, 2016, at 4:33 AM, Sam Dods via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I propose that (case .Foo = bar) should be treated as an expression with a Boolean value, so the result can be set to a variable or returned from a method.

I agree that this is an important use case that Swift doesn’t serve well right now, but I don’t think this is the right way to go.

Considering the following enumeration:

enum Bar {
  case foo(name: String)
  case notFoo
  case unknownFoo
}

One of the things we’ve discussed in the past is that we could have enums automatically “synthesize” instance members for projecting cases as optional values or bools. For example, the above enum could be compiled into the equivalent of:

extension Bar {
   func getAsFoo() -> String? { … }
   var isNotFoo : Bool { … }
   var isUnknownFoo : Bool { … }
}

Then you could just use:

  if someBar.isUnknownFoo { … }
  if someBar.isFoo != nil { … }
  if let name = someBar. getAsFoo() {... }
  someBar. getAsFoo()?.doThing()

etc. There is a question of naming, and getting the details right, of course.

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