Proposal draft for `is case` (pattern-match boolean expressions)

I think for the same reason function without arguments (or return values) are Void -> Void and not Never -> Void or Void -> Never, etc.

Take the example from above:

enum AuthorizationState {
  case notAuthorized
  case authorized(username: String, birthday: Date?)
}

If authState[keyPath: \.notAuthorized] returned Never? it would impossible to ever return anything other than nil, non?

1 Like

That's my dilemma here. Since this particular case has literally no payload, it should never return anything. Either that, or we want to express "nothing" as Void here. That's the clarification I'm seeking for. :thinking:

Well Optional<Void> and Bool are isomorphic, and if we want a general API for all enum cases, testing for != nil is equivalent to testing for == true.

We either have to drop support for them altogether (don't mind using Never over Void, they could simply not be available at all), or special-case bare cases with Bool, or use Void — as suggested!

1 Like

I think I just understood your concern, methinks.

But the "case" key path has two purposes: (1) To test if the value matches a certain case, and (2) to extract the payload if it does. If you could not return a valid value for "the cases matches and it has no payload", the API would not be able to perform its first purpose.

Let’s focus on the is case discussion in this thread :)

2 Likes

In the following enum, item1a and item1b are equivalent. If the payload would be Never, it would not be possible to create a value of that case.

enum Foo {
  case item0(Never)  // 0 possible values. This case is impossible to create.
  case item1a        // 1 possible value
  case item1b(Void)  // 1 possible value
  case item2(Bool)   // 2 possible values
}

It seems correct to me to have Void as the payload of a case without a (specified) payload.

2 Likes

I suggested this elsewhere: albeit non symmetrical, the type of payload-less cases could be the Enum type itself:

enum AuthorizationState {
  case notAuthorized
  case authorized(username: String, birthday: Date?)
}
let state: AuthorizationState = ...

state.notAuthorized // Optional<AuthorizationState>
state.authorized // Optional<(username: String, birthday: Date?)>

Void type for payload-less cases also makes sense to me.

Actually, item1a and item1b were deliberately made distinct in one of the previous enum proposals. It would be good to be consistent on this with keypaths.

Anyway, let's avoid derailing the is case thread regarding this distinct, not-yet-pitched idea.

3 Likes

I was under the impression that one of the fundamental purposes of a pitch thread is to consider possible alternatives.

For sure! I think it's really important to keep in mind both alternatives and features that might partially overlap or complement the currently pitched idea.

But this is distinct from getting into the detailed design of those alternatives or related features particularly as it relates to aspects that wouldn't affect this idea. It's been well over a dozen messages getting ever deeper in this direction.

I would suggest splitting off the parts of the thread that concern the design of case paths, but it also seems unfair to start discussing a design when the authors haven't got it to a place where they're ready to pitch it.

7 Likes

Right. This all started because Joe correctly pointed out that the motivation for is case would be a lot lower if there were an existing way to test if an enum value had a particular case, and that several of the alternative ways proposed for this in the past have been more powerful too. (In particular, they'd provide a way to get at an enum payload in expression context, which is case does not.) Past proposed alternatives I know of include

In practice, I agree that we might not bother with is case if we had one of these features already. However, I do think that is case is much more tractable. We've had seven and a half years of open-source Swift and none of the proposals have been implemented for a proper review; is case is the simplest to implement; the simplest to document and teach; has the simplest compatibility story; and will remain useful even if we get one of the other features later.

28 Likes

DITTO! I so want to write code that looks like version 1 below instead of the convoluted messes we now have to do in versions 2 and 3.

extension Optional: OptionalProtocol {
    var hasWrappedValue1: Bool {
        self is case .none
    }
    var hasWrappedValue2: Bool {
        if case .some = self {
            return true
        }
        return false
    }
    var hasWrappedValue2: Bool {
        switch self {
        case .none:
            return false
        case .some:
            return true
        }
    }
}

It would make this sort of code and doing Combine/Rx filters MUCH easier.

Heck. I'd be happy with simply having the expression case .some = self evaluate to true...

AS IT ALREADY SEEMS TO DO WHEN USED IN THE IF STATEMENT.

Can SOMEONE please make this happen?

1 Like

This is an elegant solution to a very common sugar problem, best one so far! But I wonder if there would be a way to also support what others here have mentioned, namely checking if two instances of an enum are the same case. Something like:

enum MyEnum {
    case one(Double)
    case two(Int)
}

func sameCase(x: MyEnum, y: MyEnum) -> Bool {
    case x == case y
}

This would see case as an operator that turns an instance of MyEnum into an instance of the synthesized MyEnum.Discriminant:

extension MyEnum {
    enum Discriminant {
        case one
        case two
    }
}

I feel that this enum and the operator could be generally useful, but I'm not sure how it plays with the proposal here.

1 Like

Will if !(t is case text(_)) work?

See my question three years ago on a readable negation of if case .text(_) = t, which would probably be if t is case text(_) under this proposal.

1 Like

Did this go anywhere?

2 Likes

I think it mainly needs an implementer (which still won’t be me, unfortunately).

I'd be interested to tackle this implementation but I don't have any experience with the compiler. Would anyone be able to provide any pointers on how to approach this?

2 Likes

I wrote a macro that does a very simple version of this.

9 Likes