Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift ternary operator compilation error

Tags:

swift

Why does this generate an error and how should it be written?

    let x = 5
    let y = 4
    var z:Int
    x < 4 ? z = 6 : z = 8

The error is "Could not find an overload for < that accepts the supplied arguments"

like image 579
Gruntcakes Avatar asked Jul 03 '15 18:07

Gruntcakes


2 Answers

The reason your ternary operator isn't working there is because of the precedence of the various infix operators. You can see the operator precedence list here. If you look, you'll see that the operators down the bottom are the ones that are usually placed between larger chunks of code. The higher the precedence, the tighter it'll clamp to the expressions to its left (or right). So, you usually want your = operator to have a very low associativity, so in an expression like:

let x = 2 + 3

The + will grab the two operands on either side of it before the = will, so it resolves to:

let x = (2 + 3)

rather than something like:

(let x = 2) + 3

Which makes less sense.

The ternary conditional operator has precedence of 100, whereas the = operator has precedence of 90. So in your example, when you've got this:

x < 4 ? z = 6 : z = 8

The associativity goes like this:

x...
x < ...
x < 4 // precedence 130, so resolves
(x < 4)...
(x < 4) ?...
(x < 4) ? z...
(x < 4) ? z = ... // Does not yet resolve. Why? Well, here, the ternary isn't 
                  // really an infix. It is special kind of operator that 
                  // takes something between ? and :. So normal associativity
                  // doesn't apply. (I'm still experimenting...)
(x < 4) ? z = 6 : ...
(x < 4) ? z = 6 : z // HERE it resolves. The z is grabbed, as the ternary has 
                    // precedence 100, larger than the 90 precedence of the =
((x < 4) ? z = 6 : z) = 8

So because the precedence of the ternary operator is higher than that of the assignment operator, it "grabs" the z, and then, when it finds the =, it gets all confused. What the compiler sees looks something like this:

if (x < 4) { z = 6 } else { z } = 8... // Compiler has a hissy fit

So how do you fix it?

x < 4 ? (z = 6) : (z = 8) // x < 4 ? z = 6 : (z = 8) works as well

The parentheses make the associativity explicit. Why does it work this way in the first place? Well, usually you use the ternary operator to resolve it to an expression (as was in @LeoDabus' answer). And in that case, its associativity means that you don't need the parentheses:

let z = x < 4 ? 6 : 8
let z...
let z = ...
let z = x ...
let z = x < 4 // < has precedence 130, bigger than =
let z = (x < 4) ? // ? has precedence of 100, bigger than =
let z = if (x < 4) { 6 } else { 8 }

I hope that helps, and makes sense. (I might have gotten some of my precedence explanations wrong, I find it quite confusing)

Turns out, I did get some of my precedence explanations wrong. If my explanation had been correct, this wouldn't have worked:

x < 4 ? z = 6 : (z = 8)

But it does! It turns out, the precedence issues happen after the operator, not before. (I explain a bit more above)

Also, the actual if statement with curly braces itself doesn't resolve to an expression. So this doesn't work:

let z = if (x < 4) { 6 } else { 8 }
like image 51
oisdk Avatar answered Nov 08 '22 22:11

oisdk


let x = 5
let y = 4
let z = x < 4 ? 6 : 8
like image 28
Leo Dabus Avatar answered Nov 08 '22 22:11

Leo Dabus