# Bringing Control Flow Keywords to the Ternary Operator

In Swift, the ternary operator easily allows us to clean and concisely write conditionally code. Although currently, the inability to use certain statements in conjunction with the ternary operator is limiting.

I am proposing the addition of 5 statements for usage in conjunction with the ternary operator:

All of these various statements will greatly improve the flexibility and ease of use with the ternary operator.

# `break`

This will bring the use of the keyword `break` to the ternary operator.

This will simplify:

``````if condition {
break
} else {
doSomething()
}
``````

To:

``````condition ? break : doSomething()
``````

Note: the `break` statement can also be put in the second argument of the ternary operator too, this so the code would `break` if the `condition` was not met. A loop's label may be specified after the `break` keyword as well.

### Usage Example:

The following function, `getPrimes(n:)`, returns the first `n` prime numbers as an array of `Int`s.

``````func getPrimes(n: Int) -> [Int] {
var primes = [Int]()
var count = 1

for x in 2... where !primes.contains { x % \$0 == 0 } {
primes.append(x)
if count > n {
break
} else {
count += 1
}
}
return primes
}

print(getPrimes(n: 10))
// Prints "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]"
``````

Now, let's fix this code using our new and refined syntax:

``````func getPrimesAgain(n: Int) -> [Int] {
var primes = [Int]()
var count = 1
for x in 2... where !primes.contains { x % \$0 == 0 } {
primes.append(x)
count < n ? (count += 1) : break //syntactical change
//We can alternatively use:
//count += count < n ? 1 : break
}
return primes
}

print(getPrimesAgain(n: 10))
// Prints "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]"
``````

# `fallthrough`

This will bring the use of the keyword `fallthrough` to the ternary operator.

This will simplify:

``````if condition {
fallthrough
} else {
doSomething()
}
``````

To:

``````condition ? fallthrough : doSomething()
``````

Note: the `fallthrough` statement can also be put in the second argument of the ternary operator too, this so the code would `fallthrough` if the `condition` was not met.

## Usage Example:

First let's start by creating our `Fruit` enumeration.

``````enum Fruit {
case apple, pear, orange, banana, grapes
}
``````

Now, let's create a function that takes a list (`Array`) of the fruits we have in our fruit basket. We want this function to tells us if we have an apple in our basket, but otherwise, tell us that we just have some random fruits.

``````func fruitBasket(with fruits: [Fruit]) {

guard let firstFruit = fruits.first else { return }
var str = "We have "
switch fruits {

str.append("an apple and ")
if fruits.count > 1 {
fallthrough
} else {
str.removeLast(4)
}
default:
str.append("some other fruits")
}
}

let fruits : [Fruit] = [.apple, .pear, .banana]
// Prints "We have an apple and some other fruits in our basket"

let applelessFruits : [Fruit] =  [.pear, .orange, .grapes]
// Prints "We have some other fruits in our basket"
``````

Now, let's rejig our code above with our new and refined syntax:

``````func fruitBasketRefined(with fruits: [Fruit]) {

guard let firstFruit = fruits.first else { return }
var str = "We have "
switch fruits {

str.append("an apple and ")
fruits.count == 1 ? str.removeLast(4) : fallthrough //syntactical change
default:
str.append("some other fruits")
}
}

let newFruits : [Fruit] = [.orange, .apple, .banana, .grapes]
// Prints "We have an apple and some other fruits in our basket"

let newApplelessFruits : [Fruit] =  [.pear, .orange, .grapes]
// Prints "We have some other fruits in our basket"
``````

# `return`

This will bring the use of the keyword `return` to the ternary operator.

This will simplify:

``````if condition {
return someValue
} else {
doSomething()
}
``````

To:

``````condition ? return someValue : doSomething()
``````

Note: the `return` statement and `someValue` can also be put in the second argument of the ternary operator too, this so the code would `return` `someValue` if the `condition` was not met.

### Usage Example:

We want to make a function `pythagoreanTripleGCF(a:b:c:)` where `a`, `b`, and `c` are values of type `Int`. If `a`, `b`, and `c` form a pythagorean triple, our function will return the greatest common factor of `a²`, `b²`, and `c²`. But if `a`, `b`, and `c` do not form a pythagorean triple, our function will return `-1`.

``````func pythagoreanTripleGCF(a: Int, b: Int, c: Int) -> Int {

let (x, y, z) = (a * a, b * b, c * c)
var n = -1

if x + y == z {
n = min(x, y, z)
} else {
return n
}
n = (1...n).reversed().first { x % \$0 == 0 && y % \$0 == 0 && z % \$0 == 0 }!

return n
}

let a = 6
let b = 8
let c = 10
// 6² + 8² = 10², ∴ a, b, and c are a valid pythagorean triple

print(pythagoreanTripleGCF(a: a, b: b, c: c)) //The GCF of 36, 64, and 100 is 4
// Prints "4"
``````

Now, let's update our code above with our new and improved syntax.

``````func betterPythagoreanTripleGCF(a: Int, b: Int, c: Int) -> Int {

let (x, y, z) = (a * a, b * b, c * c)
var n = -1

x + y == z ? (n = min(x, y, z)) : return n  //syntactical change

n = (1...n).reversed().first { x % \$0 == 0 && y % \$0 == 0 && z % \$0 == 0 }!

return n
}

let a = 6
let b = 8
let c = 10
// 6² + 8² = 10², ∴ a, b, and c are a valid pythagorean triple

print(betterPythagoreanTripleGCF(a: a, b: b, c: c)) //The GCF of 36, 64, and 100 is 4
// Prints "4"
``````

# `continue`

This will bring the use of the keyword `continue` to the ternary operator.

This will simplify:

``````if condition {
continue
} else {
doSomething()
}
``````

To:

``````condition ? continue : doSomething()
``````

Note: the `continue` statement can also be put in the second argument of the ternary operator too, this so the code would `continue` if the `condition` was not met. A loop's label may be specified after the `continue` keyword as well.

### Usage Example:

Let's start off by making a function called `transformedEvens(of:with:)`. The first parameter, `arr`, is of type `[Int]` and this will be the array that we are preforming the specified transformation on. The second parameter, `transform`, is of type `(Int) -> Int` and this specifies the transformation to perform on each even integer in the array (`arr`). After preforming the specified transformations on `arr`, we will return our newly transformed array `newArr`.

``````func transformedEvens(of arr: [Int], with transform: (Int) -> Int) -> [Int] {
var newArr = [Int]()

for var n in arr {
if n % 2 == 0 {
n = transform(n)
} else {
continue
}
newArr.append(n)
}

return newArr
}

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let transformedArray = transformedEvens(of: array) { \$0 * 10 }

print(transformedArray)
// Prints "[20, 40, 60, 80, 100]"
``````

Now, let's take our `transformedEvens` function and freshen it up with our new syntactical changes.

``````func transformedEvensV2(of arr: [Int], with transform: (Int) -> Int) -> [Int] {
var newArr = [Int]()
for var n in arr {
n % 2 == 0 ? (n = transform(n)) : continue //syntactical change
newArr.append(n)
}
return  newArr
}

let newArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let newTransformedArray = transformedEvensV2(of: newArray) { \$0 * 10 }

print(newTransformedArray)
// Prints "[20, 40, 60, 80, 100]"
``````

# Blank Argument ``_``

This will bring the use of the keyword `break` to the ternary operator.

This will simplify:

``````if condition {
doSomething()
} else {
//empty
}
``````

To:

``````condition ? doSomething() : _
``````

Note: the blank ``_`` argument can also be put in the first argument of the ternary operator too, this so `doSomething()` would execute if the `condition` was not met. If indeed `condition` is false in the above line of code, the line will not be evaluated.

### Usage Example:

Let's make a function called `addEvens(from:to:)`. This function will take an array of `Int`s, `arr`, and return the value of each of its even integers added to `n`.

``````func addEvens(from arr: [Int], to n: Int) -> Int {
var num = n
for i in arr {
if i % 2 == 0 {
num += i
}
}
return num
}

let number = 15
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// Prints "45"
``````

Now, let's redo our code above with our new ternary syntax:

``````func addEvensFixed(from arr: [Int], to n: Int) -> Int {
var num = n
for i in arr {
i % 2 == 0 ? (num += i) : _ //syntactical change
//We can alternatively use:
//num += i % 2 == 0 ? i : _
}
return num
}
``````

## Ternary Operators that Return Values

Given the following code

``````var totalUnderTen = 0
for n in 1... {
totalUnderTen += n < 10 ? n : break
}
// totalUnderTen = 45 = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9
``````

The code will be accepted as valid syntax and will break if the condition is true, rather than returning the value of `break` so to say.

The same is true for all of the other aforementioned additions (`break`, `fallthrough`, `_`, `return`, `continue`).

## Motivation

The primary motivation for this addition to the ternary operator is ease of writing quick code. This will help extend the functionality of the loved ternary operator, especially when dealing with control flow.

Thanks for the thorough pitch, with lots of good examples that made your idea clear. I think we have to question whether

``````count < n - 1 ? (count += 1) : break
``````

is significantly better than

``````if count < n - 1 { count += 1 } else { break }
``````

for people who want to write their control flow all on a single line. This is especially true for the `_` case, where

``````i % 2 == 0 ? (num += i) : _
``````

``````if i % 2 == 0 { num += i }
``````

So my preliminary thought is that this doesn't seem like much of a brevity improvement over the status quo, and has the potential to obscure control flow. It also makes understanding code involving the ternary operator more difficult, since you can no longer just collapse it in your head to just being a value of some type.

4 Likes

For the first scenario:

I just updated my proposal to offer an alternative for that very part.

Another way to write it with my most recent change (setting `count` initially to `1` instead of `0`):

``````count += count < n ? 1 : break
``````

Would you not say that is better than:

``````if count < n { count += 1 } else { break }
``````

I would say that the provided ternary syntax is better than the `if else`, but that's just my opinion. But regardless of the fact, shouldn't the option be available for people to use? I think so.

No, unfortunately, I would not say that the former is better. In fact, to me it's quite unreadable, whereas the existing spelling is instantly clear to the reader. Since code is read more often than it is written, I would conclude that enabling the first syntax is something that should be actively avoided.

Swift syntax is already criticized as having too many options; as an opinionated language, adding additional syntax is to be avoided if the only result is to provide more options rather than better ones.

Good effort on the pitch though; I think, overall, where you're trying to go is to make more control flow statements into expressions. I think `switch` is the highest yield in that regard, but in general the idea has some legs. I'm not sure that the example above reflects well on the overall idea, though.

2 Likes

This seems confusing to me. I understand it, but on first glance, what is a `break` statement doing inside of an assignment expression?

2 Likes

``````a == 0 ? break : a == 1 ? return : a == 2 ? 3 : 4
``````

Granted, nested ternary operators are difficult at the best of times, but I don't think being able to sprinkle different control flow statements is going to help.

1 Like

I'd have to say no to this idea. In fact I hate it. This idea turns the ternary operator into a crippled `if` statement.

The ternary operator is designed to be used in expressions. It has arguments that are expressions and it returns a value with a type. It is not a control flow statement. For this, we have the `if ... else` statement and this idea offers nothing over that except fewer keystrokes. The examples are not simpler than the code they replace, just shorter and (in my opinion) less readable.

5 Likes

Strongly agree with @jeremyp on this. It doesn't make the language any more expressive, encourages using ternaries as control flow rather than purely as expressions, and find the code harder to read.