Ternary operator for Switch

I'd like to update the Ternary operator to allow it to do for switch what it currently does for if.

Right now we can use:

let x = a ? 10 : 0

as a shorthand for:

let x:Int
if a {
    x = 10
} else {
    x = 0
}

Right now to set a variable based on the value of an enum (or other pattern) we have to use a switch statement, which can be a bit unwieldy in some cases

let x:Int
switch a {
case .small: x = 10
case .medium: x = 20
case .large: x = 30
default: x = 0
}

I am proposing that we use the Ternary syntax combined with something similar to dictionary syntax to streamline this:

let x = a ? [.small : 10, .medium : 20, .large : 30] : 0

Here the left side of each pair defined is the pattern we are matching against, and the right side is the value which is returned if it matches. The value after the final colon of the ternary represents the default (and matches nicely with the else that it represents for if).

It basically has the same strengths/weaknesses/tradeoffs as using the ternary operator for if. That is, it is very terse and compact, but also takes an extra second to decipher (mainly due to the use of ?)

The other alternative would be to use an equally terse syntax which doesn't use a ? or hanging :

let x = a ~[.small : 10, .medium : 20, .large : 30, _ : 0]

How about a 'match first' method on an array of (matchable, value) tuples?

Really, I'd like a protocol called Matchable which is the type of things Implementing the "~=" operator...

(John, if you search the archives, you will find there's been many, many, posts about extending the ternary operator. I think it's a dead horse.)

let a : [(Matchable, String)] = [(1...3, "Early"), (4...6, "On Time")]

let b = a.matchFirst(5)

The example I meant to type:

let a : [(Matchable, String)] = [(1…3, “Early”), (4…6, “On Time”), (7, "late")]

let b = a.matchFirst(5) // "On Time"

let c = a.matchFirst(7) // "late"

The terseness of ?: works because there are only two values, so it is still comprehensible at a glance. Extending it to cover multiple values wouldn't have that benefit.

The ternary operator barely clears the bar for inclusion in Swift today. From the commonly rejected proposals list:

Replace ?: ternary operator: Definitely magical, but it serves a very important use-case for terse selection of different values. Proposals for alternatives have been intensely discussed, but none have been "better enough" for it to make sense to diverge from the precedent established by the C family of languages.

As such, proposals for further magical sugar of a similar nature, but without the C heritage justification, are probably not a good idea.

Note, you can get a similarly terse lookup syntax with a dictionary literal today:

let x = [.small : 10, .medium : 20, .large : 30][a] ?? 0
11 Likes

are we sure the compiler knows how to optimize this? i remember a lot of posts about the compiler failing to optimize expressions like this

It probably doesn't today – but current optimizations or lack of them shouldn't be a factor in whether or not to change the language design.

5 Likes

this is the kind of thing that makes programming in Swift kind of weird in that if you want to write “performant Swift” you’re basically writing it the way you would spell it in C, i.e. avoiding closures, preferring UnsafeBuffers over Arrays, avoiding String and Character in favor of UnsafeMutableBufferPointer<Unicode.Scalar>, or even UnsafeMutableBufferPointer<UInt8> (!!!). This makes code that can be even harder to read than C because of how Swift represents pointers and buffers, and things like its lack of char literals — you have to type 97 to get the letter 'a'.
meanwhile there’s this weird high level dialect of Swift that uses Array and Dictionary and String that is about as readable, and about as fast as Python.

1 Like

You shouldn't have to use a different syntactic form or play around with dictionaries to pattern-match in expression contexts. switch and if should just be expressions, IMO, once the type checker problems that would currently make type-checking a switch expression prohibitively expensive are fixed.

24 Likes

I agree. I didn’t know this was a possibility in the future and am very happy to hear that it is!

2 Likes

Would love switch, if, and for to be expressions.

I don't follow how for would be an expression. Can you elaborate?

There's precedent in Haskell, Python, and Scala for for expressions as list comprehensions. You could imagine something like:

let squares = for i in 0..<100 { yield i*i }
1 Like

Scala uses syntax very similar to that Joe Groff showed. Another possibility would be return instead of yield and for return from a function name.return. EG:

func allNegativeSquares(_ ints: [Int]) -> [Int] {
    return for i in ints { 
        if i > 0 { 
            allNegativeSquares.return [0] // Named return to indicate return from outer scope. 
        return i * i 
    }
}

This has three advantages relative to yield: clarity that a return is non local, unified unnamed return to mean return from inner scope, and consistency with closures. It has the disadvantage of been a breaking change.

Another possibility is yield for all local returns and return for all non-local. Again clarifies what is going on and again is a breaking change.

I see. I am not sure I would want to reuse for for this purpose, but I agree that having something like this would be very useful.

Sure, I also think making for work that way in Swift would be difficult without inviting confusion, but that's one example of what people have in mind when they talk about for expressions.

1 Like

I like how you are trying to think of a way to make the expression shorter, but then again there is a cost to introducing new syntax. Especially when it is rather unique.

Here is my attempt at using a ternary operator to match your "unwieldy" switch statement. It works as of swift 4:

// Assuming we have an enum like this
enum Size {
    case small, medium, large, other
}

// My alternative to using switch statements
let a: Size = .other
let x = (a == .small) ? 10 : (a == .medium) ? 20 : (a == .large) ? 30 : 0

There is a little bit of repetition even though its shorter then the standard switch statement.
But I am ok with it.

This can also be written on multiple lines, for instance with the colons forming a vertical ellipsis:

let x = (a == .small)  ? 10
      : (a == .medium) ? 20
      : (a == .large)  ? 30
      :                   0
3 Likes

I like it. You make it look like a skinny switch statement.

Honestly, I hate using multiple ternary expressions at once, since I am not sure if the result works as intended. I would take a standalone dictionary any time:

let presets: [Size:Int] = [.small: 10, .medium: 20, .large: 30]
let x = presets[a] ?? 0

The only thing I don’t like about this solution is the explicit type signature on the dictionary. Otherwise I think it’s reasonably low-lech (dumb is good!) and perfectly readable.

1 Like

Then don't use a type signature :)

let presets = [Size.small: 10, .medium: 20, .large: 30]
2 Likes