-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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 syntax to repeat a Condition check for different StatementLists #27075
Comments
@smyrman Thanks for the ack. Question about They seem to add a lot of visual noise. Why not instead just evaluate whenever |
@mikeschinkel, yes, an explicit point for where to reevaluate the condition is the point of
It might work for the error case, but not sure it would work well for every case as Here is an example where assignment evaluation would not be enough: func f(deadline time.Time) (*Result, error) {
var err error
var r *Result
break err != nil || deadline.After(time.Now()) {
step:
r, err = Step1()
step:
r, err = Step2(r)
step:
r, _ = Step3() // let's imagine it's not safe to abort here.
r, err = Step4(r)
step:
return r, nil
}
if err == nil {
return nil, ErrTimeout
}
return nil, fmt.Errorf("f failed: %s", err)
}
|
Maybe I am missing something, but it seems since the condition is placed in the
Those feel like an edge case that should be handled with explicit For me the |
When does |
@smyrman Give me a full example in how you would use it and I'll answer. |
The general answer is every time it's run. The I don't see a way that the step can be avoided for this syntax. The collect suggestion in #25626 don't need the step keyword because it's collecting only one variable and not allowing a generic condition check. I think this is a trade-off. You can avoid the step keyword with the semantics of the collect statement, or you can have a generic conditional check and not be able to avoid it. |
I do think you have helped me come up with more use-cases where this statement could potentially be useful, other than error handling:
func f(deadline time.Time) (*Result, error) {
var err error
var r *Result
bar := pb.StartNew(4) // https://github.com/cheggaaa/pb
break err != nil || deadline.After(time.Now()) || bar.Increment() == 3 {
step:
r, err = Step1()
step:
r, err = Step2(r)
step:
r, _ = Step3()
r, err = Step4(r)
step:
return r, nil
}
if err == nil {
return nil, ErrTimeout
}
return nil, fmt.Errorf("f failed: %s", err)
}
|
I think you want However I much prefer the collect/try construct which you've branched this from. |
@smyrman Sorry, I did not realize your OP included an example. I was multitasking and so was not careful enough. I think, as you are realizing, what started as a solution for tedious error handling is trying to become a general purpose construct. It might be trying to do too much. But I'll humor you and assume it makes sense to do this route. (Ironically, even though @networkimprov doesn't like this proposal, his comment helped me conceptualize what you are trying to do.) If the And if the condition is time consuming then you should restructure your code so that you do not use a time consuming condition with this construct. Instead, use an if statement where you want to evaluate it. |
@networkimprov Would you mind elaborating as to why you like the collect/try construct better? |
Just my two cents so take it as you would like but I think it's an unneeded feature. Furthermore, if |
I suppose the separate lines with
Also I want a way to flag fatal errors and indicate a fatal error handler... |
I think the syntax could be in many different ways, and is probably not an important part of the proposal. I want to show a syntax so you can relate to the feature. The important part of the proposal is what it says on the top:
See the Go spec for details, as it does a great job of explaining those statements once you understand the declaration language. Maybe it would make sense to steel even more from the ForStmt, including
I feel that is getting pretty complex, but maybe it's implementable. I still feel the collect proposal is more suitable for "automatic" recalculation. |
Optimizing complexity is part of what a compiler does. So I think it is very doable, unless someone better at compilers than me can point out why not. |
This is not merely alternate keywords; try indicates where condition is tested after results are computed. I can't tell where it's tested in the OP.
And syntax strongly affects the reactions of your audience, so make it appealing sooner than later. |
I agree, but I am not sure, if the proposal is accepted, that a syntax should be included other than to serve as an example. Lets talk about the syntax, which I claim is not important. This is a completely different syntax that still implements the same proposal text: Let's say we added a func f(deadline time.Time) (*Result, error) {
var err error
var r *Result
if err == nil && deadline.Before(time.Now()) {
r, err = Step1()
} repeat {
r, err = Step2(r)
} repeat {
r, _ = Step3()
r, err = Step4(r)
} repeat {
return r, nil
}
if err == nil {
return nil, ErrTimeout
}
return nil, fmt.Errorf("f failed: %s", err)
}
This gives a completely different feal. Syntax matters when it's implemented, but before that we need to know if the semantics makes sense, because if they don't, the syntax won't neither. |
Be aware that many gophers are adamant about the necessity of 3 lines of ceremony after each function call, and do not agree that it hurts readability. #22122 I like what you're trying to do, but you have to explain how people are dying (suffering is insufficient) under the current regime if you hope to change it. And again, it is not clear in the OP when the condition is tested. I tried to clarify that with my use of |
Sure, your syntax example is perfectly fine, and woul implement the proposal. |
Proposed func f() (*Result, error) {
var err error
var r *Result
break err != nil {
step:
r, err = Step1()
step:
r, err = Step2(r)
step:
r, _ = Step3() // ignore errors here
r, err = Step4(r)
step:
return r, nil
}
return nil, fmt.Errorf("f failed: %s", err)
} vs current func f() (r *Result, err error) {
if r, err := Step1(); err != nil {
return nil, fmt.Errorf("f failed: %s", err)
}
if r, err = Step2(r); err != nil {
return nil, fmt.Errorf("f failed: %s", err)
}
r, _ = Step3(r) // ignore errors here...
if r, err = Step4(r); err != nil {
return nil, fmt.Errorf("f failed: %s", err)
}
return r, nil
} |
@cznic, see this for an example of current: #25626 (comment) Also see my comment above for a better example of proposed. |
Did a large update of the description, also included your example syntax @networkimprov. |
Also added a new example syntax that extends the for-loop by adding a This allows for all of the semantics contained int the first syntax example, and more. |
Again, all of this seems like adding unnecessary complexity to the language when the same could be achieved with much simpler syntax. I look at the proposed syntax and just them very hard to grok compared with how we can already code today in Go. I'd much rather see existing syntax used with some added semantics and put the burden on the compiler to sort it out rather than burden the developer with all this. #fwiw #jmtcw |
Some meandering observations. One nice thing about this vs many of the error handling proposals is that the checkpoints are clear and explicit. The semantics of the repeated evaluation isn't fully specified, e.g. around variable scoping and binding. But it seems like the intent is that it operate in a roughly closure-like manner. And indeed, if you were trying to de-sugar this programmatically, one natural way is with closures: break E {
S1
step:
S2
step:
S3
} translates to: var exit bool
f := func() {
exit = E
if exit {
panic(0) // value unimportant
}
}
func() {
defer func() {
if exit { recover() }
}
S1
f()
S2
f()
S3
}() (This isn't quite perfect, because if Sx panics, the stack trace will be slightly different. But it is close.) And with the There's one thing you can't do in a closure, which is cause the calling function to return and provide its return values. And there's no obvious way to add such a thing, and you wouldn't want to; local control flow is better. |
I like those observations @josharian. This started as syntactic sugar for closures, and perhaps that's all this is. On the same note, isn't switch-case just syntactic sugar for if/else semantics though? Yet I find the type switch one of the really useful features of the go language. I thing that the syntax suggestion that is furthest away of just providing syntactic sugar, is probably the |
The draft proposal in https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md seams much better suited for error handling than this proposal. I am closing it. |
Thank you for opening this, it is a worthy idea, and should have seen at least as much support as the proposal you branched from. I am developing a revision of the check/handle concept, the #id/catch Error Model #27519. |
Background
This proposal was originally suggested as comments on #25626, but is probably different enough to deserve it's own evaluation. Special thanks to @mccolljr, @smasher164 and @mikeschinkel for the inspiration.
There has been many suggestions to help with error handling. This suggestion could also help with error handling, but it's not limited to that use-case. It's a pretty generic syntax extension that may have many use-cases where it could simplify code. It could also be that it's not worth it, or that other proposals like collect in #25626, or other ones I have not read, would do more for the language. Still I believe this proposal is worth exploring.
Proposal
The proposal is to add syntax to the Go language to support the cases where you want to repeat the same Condition check for several different StatementLists. This is in opposition to the ForStmt where you want to repeat both the Condition check and the StatementList.
I expect the Go team to be better than me on choosing a syntax, so any syntax listed in this proposal are merely examples of possible ways to implement the semantics above. As of yet, I don't consider any of the syntax suggestions in particular to be part of the proposal; The proposal is to introduce the semantics.
Use-cases
There is a lot of things that is easy to do with a for-loop, that could be useful to apply for a long list of statements as well. Some examples could be:
In the example below we will look at the specific use-case of avoiding repeating error handling, and for detecting a timeout.
Example Go1 code to improve
The constructed code below does several different checks at a periodic basis:
The example is constructed, but imagine that Step1-Step4 are not easily compatible with a for-loop. A real use-case that springs to mind, is network protocoll setup routines (that looks way more complex). Some protocols requires a series of hand-shakes and message passing which do not fit well in a for-loop. Generally the results from the first step is always needed in the next step, and it's necessary to somehow check if we should continue or not after each step.
I won't deny there is also defiantly better ways the code example could be written with such restrains in Go 1, including:
Plausible new syntax
There are many plausible syntax variations that would implement the proposal. Here are some examples. Feel free to comment on them, but I don't consider any syntax in particular to be part of the proposal yet. Because the listed syntax proposals reuse existing statements, the complete set of semantics, as in what you can possibly do with it, is also affected by the choice of which statement to expand.
Break-step syntax example
Let's first show the one that was originally listed in this proposal that, perhaps somewhat strangely, extends the break statement to do a conditional break. the idea is that the block after the conditional break statement continues to run StatementLists until the check is evaluated to true. The evaluation is carried out at the end of each step.
Much like the SwitchStmt, this syntax does not really expand what you can express with the language, but it offers a convenient alternative to the IfStmt for a specific set of use-cases.
@networkimprov finds the syntax more clear, and it's defiantly less verbose, if step is replaced by a
try
statement that is placed before each statement that should result in a recalculation once completed:for-loop semantics example
A completely different syntax that would implement the proposal, is to extend the for-loop with some sort of re-check statement; a
continue
statement that continues the next iteration of the loop at the position where it's located. For the sake of the example let's use the syntaxcontinue>
, which is expected to read something like "continue here".Because it's extending the for loop, this could let you do more things as well, like ranging over a channel and send them to alternate functions:
Given the channel can be closed, the Go1 syntax for that would need to be:
if / repeat syntax example
Yet a different way the proposal could be implemented, is by adding a
repeat
keyword to the IfStmt, that would, repeat the same check. If one repeat evaluates to false, all subsequent repeats may be skipped.Proposal updates
UPDATE 2018-08-19: Rewrote the description try to better justify why the semantics may be useful. At the same time I have thrown in a more complicated example than just doing the normal
err != nil
check, and added several different examples of syntax that could potentially implement the proposal. Preliminary spec for the break-step semantics removed since there is now no longer just one syntax example.The text was updated successfully, but these errors were encountered: