proposal: spec: always branch for conditionals
Go Programming Experience
Experienced
Other Languages Experience
So many
Related Idea
- [ ] Has this idea, or one like it, been proposed before?
- [ ] Does this affect error handling?
- [ ] Is this about generics?
- [X] Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit
Has this idea, or one like it, been proposed before?
I don't think so.
Does this affect error handling?
No.
Is this about generics?
No.
Proposal
create an "always" branch for conditionals. the scope of always includes the scope of if-else so that variables declared and assigned in the scope of if-else appear in the scope of "always". in some cases, this removes the need to nest if-else logic inside of else blocks.
this...
if a, err := f(b); err != nil {
...
} else {
if b, ok := bar(a); ok {
...
}
a.foo()
}
becomes...
if a, err := f(b); err != nil {
...
} else if b, ok := bar(a); ok {
...
} always {
a.foo()
}
Language Spec Changes
addition of an "always" block to if-else sequences.
Informal Change
always blocks allow you to perform operations on variables declared and assigned in if-else statements.
Is this change backward compatible?
yes.
Orthogonality: How does this change interact or overlap with existing features?
no.
Would this change make Go easier or harder to learn, and why?
easier. cleaner syntax. less nesting is always good.
Cost Description
trival.
Changes to Go ToolChain
No response
Performance Costs
No response
Prototype
No response
Related Issues and Documentation
- proposal: Go 2: consider if and switch as an expression #68413 (closed)
- proposal: Go 2: extend "goto" - statement #53074 (closed)
- proposal: spec: else statement in assign statements #56895 (closed)
- proposal: Go2: error handling shortening #62378 (closed)
- proposal: spec: catch error model, a rethink of check/handle #67859
- proposal: Go 2: remove the "if" keyword and use "err?!" to avoid error handling boilerplate #33067 (closed)
- proposal: spec: support new form of switch statement during variable assignment which jumps to function-wide case blocks #65019 (closed)
- proposal: Go 2: Error handling via conditional assignment operators #59664 (closed)
- proposal: Go 2: Single line `if` variant for returns and other jump statements #62434 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
How does "always" differ from "else true"?
I don't understand the example. It boils down to
if c1 {
...
} else {
if c2 {
...
}
f()
}
This converts to
if c1 {
...
} else if c2 {
...
} always {
f()
}
For the converted code to be the same as the original code, it must be the case that the always block is only executed if c1 is false, and is executed whether c2 is true or false. I don't understand what always means.
I believe that the intent is to add a new keyword always. That will not be backwards compatible. Preexisting variables/functions can be named always and those would no longer be permitted. If the intent is not a new keyword, can you explain how always would work.
I'm also finding the OP proposal confusing. It breaks the identation logic as can be seen in the example. It is unexpected in that it looks much like a Python finally but seems to apply only to its immediate preceding block...?
the proposal is for a new keyword, but one that is only valid in the context of an if / else block and not as a "condition" of "if" or "else if"... thus it doesn't conflict with variables named "always".
@ianlancetaylor the example does not reduce to that simplified conditional because conditions in go can be used to declare and assign variables within the scope of the if/else block.
if a := fn1() ; a {
fn2(a)
}
creates variable "a" that is valid for the scope of the conditional block.
in this...
if a := fn1() ; a {
fn2(a)
}
fn3(a)
fn3(a) is invalid because the scope of a has already disappeared.
the ability to declare variables for only the scope of a sequence of if/else conditionals leads to clean, sequential, maintainable, readable code until the programmer needs to perform some work on or with a.
the proposal is not for a "finally" but for an "always" such that
if a, err := f(b); err != nil {
...
} else if b, ok := bar(a); ok {
...
} always {
a.foo()
} else if c, err := a.bar(); err !=nil {
...
} else {
return c
}
would also be valid.
this allows us to "do some work" with "a" (which entered the scope through a conditional assignment) before proceeding with the subsequent branches of the conditional and without introducing a nested conditional scope in an "else" block.
fwiw, i'm not married to "always". "next" or "do". "and" makes sense, but conflates with the boolean operator. perhaps "with", which is pythonic i suppose.
@jkassis Thanks, but that doesn't answer my question.
My question is: when does the always clause execute?
Your original post says that this
if a, err := f(b); err != nil {
...
} else {
if b, ok := bar(a); ok {
...
}
a.foo()
}
is equivalent to this:
if a, err := f(b); err != nil {
...
} else if b, ok := bar(a); ok {
...
} always {
a.foo()
}
The first code sequence calls a.foo() if f(b) returns a nil error. It calls a.foo whatever bar(a) returns.
If the second code sequence is equivalent, then the always clause is not executed if the first condition (err != nil) is true, and the always clause is executed if the second condition (ok) is true.
So when precisely is the always clause executed?
@ianlancetaylor ... oh my goodness. i see your point and i did make a mistake in my example. please forgive. this example is to the point...
if a, err := f(b); err != nil {
...
} else {
a.foo()
if b, ok := bar(a); ok {
...
}
}
would be...
if a, err := f(b); err != nil {
...
} with {
a.foo()
} else if b, ok := bar(a); ok {
...
}
noting that i changed the "always" keyword to "with" as per the evolving conversation.
Can there be multiple "with" keywords? Or does it depend on where it is located?
definitely, though a with after a with doesn't make much sense.
here's a practical example...
// start the client pool
var clientPool *ClientPool
if poolID, err := GetPodIdx(s.Name); err != nil {
s.ErrorCheck(http.StatusInternalServerError, fmt.Errorf("could not get poolID from podName: %v", err))
return
} else if clientPool = ClientPoolMake(s.Ctx, netConf, 1024, poolID); false {
} else if err := clientPool.Start(); err != nil {
s.ErrorCheck(http.StatusInternalServerError, fmt.Errorf("could not start client pool: %v", err))
return
}
becomes...
// start the client pool
var clientPool *ClientPool
if poolID, err := GetPodIdx(s.Name); err != nil {
s.ErrorCheck(http.StatusInternalServerError, fmt.Errorf("could not get poolID from podName: %v", err))
return
} with {
clientPool = ClientPoolMake(s.Ctx, netConf, 1024, poolID)
} else if err := clientPool.Start(); err != nil {
s.ErrorCheck(http.StatusInternalServerError, fmt.Errorf("could not start client pool: %v", err))
return
}
obviously, you can also put more than one statement in the with clause, which is an extra advantage.
You can put more than one statement in an else if clause too, sort of...
if a, b := 1, 3; a == b {
panic("error")
} else if func() bool {
a++
b--
return false
}() {
} else if a == b {
print("ok")
}
that's true, but less readable imo.
This is syntactic sugar for a nested else clause. The emoji voting is not in favor. Therefore, this is a likely decline. Leaving open for four weeks for final comments.
How much emoji is required?
with{...} vs else if func () bool {... ; return false }(){} gives me butterfly-mojis!
As a former Google Borg-man, I'm sure others would feel the same.
@rebeccajae can you give me some :heart:
@adamrogoyski can you give me some :heart: too?
How much emoji is required?
There is no specific requirement, but this proposal is all thumbs-down and no thumbs-up.
By way of comparison, in Go 1.21 we had a fairly minor change to the language: adding a clear builtin function. That proposal, #56351, had 122 thumbs up and 4 thumbs down.
wow. so many thumbs down. i suspect because the original example was in error. i updated the proposal title and content, but suspect i won't get the votes. can you please point me to the relevant package. i'll provide the PR.
was looking through the "clear" proposal... how would i locate the PR for code associated with that?
@jkassis This is getting off-topic, but you can search for the gopherbot comments saying "Change https://go.dev/cl/... mentions this issue". In this case the main change is: https://go-review.googlesource.com/c/go/+/453395
This proposal is syntactic sugar, so several places need changing. The compiler parser in cmd/compile/internal/syntax. The internal IR may need changing in cmd/compile/internal/ir, I'm not sure. Something will need to translate the new syntax into existing constructs somewhere. Then there is separate code that needs changing in go/parser, go/ast, go/print, and probably go/types.
Note that writing a pull request for this proposal is not going to make us any more likely to accept it. We are currently path to declining this proposal.
No change in consensus.