An explicit return also has its readability issues. In some cases (i.e. closures) an explicit return is confusing since it returns from the closure and not the function.
Some ideas for expressions that look more like expressions
I think in the most common situations it wouldn't be too confusing, however making implicit returns more explicit by making it clear they are expressions might help. I see this as a bigger issue if extended to the last expression of a function or closure in the future.
Maybe match
instead of switch
or a different variant of if-expressions.
// match is expression-only, so it is obvious this returns a value
func foo(y: NumNames) -> Int {
match y {
case .zero: 0
default: 1
}
}
/// some other indicator of a return value
func foo(y: NumNames) -> Int {
switch y {
case .zero => 0
default => 1
}
}
// one return is better then many (although I don't care for it in common higher-order functions)
func foo(y: NumNames) -> Int {
return switch y {
case .zero: 0
default: 1
}
}
Optional-chaining is not addressed by proposal
Optional-chaining is not addressed by this proposal. I think it makes sense to address that at the same time if we are going to re-invent the ternary operator. The proposed syntax will still require a lot of intermediary values to be explicitly stored in variables in many common situations. The if-expression alternatives below support chaining.
Use higher-order functions for if-expressions
The simplest solution would be to use higher-order functions for this.
func foo(y: NumNames) -> Int {
when(y == .zero) { 0 } // the return type of this line is `Int?`
?? when(y == .one) { -1 }?.negate() // optional-chaining
?? 2 // else is handled by the nil-coalescing operator
}
Fix the ternary operator with a new type of conditional operator
Another option would be a better ternary-style operator that uses infix semantics and optional-chaining instead of nesting. The nesting readability is the common argument against ternary operators.
I like this idea, but I think it would require a change to precedencegroup
for it not to feel weird. Currently, the condition will need to be put in parentheses due to operator precedence unless a special case is made like was done for assignment operators. It would basically need to add implicit parentheses to the comparison operator to the immediate left of the conditional operator because they are at the wrong precedence level. Changing the precedence of the nil-coalescing operator might also work, but that would be a very breaking changeā I didn't explore this method.
precedencegroup ConditionalPrecedence {
lowerThan: CastingPrecedence
higherThan: NilCoalescingPrecedence
associativity: none
conditional: true // implicit parentheses around comparison operator to left of conditional operator (alternatively require parentheses around it)
}
infix operator ?=: ConditionalPrecedence
func ?=<Value>(lhs: Bool, rhs: Value) -> Value? {
if lhs { return rhs } else { return nil }
}
This syntax avoids the major issues with the ternary operator since successive conditions are not nested. It is more versatile and Swifty with optional-chaining. This operator feels very analogous to the pattern match operator.
// already implementable
func foo(y: Int) -> SomeEnum {
(y > 0) ?= .positive // the return type of this line is `SomeEnum?`
?? (y == 0) ?= .zero // successive conditionals not nested
?? .negative // `else` is handled by the nil-coalescing operator
// or if nil-coalescing omitted, it returns an optional
}
// `precedencegroup` option to allow dropping the parentheses around the comparison operator
let msg = (Int.random(in: 0...10) < 5 ?= "less than 5")?.uppercased()
?? "GREATER THAN 4"
It would be nice to see the ternary operator eventually removed since it is such a weird operator and a common source of ambiguity.