Replace ternary _ ? _ : _ operator with a binary _ ? _ operator

I'm not sure whether this has been proposed, but since there's a switch extension for _ ? _ : _ floating about, I was thinking that it could be the last time for me to propose to get rid of the ternary operator

My first objection to it is that it is the only ? in the language not associated with optionals AFAIK.
My second is that it is the only ternary operator.
My third is that it is entirely redundant with the following substitution:

infix operator ? : TernaryPrecedence

private func ? <Value>(lhs: Bool, rhs: @autoclosure () -> Value) -> Value? {
    if lhs {
        return rhs()
    } else {
        return nil
    }
}

Here are some example uses:

let thisAndThat = this ? that ?? false // this ? that : false
let somethingIfThat = that ? something // that ? something : nil

let notThis = maybeThat.flatMap { $0 != this ? $0 }

As a side-point, which might not be terribly relevant, I feel that the ?? function with the non-optional rhs should probably be renamed as to be more explicit. Maybe ?: would be appropriate here.

Compare:

let hello = this ?? that ?? else
let hello = this ?? that ?: else

In a world with the explicit ?: operator, a human skimming the code can know, without prior information about the types of this, that, and else, that the hello is non-optional.

The reason I mention it is that, with this new operator, the diff between the old and the new lines is one character:

let thisAndThat = this ? that ?: false
let thisAndThat = this ? that : false
3 Likes

I support this idea, but it doesn't require removing the ternary - only introducing a standalone ? which returns an optional. The ternary operator's implementation shouldn't need changing, even if the mental model changes. As users see it, `c ? a : b` would be equivalent to `c ? a ?? b`, but with different precedence rules.

@forum_admins is there a way to move this swift-evolution/commonly_proposed.md at master Β· apple/swift-evolution Β· GitHub to the forum?

3 Likes

How would this help if the return type of your operator is always optional? The ternary conditional operator's return type is optional only if it's generic parameter is inferred as an optional type.

let foo: Bool? = true ? false ?? true

let foo: Bool?? = true ? false ?? nil

Removing the ternary operator from the language is a commonly rejected idea. Unless you've got a significant insight not mentioned previously, this is not going to be in scope for discussion.

Removing the ternary operator is one of the commonly rejected ideas, but I am in favor of leaving that alone while adding an operator which returns an optional value based on a boolean statement. I have this in a scripting DSL of mine and it is really lovely.

The syntax I use is ?(a) b where the value is b if a is true, and nil if a is false. This combines with my | operator which is the same as Swift's ??.

The reason I find it such a big win is that it lets me compactly chain together a number of else if style statements:

x = ?(a) b | ?(c) d | e

which is equivalent to the following in swift:

let x:Type
if a {
    x = b
} else if c {
    x = d
} else {
    x = e
}

I'd be strongly in favor of adding something with this functionality if we were to bikeshed the syntax a bit (since ? is too easily confused with ?? IMO), and leave the tertiary alone for now.

FWIW, ternary ? : chains, too:

let x = a ? b : c ? d : e

(and it's a bit more lightweight, since it doesn't construct any optionals that it immediately unwraps).

Hmm... good point. You could also always just do:

let x = a ? b : nil

for the nil behavior. My DSL had some other ways which this syntax stacked nicely with other parts of the language too, making it a huge win overall, but I don't think most of those are relevant to swift.

I guess I am back on the side of just leaving things as they are then...

1 Like

So, my proposal contains three core ideas:

  1. Remove _ ? _ : _
  2. Add _ ? _
  3. Replace ?? with ?: in the non-optional rhs case

Since I'm well acquainted with the commonly rejected proposals, 1 was a bit cheeky. Let's forget about that for the time being, as I still think 2 and 3 are worth discussing.

The idea of an explicit ?: is one that I've thought about for a while, I'm pretty confident that it enhances "scanability", and is generally a good move.

I've introduced the ? operator into my own apps/frameworks/experiments, just to see what would happen, and I quite like the general feeling of using it.

The line you quoted was written with only the addition of the binary ? operator in mind.

If we don't split the operators, your code becomes the following:

let foo = true ? false ?? true // Inferred Bool
let foo = true ? false ?? nil  // Inferred Bool?

If we split the overloaded ?? operator into ?? and ?:, then that becomes:

let foo0 = true ? false ?? true // Inferred Bool?
let foo1 = true ? false ?? nil  // Inferred Bool?
let foo2 = true ? false ?: true // Inferred Bool
let foo3 = true ? false ?: nil  // Inferred Bool?

The last case may be surprising, but that's because promotion to optional is weird. That, by the way is also a potential problem with the example you gave, since the number of ?s after Bool is fairly arbitrary. The type of the expression on the right hand side only gives a lower bound on the depth of nesting of optionals, let foo: Bool?????? = ... would also have compiled.

If here

let foo = true ? false ?? true

? is your custom operator defined in your root post and ?? is the nil-coalescing operator, then foo will be Bool? Note you return Value?.

Excuse me if I am misunderstanding something.

This is exactly the confusion that I'm talking about. If you actually do this in a playground, then the inferred type is Bool. This is because the ?? operator uses the non-Optional rhs variant, effectively of type (Bool?, Bool) -> Bool.

If we un-overload the ?? function, then the inferred type becomes Bool?. We would have to use the explicit non-Optional operator ?: to get the inferred type to be Bool.

Swift bureaucracy is a tough business, but I hope you'll have the stamina to push this into review ;-)
Afaics, the other plans to deprecate the ternary suggested to replace it by changing if or switch, so your pitch is different.
Looking at [swift-evolution] ternary operator ?: suggestion, there is a list of positive aspects of the ternary:

  • It is extremely concise, and covers a very common pattern.√
  • It is pervasively standardized in a very wide range of languages.
  • It’s weird syntax reduces the odds that people would flow it out and use very large expressions in it.√
  • It chains well for multiple conditions because of its associativity.√

Imho your operator keeps all important ones, and it removes a special case from the language. Apparently, it's much easier to get some new special cases into the language as long as they add a tiny bit of convenience, and removing a feature will always cause some churn.
But without cleanup, we'll pile up a lot of legacy, which makes the language complicated and ugly.

1 Like

No photoshop, I promise

The inferred type indeed is Bool, but the return type is Bool?

That's because the NilCoalescing precedence is higher than Ternary.

Also no photoshop:

precedencegroup BinaryPrecedence {
    associativity: right
    higherThan: NilCoalescingPrecedence
}

infix operator -? : BinaryPrecedence

let foo = true -? false ?? true
type(of: foo) // Bool.Type

Ah, you want

let foo = (true ? false) ?? true

Yes, that will work

1 Like

@masters3d I've created a post with a link to it and pinned it to the top of Pitches.

2 Likes