If case logical operator integration

Hello!

Have you ever considered the convenience of integrating the if case syntax with && or || operators?
This enhancement could prove particularly handy for enums with multiple cases, especially in scenarios where employing a switch statement feels excessive.

For instance:

enum MyEnum {
	case first(data: String)
	case second(data: String)
	case third(data: [String])
	case fourth
	case fifth
	case sixth
}
let first = MyEnum.first(data: "Gabriel")
let second = MyEnum.second(data: "Aurora")
var third = MyEnum.third(data: ["Gabriel", "Aurora", "Marcela"])

if case .first(let data) = first || case .second(let data) = second {
    print(data)
}

In this envisioned implementation, I imagine that utilizing || would require all cases to possess parameters of the same type (as demonstrated above) or no parameters at all. Conversely, with &&, compatibility would extend to any combination of parameters.

Consider:

if case .first(let firstData) = first && case .second(let secondData) = second && case .third(let thirdData) = third {
    print(firstData)
    print(secondData)
    print(thirdData)
}

What do you think?

This can already be done, it's just spelled with , rather than &&.

The || though is not easy. For the type (I changed your type a bit to add more use cases):

enum MyEnum {
    case first(string: String)
    case second(string: String)
    case third(strings: [String], someInt: Int)
    case fourth(Int, Double)
    case fifth
}

My long standing wish is to have an autogenerated code:

extension MyEnum: SomeMagicProtocol {
    // Autogenerated conformances
    var first: String! {
        get {
            if case .first(let string) = self { string } else { nil }
        }
        set {
            self = .first(string: newValue)
        }
    }
    var second: String! {
        get {
            if case .second(let string) = self { string } else { nil }
        }
        set {
            self = .second(string: newValue)
        }
    }
    var third: (strings: [String], someInt: Int)! {
        get {
            if case let .third(strings, someInt) = self { (strings: strings, someInt: someInt) } else { nil }
        }
        set {
            self = .third(strings: newValue.strings, someInt: newValue.someInt)
        }
    }
    var fourth: (Int, Double)! {
        get {
            if case let .fourth(x, y) = self { (x, y) } else { nil }
        }
        set {
            self = .fourth(newValue.0, newValue.1)
        }
    }
    var fifth: MyEnum! {
        get {
            if case .fifth = self { .fifth } else { nil }
        }
        set {
            self = .fifth
        }
    }
}

which could happen when my type marked with SomeMagicProtocol.

protocol SomeMagicProtocol {}

Here's how your example could look like then:

let a = MyEnum.first(string: "Gabriel")
let b = MyEnum.second(string: "Aurora")

if let string = a.first ?? b.second {
    print(string)
}
if let strings = a.third?.strings {
    print(strings)
}

It's not difficult. As the case spelling would hint, you switch over the two values:

switch (first, second) {
case (.first(let data), _):
  fallthrough
case (_, .second(let data)):
  print(data)
default:
  break
}
1 Like

Much of this can be accomplished using macros, such as PointFree’s swift-case-paths, to generate the Bool accessors for each case. I still think it would be better for this to be built it, and for the case syntax to be better integrated, but there are workarounds now.

Yeah, for what it's worth, using our case paths library your first situation can be expressed like so:

import CasePaths

@CasePathable
@dynamicMemberLookup
enum MyEnum {
	case first(data: String)
	case second(data: String)
	case third(data: [String])
	case fourth
	case fifth
	case sixth
}
let value1 = MyEnum.first(data: "Gabriel")
let value2 = MyEnum.second(data: "Aurora")
var value3 = MyEnum.third(data: ["Gabriel", "Aurora", "Marcela"])

if let data = value1.first ?? value2.second {
    print(data)
}

And your second situation like this:

if
  let data1 = value1.first,
  let data2 = value2.second,
  let data3 = value3.third
{
  print(data1)
  print(data2)
  print(data3)
}

And there are a few other niceties too, such as an expression version of case pattern matching:

if value1.is(\.first) {
  // ...
}

Of course, this is only viable if you are OK depending on a 3rd party library and OK with the cost of macros.

6 Likes