-
Notifications
You must be signed in to change notification settings - Fork 17.6k
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 ??
operator to select first non-zero value
#37165
Comments
None of your examples require the short-circuit behavior. I.e., |
Yes, that's fair, although so far generics are still only theoretical. |
For language change proposals, please fill out the template at https://go.googlesource.com/proposal/+/refs/heads/master/go2-language-changes.md . When you are done, please reply to the issue with Thanks! |
Intermediate/advanced. Long-time Go user, but not a language dev.
Extensive Python, JavaScript, and PHP. Scattered others.
Incrementally harder, since there would be one more operator to learn, but the operator has syntax and semantics similar to the existing
Not AFAICT, but ternary operators are frequently proposed.
Rather than creating a full-blown ternary, it only allows the equivalent of (x != 0) ? x : y or || in languages that have weak-typing of booleans.
It helps anyone who has to write or read default configuration. It helps writers because
Add
After the "Logical operators" section, there would be a section "Coalescing operator" with text like:
In the spec,
Go provides a convenient way to select the first non-zero value in a series. For example
Yes.
Before:
After:
Before:
After:
The costs are that the
The language grammar would change, so essentially every tool would need to learn the new grammar. :-(
Should be negibile.
Negibile.
See spec changes above.
No.
See above.
While it could be abused for control flow, I don't see that as being very likely, since
No.
It has minor effects on the case of wanting to return the first non-nil error in a write and flush operation:
No, but if there were generics, a user could write a non-shortcircuiting @gopherbot please remove label WaitingForInfo. |
Although I'm a supporter of adding some sort of short-circuiting ternary operator or built-in function to Go, I'm not convinced that this proposal goes far enough in that direction to be worth doing. If I'm understanding it correctly, what you're proposing is that So stuff like this would become possible: a, b := 0, 1
c := a ?? b // c is assigned 1 as a is 0
a, b = 2, 3
c = a ?? b // c is assigned 2 as a is non-zero
s, t := "", "one"
u := s ?? t // u is assigned "one" as s is empty
s, t = "two", "three"
u = s ?? t // u is assigned "two" as s in non-empty As such it's equivalent to writing the following in a C-like pseudo-code: // d and e are expressions of someType (non-boolean)
someType f = d != zeroValue(someType) ? d : e; This is really quite limited compared to a 'full-fat' ternary operator where the condition can be any boolean expression. So, whilst I congratulate you on a novel proposal, I'm finding it difficult to get enthused about it. |
Note that this is similar to Many languages have similar concepts and are widely used, however I don't see the use as much in Go. Go relies less on classical sentinel values than other languages, and instead uses errors to denote when errors happen, rather than returning a sentinel value. It still does have it's uses of course as you've shown, but I still think coalescing is a bit limited in the context of Go. |
Yes, exactly. :-) I believe this covers a huge amount of the situations where ternary is actually useful without allowing the complexity of ternary that the Go authors specifically don't want. Your examples use single letters, so it makes the
etc. |
Yes, thanks for pointing that out. I realized that I had written the wrong thing before when I was writing up the change proposal template but didn't go back and correct it. |
For future proof (if this is the right scenario), we should avoid I have often assume empty string/out of range/garbage value as either valid or not valid, how can we be sure the user supply value/input is correct? Invalid value could also cause runtime errors that I prefer to stick to the current way to handle such cases with regexp or custom validations in a function that can be change without breaking change and less noise.
In my opinion, I would wish to avoid less boilerplate for this case. I guess it was possible for JavaScript and PHP as they are scripting languages that can benefits from smaller file size transfer over the slow connectivity and seem fragile for Go, and opportunity to make an assumption that it work in my specifications/requirements. I see Github CLI as one of an example: |
TLDR; There are many developers who simply want to solve short-term problem using |
There is a Go proverb, "Make the zero value useful." I believe this makes the zero value easier to use because you can easily write code to test for the zero and use a good default if the zero is provided. Of course there will always be code that needs better validation like "ensure timeout is a positive value" or "port string must begin with colon". This just makes the simple case more simple. The advantage of this vs. a ternary is that a) it's actually simple (IMO, ternary is hard to read) and b) it cannot be abused to create unreadable monstrosities like nested ternaries ( |
There don't seem to be any examples above that require lazy evaluation. If lazy evaluation is not an essential part of this, then it seems that it would be possible to write a generic zero-coalescing function. Using the syntax from the current generics design draft, it would look like func Default(type T comparable) (a, b T) T {
var zero T
if a != zero {
return a
}
return b
} This is longer to write than |
Nitpick, do you need the type to be comparable? Types that aren't fully comparable, such as functions, can still be compared to the zero value.
Edit: This doesn't compile because type T = func()
func Default(a, b T) T {
var zero T
if a != zero {
return a
}
return b
} Making a constant of zero value for arbitrary type T seems to be impossible? |
|
As proposed, |
Sorry, but is this then basically the nullish coalescing operator (??) from javascript? https://github.com/tc39/proposal-nullish-coalescing The problem with javascript is that the || operator evaluates empty strings '' and 0 as false and not as strings and ints. This is a "feature" of JavaScript which leads to the use case of adding a nullish coalescing operator. GO does not follow this scenario when it comes to evaluation, thereby the use for a ?? operator seems pointless to be honest. If I want to return a non nil value I would just use |
Please read the proposal before criticizing:
Yes, it is similar to JavaScript, which in turn has taken an operator from C#/PHP. Your input that you don’t want a new operator is valuable. Giving your criticisms when you clearly haven’t read the proposal is not. |
I'm merely explaining why I personally don't think this proposal will add value to the language as is. I think that was pretty clear in my original answer. Thank you. |
Thank you for a well considered feature proposal. The current Go language has conditional flow control, notably switch and if. However, among serious programming languages, Go is unique in its lack of a conditional expression. With respect to this common programming need, Go comes up short by comparison. The proposed feature would greatly improve Go coding and readability, while taking nothing away from simplicity or compatibility. |
??
operator to select first non-zero value??
operator to select first non-zero value
An example where short-circuiting would be important would be along the lines of
The behaviour of the usual 3-part ("ternary") conditional operator is only important here in that it illustrates how this operator is simpler: it coalesces I agree with the sentiment of "make zero useful", and the conciseness of My idea here is to read these as "use left operand provided the assertion is met, otherwise use the right operand": i := GetNum() ?? GetDefNum() // default
i := GetNum() ??{_!=0} GetDefNum() // equivalent
p := GetPtr() ?? GetDefPtr() // default
p := GetPtr() ??{_!=nil} GetDefPtr() // equivalent
s := GetStr() ?? GetDefStr() // default
s := GetStr() ??{_!=""} GetDefStr() // equivalent
s := GetStr() ??{_!="none"} "" // any string except "none"
i := LinuxSysCall() ??{_>=0} -1 // emulate libc; system calls return -errno on error
f := GetFloat() ??{min<=_ && _<=max} math.NaN() // defer handling range error
a := FindAnswer(TheQuestion ??{_!="" && _!="where's Waldo"}
os.Getenv("thequestion") ?? // still not Waldo
MyDB.Lookup("thequestion") ??{} // default _!=""
"what is six times nine") |
Would prefer a genuine ternary operator |
func cond[T any](match bool, ifVal, elseVal T) T {
if match {
return ifVal
}
return elseVal
}
absoluteDoubleN := cond(n > 0, func() int { return n*2 }, func() int { return n*-2 })() |
@seankhliao does this have to wait for Go2? It's not a breaking change, since it adds a new token from a byte sequence that's currently invalid. @carlmjohnson effective, but rather verbose. Does the current compiler elide the trampolines and extra call frames? Or maybe we approach this from another angle: some form of auto-lambda for function parameters. func cond[T any](match bool, ifFn, elseFn lambda T) T {
if match {
return ifFn();
}
return elseFn();
}
absoluteDoubleN := cond(n > 0, n*2, n*-2) I would initially allow this only for parameters of functions that can be fully inlined. I suggest that until Go2, |
@kurahaupo We conventionally label all language changes as v2. We don't currently expect that there will ever be a Go 2 in the sense of adding breaking changes; see https://go.googlesource.com/proposal/+/refs/heads/master/design/28221-go2-transitions.md . |
As I said over at #60204 (comment), I think that if we do add an operator it should be |
On the subject of auto-lambda, it may be interesting to read #37739. |
The argument for a new operator is so you can tell at a glance if a variable is a Boolean or not. Then again in JavaScript it doesn’t seem to present much problem in practice. |
That is one argument, and it's a good one, but after all if you just look at |
Though I am somewhat in favor of a new operator over reusing |
Would |
I don't see what the semantics of an expanded |
Return the first zero or last nonzero value. It's what other languages with "expanded" |
How would a && b == c bind? |
I would presume that nothing else changes. |
If a b and c are all eg strings, I would want (a && b) == c, but if a is a bool and b c are strings, I want a && (b == c). |
On Sun, 4 Jun 2023, 14:07 Carl Johnson, ***@***.***> wrote:
If a b and c are all eg strings, I would want (a && b) == c, but if a is a
bool and b c are strings, I want a && (b == c).
Oh heavens no, I want to be able to parse an expression in my head BEFORE I
go away and look up the object declarations.
|
Marking this obsoleted by #60204. |
This proposal has been declined as obsolete. |
This proposal would appear to be significantly broader than #60204:
So I am unclear why "obsoleted by" wasn't in the opposite direction. If there are other reasons to decline this proposal, could we have some feedback on them please? |
True.
#60204 applies to any comparable type, not just string.
In the Go world that is a minor advantage. Adding a new operator is a language change that has a much much higher bar than adding a new function to the standard library. Supporting short-circuit evaluation simply isn't a strong enough argument for adding an operator. Sorry. |
It is often asked why Go does not have a ternary operator. The Go FAQ says,
However, technically Go has a second form of control flow:
I believe that many of the usecases that people want a ternary operator for could be covered by adding a
??
operator that is similar to&&
but instead short-circuit evaluates non-boolean expressions while the resulting value is a zero-value.For example, these two snippets would be identical:
Another use case might be
In general,
??
would be very useful for setting default values with less boilerplate.Another use for
??
might beSome rules:
??
should only work if all expressions evaluate to the same type (as is the case for other operators), and??
should not work for boolean types, since that would cause confusion in the case of a pointer to a bool. If/when Go gets generics, you can trivially writefirst(ts ...T) T
, so the operator is only worth adding to the language if it has short-circuit evaluation.In summary, ternary is notoriously unclear, but
??
would not be any more unclear than&&
. I believe it would be more clear than an equivalent if-statement since it would more clearly express the intent of setting a default, non-zero value.The text was updated successfully, but these errors were encountered: