Enumeration case evaluates as boolean

There's been a bit of discussion on this in the past, but as there's been no movement I thought I'd bring it up again. Given an enumeration with an associated type;

enum MyEnumeration {
    case aaa
    case bbb(String)
    case ccc
}

One can do...

let e = MyEnumeration("Dummy")
if case MyEnumeration.bbb = e {
    print("matches")
}

Where inside of the if statement the case (let) obviously evaluates to true. Our enumeration value is some form of a bbb. Great.

However, try to do something like...

let publisher = PassthroughSubject<MyEnumeration, Never>()
publisher
    .filter { case MyEnumeration.bbb = $0 }
    .sink { e in
        print(e)
    }

And you'll get an error. What one is forced to do, instead, is...

let publisher = PassthroughSubject<MyEnumeration, Never>()
publisher
    .filter {
        if case TestEnumeration.bbb = $0 {
            return true
        }
        return false
    }
    .sink { e in
        print(e)
    }

Which is ugly, verbose, and would be completely unnecessary if said case would simply evaluate as a boolean. Which it obviously can do, as shown in the earlier example.

Another, entirely too common situation is wanting something like...

extension MyEnumeration {
    var isBBB: Bool { case .bbb = self }
}

Nope. Back once more to our previous over-elaboration.

extension MyEnumeration {
    var isBBB: Bool {
        if case .bbb = self {
            return true
        }
        return false
    }
}

Which, when added, let's us do...

    .filter { $0.isBBB }

Which is where we wanted to be in the first place.

Heaven help you if you want something like...

extension MyEnumeration {
    var isSomeSubset: Bool {
        case .bbb = self || case .ccc = self
    }
}

Currently your best option there is some variation of...

extension MyEnumeration {
    var isSomeSubset: Bool {
        switch self {
        case .bbb, .ccc:
            return true
        default:
            return false
        }
    }
}

Again, ungainly and totally unnecessary.

Allowing the case to evaluate to boolean anywhere and everywhere would solve the problem, and while creating the least impact.

But as long as I'm making a Christmas wish, what I'd really like is to be able to do something like...

    .filter { $0 is .bbb }

We already know the type, and as such requiring the case MyEnumeration. in case MyEnumeration.bbb = $0 is again overly verbose. Further, if all we care about is the pattern match then the case let assignment syntax no longer makes sense either.

And finally, an easy to use syntax would pretty much obviate the need to create supporting code like MyEnumeration.isBBB in the first place. (Or the need to synthesize them, as some proposals have suggested.)

Any thoughts on this?

1 Like

Agree that there are improvements in this area that would make life better for users.

It’d be great if you could link to prior conversations and pull out key points, summarizing the design space already explored, so that this conversation doesn’t have to start from scratch and rehash what’s already been said.

The core team has already given guidance about this so it would be helpful to refresh out memory about what they’re looking for, so that would be good to bring into this conversation too.

For my part, I’d be intrigued to see if expanding is to pattern matching with a syntax as follows would get some mileage:

let result: Result<_> = …
let foo = result is case .success ? 42 : 0
4 Likes

Would it be possible to simply use the infix ~= operator? That’s what case actually does in most cases.

The is case syntax might even allow local pattern matches within the "then" expression:

let result: Result<Int> = …
let foo = result is case let .success(x) ? x : 0
2 Likes

I mostly support this proposal (there seems to be quite a bit of room for improvement with Swift's pattern-matching system in general), though I have some comments.


FYI you don't need to include the type of the enumeration — type inference can figure that part out. You can just write this:

let e = MyEnumeration.bbb("Dummy")
if case .bbb = e {
    print("matches")
}

I'm against this since the is keyword is too strongly tied to type checking, and because it's not that much better than .filter { case .bbb = $0 }.


Using ~= only works for custom pattern-matching overloads. Enumerations don't override this operator by default. (In fact, MyEnumeration.bbb ~= e wouldn't compile even if ~= were overloaded since MyEnumeration.bbb doesn't specify its associated value.)

I think changing that would be the simplest solution.

Blockquote FYI you don't need to include the type of the enumeration — type inference can figure that part out. You can just write this: if case .bbb = e

You can... but when actually writing the code you don't get autocompletion on the enumeration since you're writing the assigned value last. It's backwards. That's why you tend to include the type.

It's also why e is .bbb might tend to work better (or, as mentioned by someone else, e is case .bbb. With the type first, autocompletion on the value can actually occur.