If case in?

I have always found the if case .foo(let x) = a syntax to be super confusing. After some thought, I think the problem is with the = which my mind wants to read as == because of the proximity to if.

How about we get rid of the cognitive dissonance by using in instead of =?

if case .foo(let x) in a {
    //Do stuff here if .foo(x) was found in a
}

Note: This is already the syntax used by for case, so someone must have thought it was a good idea. Let's just bring if case and guard case over to the same syntax...

1 Like

There is no consistency issue here. The in in for...in refers to iteration, not to pattern matching.
There is no iteration that takes place when pattern matching using if, so there is no justification for in. There is, however, an assignment occurring, and the assignment operator in Swift is spelled =.

I also disagree with this idea. in is used in the context of iteration, which is why for case already uses it. I believe it is misleading to assign variables using in.

1 Like

What is being assigned in the following?

if case .foo = a {
}

I disagree that the word "in" intrinsically means iteration. By definition it is referring to containment. For each x contained in a collection. It could easily be used in other places where there is some sort of containment relationship.

2 Likes

At first I thought this was a great idea. And I still think its worthwhile, but perhaps 'in' is not the correct keyword. It makes me think of how Rust uses the word 'match' instead of 'switch'. While I don't think Swift is going to make a change there, would the word 'matches' work here?

if case .foo(let x) matches a {
    //Do stuff here if a has a value of .foo(x)
}

I know its unlikely for Swift to get a new keyword like this at this point, but just thought I'd throw it out there...

The reason I don't think 'in' is appropriate is that a is a single value, not a collection of values. I mentioned in a previous post that I would like to see 'in' to be used to check if a "left hand side" value is contained within a connection on the right hand side, i.e.:

if char in Set<Character>(["a", "e", "i", "o", "u"]) { ... }

Not to sidetrack this discussion...

1 Like

I'm not sure replacing = by in really makes things better, but I do agree in general that if case syntax is confusing.

What I'd really like is if it could work with auto-completion. For instance, if I write this non-case if around an enum:

if someEnumVar == .

and then trigger auto-completion, I'll get a list of cases for that enum. Can't do that with if case because the order is reversed and the variable isn't written yet so autocompletion has no context:

if case .

What would work better is if the two operands around the = were reversed. So maybe we could tweak the syntax like this:

if someEnumVar case .foo(let x) {
    ...
}

Now autocompletion works after the dot.

5 Likes

It's there, but you have to reach for it.

In general, the case may have an associated value. If it does, the syntax looks like this:

if case .foo(let bar) = a {
}

If you don't want to bind the associated value, you just drop the binding (as you do in a switch statement case):

if case .foo = a {
}

A case with no associated value uses this "degenerate" syntax.

As @michelf says, the real problem is that the expression is backwards for autocompletion purposes. Personally, I'd prefer to see:

if a is case .foo {
}

I think this is not as big an abuse of is as it may seem, since enum cases are very close to being subtypes anyway.

Even though the if-case-let syntax is “technically correct” I’ve always found it confusing as well. Most Boolean checks have the variable on the left and the constant on the right. Even a switch-case follows this pattern, if you consider switch a the LHS and case .foo(let x) the RGS. Perhaps if a ~= .foo(let x)would be a better way to express if-case-let, as it more closely mirrors switch-case, which it is a special case of.

2 Likes

From a purely clarity-oriented standpoint, I think the most readily-understood spelling is:

if <value> matches <pattern> { }

As an example,

if status() matches .connected(let port) { … }

would be sugar for:

if case .connected(let port) = status() { … }

The sugared version with “matches” is actually 1 character longer, but it reads far more naturally.

• • •

If I were to go even further, I might suggest that we could introduce automatic binding of associated values in patterns like this, much as we do for “newValue” in setters and “error” in catch blocks.

Perhaps the bound value could be named “value”, and it would be a tuple if more than one associated value is present. The compiler can of course check whether “value” is used inside the block, and if not then optimize it away.

The usage would look like this (and similarly for switch statements):

if status() matches .connected {
  print("Connected on port \(value)")
}

If we ever actually support overloading enum case names, then obviously this implicit binding would not work for overloaded cases, and the programmer would have to bind values manually just as they do today.

10 Likes

I generally agree, but don’t see why we need the matches keyword when we already have the ~= operator

One reason is that operators are generally more difficult to find than keywords. Keywords get code completion, operators do not.

I've always found the if case/guard case syntax to be unintuitive. matches does seem to fit better and as a result people may find it on their own. While I like the ~= operator, I only learned about it while reading posts on these forums and I'm willing to bet I'm not the only one who wasn't aware of the ~= operator. It's not something I've ever seen during google searches or in tutorials I've read. I've only ever seen it mentioned here, which was mostly a coincidence.

1 Like

~= is an operator and thus cannot be used to destructure a case’s payload. The right-hand side doesn’t take a pattern, it takes a value. A matches expression takes patterns (which includes values) on the right-hand side.

If matches were to be added to the language, ~= would still continue to exist as an extension point for matching non-case values. But it would still remain sugar for the current (C++ inspired?) syntax using if case and =.

Anyone know why Enums with payloads don't just use some kind of property syntax. Seems like all these problems and tortured bits of syntax would vanish. Is it some philosophical thing?

1 Like

i can’t stand the ~= i can never remember if the ~ goes before the = or after

googling “swift ~=” actually returns helpful results which i was surprised by since google usually doesn’t do too well with punctuation. the ... and ..< are way more problematic by comparison

That surprised me too when I first saw a post containing the ~= operator on these forums, but how do you even know to google for ~= when you've never used it in another language? These forums are the first place I ever saw noticed that operator (and I've even visited this page several times). There are a lot of operators and it's really easy to miss one and not realize the one you want already exists.

If ~ is read “fuzzy” and = is read “equals”, then the phrase “fuzzy equals” would correspond to the operator ~=. That’s how I remember it, anyway.

Can you give an example of what you would change to what? I'm unclear.

I do like matches as a keyword because it reads well. But I'm a bit reluctant about changing the keyword. Using case to introduce a pattern is well established by now.

if status() case .connected(let port) { … 

Currently we have that thing called the "if case" syntax. Simply moving the keyword a bit further probably won't change much how people talk about it. So it'll end up with the same name. And this is good, because it's the same thing.

1 Like

Given all of the options here I am inclined to agree that our best option (so far) is:

if a case .foo(let x) { ... }

It doesn't read as well in english as matches, but it is a lot less confusing than the status quo (and we gain autocomplete) w/o burning a new word.