Go: proposal: Go 2: add a ternary conditional operator

Created on 18 Jul 2019  ·  78Comments  ·  Source: golang/go

I disagree with the Go convention and language's designers arguments here https://golang.org/doc/faq#Does_Go_have_a_ternary_form and think that it is a real missing feature in the language.

Consider in C the following code:

printf("my friend%s", (nbFriends>1?"s":""));

or in C++ :

std::cout << "my friend" << (nbFriends>1?"s":"") << std::endl;

In Go it causes either huges repetitions which can cause mistakes, or very verbose and inefficient code, or both, for something which should be straightforward:

Solution 1:

// horribly repetitive, risk of divergence between the two strings
if nbFriends > 1 { 
  fmt.Printf("my friends\n") 
} else { 
  fmt.Printf("my friend\n")
}

Solution 2:

// difficult to read
fmt.Printf("my friend")
if nbFriends > 1 { fmt.Printf("s") }
fmt.Printf("\n")

Solution 3:

// difficult to read
var plural = ""
if nbFriends > 1 { plural = "s" }
fmt.Printf("my friend%s\n", plural)

Solution 4:

// dangerous (ifTrue and ifFalse are both evaluated, 
// contrary to a real ternary operator), 
// and not generic at all (works only for strings)
func ifStr(condition bool, ifTrue string, ifFalse string) string {
  if condition { 
    return ifTrue
  }
  return ifFalse
}
fmt.Printf("my friend%s\n", ifStr(nbFriends > 1, "s", ""))

Solution 5:

// horrible to read, probably inefficient
fmt.Printf("my friend%s\n",
        func(condition bool) string {
            if condition {
                return "s"
            }
            return ""
        }(nbFriends > 1))
Go2 LanguageChange Proposal Proposal-FinalCommentPeriod

Most helpful comment

I think the example is kind of a bad one considering its only about one character and is not really readable in my opinion (?:"s":"")
but i do agree that the ternary operator should be added its a no-brainer for me
and i made up some examples which would be useful for me personally and i think you can somewhat relate to some of them.

const PORT = production ? 80 : 8080
instead of

const PORT = -1
if production {
    PORT = 80
else {
    PORT = 8080
}

fmt.Printf("Running %s build!", production ? "Production" : "Debug") .. etc

But of course the ternary operator is very hard to learn.

All 78 comments

@gopherbot, add label Go2, LanguageChange

See also #31659 and #32860.

This decision has already been enshrined in a FAQ, as you note. If you want to argue that the FAQ answer should be changed, you need more than a few examples. I promise you that we've seen and considered those examples already. What you need is data: real programs with code that would become simpler and easier to read by adding a conditional operator. You also need arguments against the common concerns about the conditional operator, such as that it makes code harder to read especially when nested.

Also, a minor point, but your example is not great since it only works for English, and does not support localization of message strings.

@ianlancetaylor

In #31659 you made what I thought was a very good counter-suggestion of having a built-in cond function to provide ternary functionality. This needed to be a built-in (as opposed to a generic function) to enable short-circuiting evaluation of the true/false arguments. It still suffered from the possibility that people could nest cond functions though personally I didn't regard that as a fatal problem because, even if they did, it should still be more readable than the hieroglyphics of C's ternary operator itself.

As that proposal has now been closed, do you intend to pursue that suggestion further or have you given up on the idea of having an alternative ternary form altogether?

I don't personally intend to push that idea further. That was more of a discussion thought than a serious suggestion. Of course, I don't mind if someone wants to polish it into a real proposal. But in order to be accepted I think we would still need to see some data as to how much it would simplify real existing programs.

OK, thanks for clarifying.

One has only to look at code in other C family languages to see how common the ternary operator is but it would be difficult to analyze Go code itself since, as @Phrounz pointed out in his opening post, a number of constructions are used to work around its absence.

Using the cond idea, his example would become:

fmt.Printf("my friend%s\n", cond(nbFriends > 1, "s", ""))

Having said all that, if we get generics, I'd personally be content to write my own cond function and, given the lack of short-circuiting, only use it where the arguments were cheap to evaluate.

In my opinion, to write more code (just a few lines) is better than to figure out the rule of x ? a : b. The if statement may seems verbose (not sure) but easy to understand.

Besides, a ternary conditional operator can be easily abused when people write multiple nested x ? a : b. The benefit of introducing it isn't great enough.

I see ternary operators only being easy to visualize in one-liners functions. Even then, most of the time they deal with error handling, at which case is better to adhere to a "the less indentation the better" approach by having the error or least probable path for a function be wrapped in an if and handled then, as opposed to having multiple branching logic.

I think the example is kind of a bad one considering its only about one character and is not really readable in my opinion (?:"s":"")
but i do agree that the ternary operator should be added its a no-brainer for me
and i made up some examples which would be useful for me personally and i think you can somewhat relate to some of them.

const PORT = production ? 80 : 8080
instead of

const PORT = -1
if production {
    PORT = 80
else {
    PORT = 8080
}

fmt.Printf("Running %s build!", production ? "Production" : "Debug") .. etc

But of course the ternary operator is very hard to learn.

Yes, that's a good example particularly if it's at top-level:

const production = true

//...

const PORT = production ? 80 : 8080

as you don't then need an init function to initialize PORT.

A built-in cond function _might_ be able to be used as a const initializer though a generic version definitely could not.

@Terottaja @alanfo

I think the idiomatic solution to that specific problem is to use Build Constraints.

@Terottaja

See my other comment for the solution for global variables/constants.

For local variables/constants, I think the idiomatic way to write this peace of code:

const PORT = -1
if production {
    PORT = 80
else {
    PORT = 8080
}

is:

PORT := 8080
if production {
    PORT = 80
}

You could argue that I changed the const to a var, but I'd be surprised if the compiler wasn't smart enough ™ figure out that PORT is constant so IMO it makes no difference in real code.

Although it probably wouldn't matter in this particular example whether PORT was a const or a var, more generally it might matter. For example, if you were declaring some integer which was to be used to define the size of an array.

Perhaps I should make my own position clear on this proposal. Whilst I personally have no problem with the 'standard' ternary operator, I'm not sure it would be a good idea to introduce it into Go in that form. I'd much prefer a cond built-in instead which is far more readable though, realistically, I see little chance of either being adopted.

@Terottaja

See my other comment for the solution for global variables/constants.

For local variables/constants, I think the idiomatic way to write this peace of code:

const PORT = -1
if production {
  PORT = 80
else {
  PORT = 8080
}

is:

PORT := 8080
if production {
  PORT = 80
}

You could argue that I changed the _const_ to a _var_, but I'd be surprised if the compiler wasn't _smart enough ™_ figure out that _PORT_ is constant so IMO it makes no difference in real code.

im just talking about if you have a lot of this kind of stuff in your code the ternary operator definitely would be more clean in this case, just my opinion

In my opinion, to write more code (just a few lines) is better than to figure out the rule of x ? a : b. The if statement may seems verbose (not sure) but easy to understand.

Besides, a ternary conditional operator can be easily abused when people write multiple nested x ? a : b. The benefit of introducing it isn't great enough.

there will always be people abusing it, code can be abused its inevitable but will that be affecting you? in most cases, no

I support this feature, while it can lead to abuses in code, it is really beneficial to compiler optimisations, when u avoid generic if /else and replace it with ternary operator.
People have been doing bitwise shifts from the start (who can blame them, and still no one suggests let's take away bitwise shifts cause of readability), and ternary operator is just crucial to have nowdays.

@Lexkane: The compiler already has optimizations that use conditional moves. We don't need a language construct to force such optimizations. For instance, the following code uses one:

func f(x, y int) int {
    r := 3
    if x < y {
        r = 7
    }
    return r
}

If you have particular instances where a conditional move is not being generated, and you think it should, open an issue with the code.

Since I use Go and Javascript in my job at the same time it has been countless times I wanted to write x ? a : b in Go programs! I should have written it down to show all theese cases to @ianlancetaylor ! It all was real programs.
Ternary operator is the one we all learn at school (not even at University) so in it's classic form it is the natural way to write and read code.
We all learned that there are three types of operators: unary, binary and ternary. Go lacks one type for no real reason IMO.
Both hands fo x ? a : b.

We all learned that there are three types of operators: unary, binary and ternary. Go lacks one type for no real reason IMO.

There's no reason that there has to be, though. 'ternary' is just an English word that means 'composed of four parts'. You could just as easily have quaternary or quinary operators, too.

Personally, I feel that ternary operators default to being annoying to read. With unary and binary operators, you can easily see exactly where everything is, but with ternary it's not always clear what goes with what, especially once you start nesting them. I can see the argument for them being cleaner in specific situations, but outside of those situations they're almost always worse. gofmt could help, potentially, but only if it was a lot more aggressive about how it reformatted code than it is. Maybe some kind of limited one could be introduced, like disallowing nesting or chaining it, but at that point I'm not sure if it's really worth it.

It has been already said that one can make the mess with the simplest set of operators. It's true that Go-code is simpler to read and write than Java or Javascript code. But it's not impossible to make it unreadable.
So ternary operator is for one-liners mostly and should be used for those cases.
Otherwise you can take "if - then - else", nest it several times and make your code a total mess. It's always up to you.
I think that people underestimate the frequency one-liner expressions are arise in the code. Sometimes they are rare but sometimes they fill half of the code written and in later case i want to have ternary operator preferably in the form it is in Javascript.

I like that in Go, control flow is generally done with statements, not expressions (function invocation being the obvious exception). Many popular languages strive for "everything is an expression," which is often fun, but imo encourages writing "clever" code. Go, to me, is all about minimizing cleverness.

By the way, another (gross) way to implement a ternary expression is:

map[bool]string{true: "", false: "s"}[nbFriends == 1]
map[bool]string{true: "", false: "s"}[nbFriends == 1]

nice trick but a way more "clever" than simple well-known ad obvious ? : .

Some classic features could be harmful but we've already get used to them. We don't even realize it.

The condition must be a bool, so sometimes we have to write

x := 0
y := x != 0 ? 1 : 2

It's not intuitive. Because in if statement you see if at first glance and you know there'll be a conditon.

In ternary expression only if you see ? will you know that it is a condition. You'll surprised and go back to read the condition again when it's complicated.

It breaks reading flow from left to right, top to bottom.

You could argue that I changed the _const_ to a _var_, but I'd be surprised if the compiler wasn't _smart enough ™_ figure out that _PORT_ is constant so IMO it makes no difference in real code.

Well, it does make a difference by losing the const-ness. Const is not just about optimization. Code further down can now mess with the value

How about: alow ternary but disalow nesting them.
I'd like that.

The only reason something ? foo : bar _seems_ readable is because we're already used to using it in other languages. But by no means is that more readable or clearer than

if something {
  foo
} else {
  bar
}

specially for newcomers for whom Go is their first language.

If readability is not better, the only possible gain from this is to write less lines of code. To me, that does not seem like a good enough reason to introduce an alternative not very intuitive construct for something we already have.
We already have if. Why add another way that's less intuitive to do the same thing?

if something {
  foo
} else {
  bar
}

'else' also breaks line of sight and readability (you have to go back and read if condition again). I would better get rid of 'else' in favor of '?'.

The only reason something ? foo : bar _seems_ readable is because we're already used to using it in other languages. But by no means is that more readable or clearer than

if something {
  foo
} else {
  bar
}

Well, it's clearer in several ways:

  • it reduces ceremony for what is essentially a trivial operation,
  • it expresses one assignment in one line,
  • it allows us to keep the variable as "const" (and thus reducing the mental burden and the possibility of messing with it later),
  • it's 5 lines less and thus makes it easier to take more of the code in.

specially for newcomers for whom Go is their first language.

Why would a language focus its expressibility on newcomers? It's like making a simplistic UI only for the computer illiterate, and thus frustrating anyone that gets beyond a certain point and misses the extra power.

@bugpowder Ternary expression is totally replaceable, and if statement is more intuitive, more expressive and more common.

@bugpowder Ternary expression is totally replaceable, and if statement is more intuitive, more expressive and more common.

What about cases in like;

println("Example bool is: ", bool ? "true" : "false")
this is really where it shines for me where you really cant use the if statement unless you want your code to look like this:

bool := "false" if bool { bool = "true" } println("Example bool is: ", bool ? "true" : "false")

The only reason something ? foo : bar _seems_ readable is because we're already used to using it in other languages. But by no means is that more readable or clearer than

if something {
  foo
} else {
  bar
}

specially for newcomers for whom Go is their first language.

If readability is not better, the only possible gain from this is to write less lines of code. To me, that does not seem like a good enough reason to introduce an alternative not very intuitive construct for something we already have.
We already have if. Why add another way that's less intuitive to do the same thing?

Back when i learnt to code, the ternary operator seemed kind of odd and complex at first, but after looking it up furter and seeing these examples such as:

true ? "it's true!" : "It's false!"
it seemed really logical and simple, but that's just me. maybe to others its suuuper complex and hard to understand.

I disagree with the Go convention and language's designers arguments here https://golang.org/doc/faq#Does_Go_have_a_ternary_form and think that it is a real missing feature in the language.

But the last sentence from a FAQ clearly explains 'why there is no ?:`:

A language needs only one conditional control flow construct.`

Ability to squeeze more logic in one line gives nothing new at all. There is already a clean way to change control flow, another one is just excessive.

Let's look at what's wrong with ternary expression. In a simple case, both of them are ok.

var a, b int
fmt.Println("Example bool is: ", a != 0 && b != 0 ? "true" : "false")
var a, b int
var c string
if a != 0 && b != 0 {
        c = "true"
} else {
        c = "false"
}
fmt.Println("Example bool is: ", c)

When things get complex(add one condition), ternary expression looks ambiguous.

However, if-else form does its job. I think a shorter form is not always the clearer form.

var a, b, c int
fmt.Println("Example bool is: ", a != 0 && b != 0 ? c != 0 ? "true" : "false" : "false")
var a, b, c int
var d string
if a != 0 && b != 0 {
        if c != 0 {
                d = "true"
        } else {
                d = "false"
        }
} else {
        d = "false"
}
fmt.Println("Example bool is: ", d)

Ternary expression is just a special case of if statement. It's not worth it to add a new syntax for a simple special case.

Yes, please, yes! I love Go and it’s focus on readability, but it comes at a price as very verbose code.
I strongly believe that ternary operator will improve the “writability” without hurting the readability. It will just make Go’s code little bit more elegant and convenient.

Speaking of ambiguity and other coding issues, I would say that developers with bad coding practices will write bad code without ternary operator anyway.

@ziflex IMHO, the best coding practice of avoiding ambiguity is NOT to use ternary expression even in a language with ternary expression.

There are traps you have to avoid so you have to learn coding pratices like

  1. do not nest it.
  2. only use it in a really simple case.
  3. when logic gets complex, rewrite it in if-else form.
    ...

These traps will bring you overhead.

@DongchengWang Some of these practices can be easily applicable to plain “if” statements.

Many responders say that ternary is replaceable and doesn’t bring nothing new.

But at the same time I see how many people use “if err := maybeError(); err != nil {}” construction which can be easily rewritten with plain “if” statements as well. But by some reason, nobody really complains about it. Why? Because it’s convenient. And probably because it was in the language from the beginning.

The only reason why we are arguing about it is that just because the operator was not there from the beginning.

Personally, there have been a few occasions where having a ternary expression would have resulted in cleaner looking, less nested code. Only a few though, so I'm not wedded to this...

To avoid the additional complexity of the syntax by using the more or less standard ?: characters, what about reusing existing language keywords like in the case of the for loop as follows?

port := 80 if production else 8080

It still allows you to chain and abuse it but only as much as you could with a traditional if/else block...

I think you should still meditate on the principles of why this language was born: simple, one obvious way to do things, orthogonal features and libraries.

I think we are at something like 70% there. Not everything is perfect.

What I like about Go so far is the fact that it lessens the argument between team members about the way of coding, about the code style each of them may use (remember various versions of PSRs in PHP?). We, at our 7-member team, never argue about each other's codes. We just focus on our goal.

Not that i don't like ternary conditional operator, but I need to object adding it and anything such to Go because I don't like the way of coding become one of our arguments.

Unneeded sugar. I think it is one of those times when we're trying to bring something that "was used in another language". Doesn't feel Go.

As noted above, there is an existing FAQ entry explaining why the language does not have the conditional operator. The issue does not have strong support. Adding a new feature to the language can never make it simpler. Once there are two ways of doing something (if or ?:), each programmer will often have to decide which form to use, which is not a good thing. In general, we don't see any new arguments here.

Therefore, this is a likely decline. Leaving open for a month for final comments.

To @ziflex 's point, I'm not sure how a verbose inline if:

if bool := operation(); bool {}

is much different from a ternary operator. It's a construct I wouldn't happily give up, but it has the possibility of suffering the same complexity of an eval ? a : b like:

func main() {
    if a := A(); !B(a) && !C(a) && D(C(a)) {
        fmt.Println("confused")
    }
}

func A() bool {
    return true
}

func B(in bool) bool {
    return !in
}

func C(in bool) bool {
    return in
}

func D(in bool) bool {
    return in
}

This serves only to illustrate that poorly written code on a single line can become as unreadable as some of the examples above. The ability to abuse a syntax, IMHO, shouldn't be reason enough to prevent a significantly usable operator from being added to the language.

However, if-else form does its job. I think a shorter form is not always the clearer form.

var a, b, c int
fmt.Println("Example bool is: ", a != 0 && b != 0 ? c != 0 ? "true" : "false" : "false")

That's a bad example, because you omitted parenthesis, which makes your example sound extremely obfuscated while it could be understandable.

I genuinely think
go var a, b, c int fmt.Println("Example bool is: ", (a != 0 && b != 0 ? (c != 0 ? "true" : "false") : "false"))

is more readable than

var a, b, c int
var d string
if a != 0 && b != 0 {
        if c != 0 {
                d = "true"
        } else {
                d = "false"
        }
} else {
        d = "false"
}
fmt.Println("Example bool is: ", d)

(Even if your example could actually be even simpler, but I assume that's not the point: )

go fmt.Println("Example bool is: ", (a != 0 && b != 0 && c != 0 ? "true" : "false"))

Although I occasionally find myself wanting a conditional within other expressions or statements, I nearly always feel like it would be a net loss to readability.

The one thing I do regret about it is the lack of a way to have a conditional initialization where both branches would not be a zero value. I know the compiler is probably smart enough to make this not wasteful, but my sense of what the abstract-machine does tells me that I'm zeroing a value and then overwriting it immediately. This is... a pretty weak argument, really.

FWIW, I don't find the ternary form of the "Example bool" examples more readable, not least because, on my display right now, they end up requiring horizontal scrolling to even see the full expression. Even doing that, I have to look back and forth a lot more often to figure out what it's doing.

The obvious answer from some of the scripting languages is to have if statements be expressions that can be assigned, and I actually really like that design for those languages, but I do not think I would like it so much for go.

I suppose there's always:

x := func() int {
    if a {
        return 1
    }
    return 2
}()

... but come to think of it, if we could streamline that, it would actually be pretty usable for circumstances like this. Something which lets us express "this function is actually merely notational, there is no need to actually generate function code, we just want a way to use return expressions in an inner block"...

The Go language is born for simplicity. Why do you want to do these fancy things? You feel that you have written a few lines of code, but it adds more complexity and confusion to the code.
So I don't support

It’s not “fancy”, it’s “simple” so it fits right in. It’s familiar because we use it in many other languages. It’s convenient because it’s a single line expression with little typing. It’s important to have because it’s a common construct we would use a lot.

Then I would rather use a list comprehension like python:
a if a>1 else b
Instead of all sorts of strange symbols, just like Rust.
I would rather write more code to express it than to use these strange symbols to omit the code.
The code is for people to read.

Please don't add new grammar functions for a situation that is not commonly used, because you are destroying Go's original intention, that is, simple and easy to read.
I think that sometimes, a lot of people are selfish, for their convenience in certain situations, or because they are spoiled by the convenience features provided by other programming languages, you have to add their favorite features in Go.
What I want to say is that Go is not your language, Go has his own way to go.
Although you can invent your own language, you can.

@Yanwenjiepy Looks like you've also become a Go "purist" that in my opinion is hindering progress.

I would rather write more code to express it than to use these strange symbols
It may be strange to you but not strange symbols for most of us who are familiar with any common C based language; C, C++, C#, Java, JavaScript, etc. They all have the ternary expression.

Please don't add new grammar functions for a situation that is not commonly used
Ternary conditional statements are actually very handy and commonly used!

Just to be picky, that's not a "list comprehension". A list comprehension is specifically something like [x for y in z] (possibly plus conditions). The use of if/else in expressions is a different feature.

I'm plenty familiar with ternary operators, and have used languages that had them for most of my life, but roughly 95% of the usages I have seen in other languages, I think would be a poor fit for what makes Go pleasant to work in. Go has tended to avoid information density of some kinds, like preincrement/postincrement operators that can be used in expressions, and I think the ternary operator has the same underlying problem; it takes too much space to think through what it does.

The compiler is reasonably smart. You can declare a value, assign to it conditionally, use it once, and expect the compiler to do about as well as it would have for a ternary expression.

@Yanwenjiepy看起来你也成了一个Go“纯粹主义者”,在我看来这阻碍了进步。

I would rather write more code to express it than to use these strange symbols
对于我们大多数熟悉任何常见C语言的人来说,这可能是奇怪的,但不是奇怪的符号; C,C ++,C#,Java,JavaScript等。它们都具有三元表达式。

Please don't add new grammar functions for a situation that is not commonly used
三元条件语句实际上非常方便且常用!
Maybe, I have a similar feeling myself. Perhaps on other issues, I am not a ‘Go purist ‘’. This is very confusing.

@seebs I can certainly respect that opinion, but for a lot of us coming from other C-based languages, "pleasant to work in" means productivity via familiarity and convenience. I could never comprehend why i++ in Go is less pleasant/convenient than i = i + 1, especially when a postincrement is ok in loops, e.g. for i := 0; i < 5; i++ {...} but it's not OK as a statement! Too much purism I say! :)

What are you talking about? i++ is perfectly allowable as a statement in Go. what's not allowed is using it in an _expression_, where it is evaluated both for a value and for a side effect.

https://play.golang.org/p/m_LbSbmT1Ar

As someone reasonably solidly familiar with C, I nonetheless find that I'm fine without the ternary operator, and I like the resulting code better. I've stopped using it as much in C, too, just like I've become more consistent about using braces on all blocks, not just blocks with more than one statement in them.

I didn't mean i++ on it's own, I meant as an expression like fmt.Printf("%d", i++) which would be a convenience for some of us.

Yes, it's definitely convenient, but it is, pretty clearly, less maintainable. People make mistakes with it, people misunderstand code using it. It is a source of bugs, and it just plain doesn't add that much value.

Yes, if I want to do x++, I have to do it as its own statment before or after the Printf. That is indeed a cost, at all. But in exchange, I get:

  • I don't suddenly have x getting the wrong values if I comment out some of the Printf calls.
  • Changes to the format messages don't break my program logic.
  • Someone else skimming the code (or me without enough coffee) won't just plain miss that the increment is in there.

It is a tradeoff, but I think it's a pretty good one. I spend some of my time trying to answer newbie programming questions, because that's part of how we develop healthier language communities. I spend a lot less time trying to puzzle through subtleties in punctuation in people's Go code than I do in their C code, and they have a lot fewer problems caused entirely by subtle typos.

I assure you, this isn't purism coming from people who don't understand or appreciate C. It's a considered decision that this language seems to get a lot of value from simply not being that complicated, and that leaves us more room to be doing complicated things with our logic since we're not spending so much of our effort parsing the code.

I hear you, but I'm not convinced on all of that. For example, the following works in Go and according to your argument, it should be the only allowed syntax:

    for i:=0; i<5; i=i+1 {
      fmt.Printf("%d\n", i)
    }

But the more popular/familiar syntax IS also allowed:

    for i:=0; i<5; i++ {
      fmt.Printf("%d\n", i)
    }

Why do you think that is?

My argument does not in fact state that only the first should be allowed. I like the second better, it's simpler and easier to read, because the increment operator is a standalone thing, not a side-effect.

Note that you can't do:

i := 0
for i++ < 5 {
    ...
}

Because Go doesn't let you put assignments, or increments, in expressions. And that may be an inconvenience sometimes, but the frequency with which it does not result in people misunderstanding an expression and not realizing it modifies values is basically 100%, which is nice.

See that's too purist to me :) Anyway, my point is, since both i=i+1 and i++ are allowed in loops, I say also allow a ternary variation for those who prefer the single-line convenience, e.g.

```Go
Port := production ? 80 : 8080

as well as the usual:

```Go
Port := 8080
if production {
    Port = 80
}

If it's about simplicity, I think the former is simpler.

What about:

Port := production++

or

fmt.Printf("port: %d\n", production++)

Both of those are also shorter using the C idiom than they would be in Go. But I would argue that in both cases, the possibility of that complexity existing makes the entire program slightly harder to understand -- you now have to be watching out for those effects all the time.

At a more fundamental philosophical level: The problem is that your solution is not only for those who prefer that "convenience". It's also imposed by force on everyone else, forever. Because everyone has to read other people's code. So people can't opt-out of a feature like this. They can't say "well, that's harder for me to maintain, so I won't use it" and not have to deal with it. They're stuck with it being part of their world. Whenever they read code that anyone else could have worked on, they have to watch out for a new set of complexities.

In practice, this "simplicity" comes at a significant cost, because it's not actually simplicity, it's just making the expression shorter. It's like the single-line braceless if; it seems like it's simpler, but then you need a second line and there's a non-zero chance that you forget to add the braces then, and pretty soon you've lost more time than you would have just putting the braces there.

I know what happens if you write:

Port := production ? 80 : 8080

A few days later:

Port := production ? 80 : test ? 4080 : 8080

But then someone realizes that the two bools are a bad choice, and fixes that:

Port := mode == "production" ? 80 : mode == "test" ? 4080 : 8080

and because it was just one line, and using ?:, people feel like making it longer is Extra Effort, and they don't fix it or clean it up. And now they've got an investment in it being that way.

And that's how you end up with ?: operations nested 15 deep, which I've seen in actual code, which should absolutely have been lookup tables.

And that's how you end up with ?: operations nested 15 deep, which I've seen in actual code, which should absolutely have been lookup tables.

"if-else" operations nested 15 deep, should also absolutely have been lookup tables, though.

Oh, certainly.

But if you have 15-deep nested if/else operations, and you convert to a lookup table, you don't feel like you've lost the "simplicity" of the single-line solution.

The problem is that your solution is not only for those who prefer that "convenience". It's also imposed by force on everyone else, forever

I couldn't disagree more because the truth is the opposite! It is actually your purist view that imposes your way of doing it and limits my freedom to choose a short-hand version. If you don't want to use it, that's your choice, but don't limit my choice!

If I can choose to write a shortened a := "freedom" instead of var a string = "freedom" then I should have the freedom and convenience of a ternary assignment.

Go tooling does a great job of standardising code formatting and I think that alone makes it quite easy to read other people's code.

The bottom line for me is, I find ternary assignments easier to read and understand because they are more naturally translated to English. I believe this is why it is so popular in many other languages. To me this:

port := production ? 80 : 8080

...translates to : "Is this production? if yes, port is 80 and if no, port is 8080"
(a simple, straight forward single assignment, even when nested)

port := 8080
if production {
    port = 80
}

This translates to: "port is 8080 (period) Oh, but if this is production, change port to 80" (second assignment).

The second is definitely NOT easier to read for me. Never has been.

If it's the ? and : that is bothering people, I'd also be happy with any alternative single line syntax.

This single-line construct works for non-computed initial values. A way to extend this to computed initial values would be great.

v := a; if t { v = b }        // non-computed initial value

v := f(); else if t { v = b } // f() not evaluated where t==true

Sadly _go fmt_ destroys many useful single-line constructs. I don't use _go fmt_ for that reason; telescoping readable compact code makes it less readable. But that's tangential.

This functionality is achievable without a ternary operator if Go 2 adds Generics

func ternary(type T)(cond bool, vTrue, vFalse T) T { 
    if cond { return vTrue } else { return vFalse }
}

Of course using this wouldn't be pretty especially if there was more than one ternary call.

As for evaluation, this may be an optimization on the compiler level.

This functionality is achievable without a ternary operator if Go 2 adds Generics

func ternary(type T)(cond bool, vTrue, vFalse T) T { 
    if cond { return vTrue } else { return vFalse }
}

Of course using this wouldn't be pretty especially if there was more than one ternary call.

As for evaluation, this may be an optimization on the compiler level.

Hum no, vTrue and vFalse will always be evaluated, I mean

ternary(3>2, func1(), func2())

will cause the call of both func1() and func2(). No compiler could know that func2() do not need to be evaluated... and it should never assume that anyway, because it's a fundamental principle that in a function call the arguments are always expected to be evaluated, before the call of the function itself. If func2() does stuff additionally to returning a value, we want this stuff done, otherwise it would be very unpredictable and difficult to comprehend.

(Contrary to a real ternary operator where the value of false is not supposed to be evaluated by principle.)
```

This functionality is achievable without a ternary operator if Go 2 adds Generics

func ternary(type T)(cond bool, vTrue, vFalse T) T { 
    if cond { return vTrue } else { return vFalse }
}

Of course using this wouldn't be pretty especially if there was more than one ternary call.
As for evaluation, this may be an optimization on the compiler level.

Hum no, vTrue and vFalse will always be evaluated, I mean

ternary(3>2, func1(), func2())

will cause the call of both func1() and func2(). No compiler could know that func2() do not need to be evaluated... and it should never assume that anyway, because it's a fundamental principle that in a function call the arguments are always expected to be evaluated, before the call of the function itself. If func2() does stuff additionally to returning a value, we want this stuff done, otherwise it would be very unpredictable and difficult to comprehend.

(Contrary to a real ternary operator where the value of false is not supposed to be evaluated by principle.)

Then the signature would be like so:

func ternary(type T)(cond bool, vTrueFunc, vFalseFunc func() T) T { 
    if cond { return vTrueFunc() } else { return vFalseFunc() }
}

I gotta admit though, this implementation is a whole lot ugly :(

There were additional comments since we declared this a likely decline (https://github.com/golang/go/issues/33171#issuecomment-525486967), but as far as we can tell none of them said anything substantially new. We agree that there are cases where the ?: syntax would be convenient, but overall it doesn't seem worth adding to the language.

-- for @golang/proposal-review

So the decision is based on "doesn't seem worth adding"?
I'm not surprised at at all... Ternary expressions would destroy the "purity" of the language, right?

Language changes are always a cost-benefit tradeoff. There is rarely an unambiguous answer.

There is not a easy explanation regarding the absence of ternary operator period.

On its surface, the refusal to implement this basic feature is analogous to arguing that bicycles can be dangerous if they're fast, and refusing to make a bike with high gears as a result. Some may get mixed up in the conversation about whether Go's language architects are right or wrong, but I'd prefer to go up a level of abstraction and consider whether languages should couple feature concerns with maintainability concerns:

If feature X could be abused and result in a rats nest of code, is that sufficient reason for feature X to be excluded from the language?

I would argue it may be, but not by itself: It should be weighed against Demand and Difficulty. If a feature is high in demand and easy to implement, it would be better to decouple the concerns by implementing the feature and presenting a way to disallow it -- and even disallow it by default.

In fact, if the demand is high enough, even difficulty is a bad reason to refuse it. Consider the class syntax in Javascript (ES2015): The language's architects really didn't want to add the feature, and it was actually quite a bit of work to add the syntax, but demand was incredibly high. What did they do? They added the syntax, knowing full well that any organization that did not want the feature could easily disallow the syntax at the linting level.

This is the appropriate resolution for the ternary operator, given the demand. De-coupling these concerns is appropriate, and solving them in a more configurable way would make the most sense. The same should happen for things like "unused variable" errors that make linting concerns into "stop-the-presses you need to fix this one thing before the program will run" crises. (Yes, I know there's a _ solution, but it's still just the case that this should be configurable)

It is a mistake to think that a language should be the product of a small number of architects that know better without a deep connection to those that are actually using the language. The call for data to prove the architects of the language wrong is admirable, but such analysis is unnecessary. Just look at the size of this thread: There is demand.

Unfortunately, ignoring demand is what leads to competing products: In this case, this is how languages get forked. Do you want to get forked? (To be clear: This is a prediction, not a threat.
I'm definitely not forking a language.)

@dash This issue is closed, and I am not going to argue it, but I'd like to correct what I believe is a misrepresentation. You're implying that Go is a language that is "... the product of a small number of architects that know better _without a deep connection to those that are actually using the language_." This is certainly not true. Everybody in the Go Team, and certainly the "architects" are writing Go code every day, and have been writing a substantial amount of it ever since 2007. We also interacting with other Go users on an almost daily basis. We absolutely have a deep connection with those that actually use the language, which is us - among many others.

I'm not one of the architects, and I use the language heavily, and I regularly encounter situations where I would almost certainly use a ternary operator if it were available. And then I read my code later, and I think about it, and I'm glad it's not there. YMMV.

I don't think that making things like this, or the unused variable warnings, "configurable" would make my life as a developer easier; I think it'd make my life as a developer harder.

I'm not one of the architects either, and I use the language heavily too, and I regularly encounter situations where I would almost certainly use a ternary operator if it were available. And then I read my code later, and I curse the few people who deny us this useful feature!

Same here, I use Go everyday and would need it everyday and I am convinced it would make my code clearer and even more robust.

By the way, in the proposal of "Reword FAQ answer"

using ternary operator instead of conditional statement (not expression) for brevity;

"Brevity" is mentioned like if it is a bad thing. Brevity helps readability. The whole idea of a readable code is that it is "straight to the point" of what it actually does. Not like affecting 8080 or -1 to the port, and then 80 later in the code because this is production.

I think there's little chance now of Go getting a ternary operator, not just because the Go team have consistently argued against it but because (judging by the emoji voting) around 60% of the community are against it as well.

However, if Go does eventually get generics, I think the team should seriously consider adding a ternary function to the standard library notwithstanding that there'll be no short-circuiting except possibly by way of compiler optimization.

If they don't do this, then the 40% who are in favor of some sort of termary operator/function (myself included) will immediately write their own. This will create a readability and maintenance nightmare because different names will be chosen (Cond, Iff, Iif, Pick, Choose, Tern etc.) and they'll be in packages with different names.

If it's added to the standard library instead, then this fragmentation won't occur as everybody in favor will use the standard version and those who don't like it will at least know what it does.

func ifThen(condition bool, ifTrue,ifelse interface{}) interface{}{
if condition {
return ifTrue
} else {
return ifelse
}
}

It feels to me that this discussion regarding ternary operator in a some cases comes down to a "solution for a different problem".

The lack of default values in functions results in people writing code as:

if elementType == "" {
    elementType = "Whatever"
}
//  times X ...

with people wanting to simply this as:

elementType = elementType == "" ? "Whatever" : elementType
// times X ...

Or

func DoDesign( elementType string = "Whatever" )

So a ternary operator tries to solve a issue, that is related to a other problem. While the more standard Go version is indeed more readable, it gets less readable when you are dealing with 4 or 5 of those in a row.

One also needs to question, if readability is served when people start building more and more their own "solutions" as shows by @ArnoldoR . One of the issues that plagued Javascript is the growth of "solutions" for missing functionality, what resulted in some projects importing NPM packages left and right. Popular packages like SqlX are more a sign of missing features in Go.

Readability is one thing. But having to write sometimes 20 lines that are more or less can be contained in 5 lines. It piles up on any project.

If the issue is that ternary can be misused to created unreadable code, especially nested ternary operators, then put a limit on it. I think that most people in this topic will have no issue if a ternary operator is limited to only "one level" and the compiler stops you from deep level operators.

We know people are going to abuse the hell out of generics to implement them anyway. So why not provide a official version, then you can limit the abuse of it. But those wild functions as we see above, will only grow more popular over time, as we have seen in the Javascript community. And when the genie leaves the bottle, there is no more controlling it.

Was this page helpful?
0 / 5 - 0 ratings