-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: add a ternary conditional operator #33171
Comments
@gopherbot, add label Go2, LanguageChange |
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. |
In #31659 you made what I thought was a very good counter-suggestion of having a built-in 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 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 |
In my opinion, to write more code (just a few lines) is better than to figure out the rule of Besides, a ternary conditional operator can be easily abused when people write multiple nested |
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 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 A built-in |
@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:
is:
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 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 |
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 |
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. |
@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:
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 |
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. |
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. |
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] |
See that's too purist to me :) Anyway, my point is, since both Port := production ? 80 : 8080 as well as the usual: Port := 8080
if production {
Port = 80
} If it's about simplicity, I think the former is simpler. |
What about:
or
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 I know what happens if you write:
A few days later:
But then someone realizes that the two bools are a bad choice, and fixes that:
and because it was just one line, and using And that's how you end up with |
"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. |
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 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" 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.
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
Of course using this wouldn't be pretty especially if there was more than one As for evaluation, this may be an optimization on the compiler level. |
Hum no, vTrue and vFalse will always be evaluated, I mean
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:
I gotta admit though, this implementation is a whole lot ugly :( |
There were additional comments since we declared this a likely decline (#33171 (comment)), but as far as we can tell none of them said anything substantially new. We agree that there are cases where the -- for @golang/proposal-review |
So the decision is based on "doesn't seem worth adding"? |
Language changes are always a cost-benefit tradeoff. There is rarely an unambiguous answer. |
This comment has been minimized.
This comment has been minimized.
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 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 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. |
@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"
"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{}{ |
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:
with people wanting to simply this as:
Or
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. |
IMHO ternary operator is SO simple to implement that it shouldn't lead to such vast discussions :) If you want examples I have one: https://github.com/kahing/goofys/blob/master/internal/dir.go#L589 - with the ternary operator it could be: en = &DirHandleEntry{
Name: *child.Name,
Inode: child.Id,
Offset: fuseops.DirOffset(offset) + 1,
Type: (child.isDir() ? fuseutil.DT_Directory : fuseutil.DT_File),
} without it's en = &DirHandleEntry{
Name: *child.Name,
Inode: child.Id,
Offset: offset + 1,
}
if child.isDir() {
en.Type = fuseutil.DT_Directory
} else {
en.Type = fuseutil.DT_File
} I highly doubt that the latter can be considered more readable. |
How about the following? entryType := fuseutil.DT_File
if child.isDir() {
entryType = fuseutil.DT_Directory
}
en = &DirHandleEntry{
Name: *child.Name,
Inode: child.Id,
Offset: offset + 1,
Type: entryType,
} Or this one? en = &DirHandleEntry{
Name: *child.Name,
Inode: child.Id,
Offset: offset + 1,
Type: fuseutil.DT_File,
}
if child.isDir() {
en.Type = fuseutil.DT_Directory
} This is assuming that you don't just create a function that handles the conversion in one line for you, of course. |
Still undoubtely less readable or safe than a ternary operator. Maybe even less efficient (field affected twice if child.isDir()). |
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:
Solution 2:
Solution 3:
Solution 4:
Solution 5:
The text was updated successfully, but these errors were encountered: