-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: Go 2 : add support for conditional statements #25582
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
Comments
This might reduce the amount of boilerplate, for errors at-least, but it also creates a lot of confusion. |
I admit that this has the potential of making things way too confusing, which might be the ultimate point I am trying to get to. That trying to fix this issue causes more harm than it solves. However I should also point out that even things considered wonderful in Go like interfaces and structs embedding can be abused if people wander off from standard convention. Also I should also point out that this adds the ability to program generic behavior with regards to how statements interact with interfaces. especially in code that isn't our own. In other words it adds a generic function you can call on all interface{}s. Considering that we are moving into the realm of dynamically linked packages, we shouldn't assume that we will always have the ability to influence the way our specific objects get handled. I just think that something like this might create a way to better communicate to the other side where reflection is the primary method to try and understand the passed in data at a deeper level at the cost of adding exponentially more complexity to a program. In my view, this feature might be the right approach to solving issues that make people wish that Go still had generics, operator overloading, boilerplate reduction, and possibly other things that I haven't thought of yet. And as far as I know, no other language uses the ? operator specifically on statements like this. There are languages that has similar functionality (like side effects) but not quite like this. |
Also consider the fact that goroutines are discouraged to be used in library code. The reason being primarily the fact that concurrency should be something that should be left to the user to determine what is appropriate. Right now we define GOMAXPROCS as a very general way of stating how many threads we want our program to use. Of course we can use the runtime.GOMAXPROCS function to edit this requirement at runtime, but what if we want to use more system threads in the concurrency we define, but not in the concurrency that is used in a library we import. What if we want an imported pkg to never use more than 2 threads, because we want our functions to have a higher priority? The only way I can see how to do that is to encourage libraries to use a conditional Go statement. One that can essentially ask is it okay if I run this data concurrently? and if so on how many threads? Or what about a situation like this, we have a library function that takes as an interface that defines a method called GetChan() chan pkg.SensitiveDataType. And then say that we define the same interface in the client code which uses a specific channel that we need to be very careful when dealing with concurrently. Now say that we pass our object to the library function which takes a GetChanner. And then on their end they run `go func(ch chan pkg.SensitiveDataType){ modifyChan(ch)}(OurChanner). Now we don't know if our channel has been corrupted, and in a proprietary dynamically linked pkg, we can't discover why, and the library author really has no way to ask if this is okay to do on their end, and modifying the interface to include an "isItOkayIfIRunThisConcurrently()?" breaks code if it isn't already implemented in a popular interface, and fuglifies interfaces in general. The better way I see is to have a go func(ch chan pkg.SensitiveDataType){ modifyChan(ch)}(OurChanner?) that will either pass in nil or changes the underlying channel when the ? side effect is executed. On the library end they will either get a panic or error they can recover from or they will be able to proceed as normal modifying a channel the user determines is okay and on their end is able to resolve as they see fit. I mean these are really bizarre "what if" corner cases, but I don't think it's best to just say "yeah, how about you just don't do that in library code." |
wut? |
Im not actually sure when or where I heard that it was discouraged to use goroutines in library code. (I think it was in one of the older talks which makes finding the exact reference at this moment difficult) Looking at Go's code review policy I can see that what I am talking about has specifically been addressed to make sure that functions are designed to be sync and not async. |
I'm having trouble following what it is that you're actually proposing. Could you split up the text into smaller sections and more clearly separate the background discussion from the specific version you want to propose? |
Your proposal is way too verbose. Just get to the point with some good code examples. |
I am also unclear on the actual proposal. @mortdeus can you condense into a specific proposal without the discussion of how the proposal was developed? Thanks. |
I apologize for the confusion and I'll start working on a formal proposal. |
I think this proposal could be break in two different proposals, one for conditions and other one for error handling, the proposal with question mark could be nice for conditions but not to mix with error handling, this is a proposal example to avoid return err in each statement: Now
Proposal
Also could be used with wrapped type like:
And like this will be reduce the read time in our brains (I used other symbol ^ but it's to show that the ? should be for conditions not an alias for returning an err statement. Feel free to open a new proposal with this example or tell me with emoji what do you think If you see this useful etc..., regards!. |
This proposal has been waiting for clarification for some time. I'm going to close it out. Please comment if you disagree. @alex-unikorny I'm not sure if that is a new proposal or not but it seems very similar to #21155 and others. |
Preface:
(This proposal started as an alternative solution to Ian Taylor's proposal "simplify error handling with || err suffix")
Proposal:
My proposal is to implement the conditional operator ('?') as an entirely new Go feature.
Rationale:
Before I start trying to explain what my proposal is, I want to take a moment to explain what my proposal isn't. This is not a proposal to implement a C style '?' operator in Go.
'expression ? expression : expression' // not this
Rather this is a proposal to try and integrate the ? operator into the language in such a way that "makes sense" in terms of Go's design.
Now, to explain what the Go ? operator does. The operator adds an element of conditionality to a built in statement. In other words, statements in code are read more like "questions". Should we return this? Should this Go routine run? etc.
In order to demonstrate what we are ultimately trying to solve here I first need to refer everyone to the warty issues we have when it comes to the way we currently deal with error handling, (particularly the issue of having to read and write the following code over and over again.)
x, err := foo() if err != nil { return err }
This sequence of code is so unnecessarily prevalent in Go code; that Ian Taylor's proposal's discussion thread has grown to a staggering 400 comments (and still appears to be rising without any signs of slowing down.),
And out of all those comments, all but a very minute few are complaints regarding the never ending repetition of boilerplate error handling. And from those complaints many counter proposals have materialized. Proposals such as making "error" even more magical using a '||' operator to write a magic one liner.
Other proposals are suggestions that have been proposed before and tend to have an element of feature overlap to them like the proposal to add try-throw-catch exception handling when we already have panic and recover.
Having read what most people had to say, I decided to chime in and give my two cents on Ian Taylor's proposal. Which is copy and pasted below.
And now that I have caught everyone up on where we are right now and how we ultimately got here. We now get to move on to the more interesting bits of my proposal.
Design:
This proposal suggests to add to the language
Now, I realize this is very scary sounding and many of you are more than likely motioning your finger towards the backspace key right now. But I implore you all to hear me out.
First lets look at the syntax of the operator.
_, ? := foo()
This was my initial idea, that you can just assign to "?" as a way to say that if err != nil, you want to return the error to caller.
x?, err? := bar()
And this was taking the idea a step further. Here either x and/or err being != nil (should 0, "" == nil?) causes the function to assign new values to x, and err (in case were dealing with references) and then immediately return.
x, err? := bar()
And I just wanted to point out in this scenario, we only return when err != nil, when err == nil, x gets assigned to the new value and the program proceeds as normal.
Also I want to take a quick moment to point out that what I just described is not actually apart of the design im proposing. Rather its an entirely separate "mini" proposal, that can be used if we want to just get rid of the annoyingly repetitive "if err != nil {return err} code that is all over the place. And it also cleans up the syntax when you run into this wart
var err error if x, err = foo(); err != nil { return x, err }
with this proposal all that code gets condensed down to
x?, ? = foo();
Now I should point out that another key aspect of the ? operator in assignments is that it designates which variables are going to be sent back to the caller as return arguments.
For example x, ? = foo(); only sends back err if err != nil. x?, _ = foo(); only sends back x if x != nil (or 0, ""?), and last but not least x?, err? = foo() sends both back, in the event that either x or err != nil. It doesn't need to be both in that case.
Now, you might be asking, well what about when I have more or less return arguments than the function im returning from defines?
We can get around that a couple ways.
Assign identifiers to the return arguments and rely on a rule of a rightmost alignment when assigning to '?'.
For example
func foo() (msg Msg, len int, error){ defer func() int {? = msg.Len()} msg?, ? = fetchMsg() }
In this case every thing will line up properly because we are chaining the return args. However if you tried ?, ? = fetchMsg(), it should throw an invalid type error.
Or we can just use
Using ?, ??, ??? and ?n to manually indicate argument index from left to right. (or should it be right to left? and variadic assigns to same number?
func foo() (msg Msg, len int, error){ defer func() int {?? = msg.Len()} ?, ??? = fetchMsg()
And in the event that we have a ton of return args.
func foo() (msg Msg, len int, ts timestamp, error, error){ defer func() int {??,?5 = msg.Len()} ?, ???, ?4 = fetchMsg()
Anyways, as you can see, this "mini proposal" looses it's sex appeal completely when trying to use ? in many assignments and within defer.
Which is ultimately what led me to try to think of a completely different approach when thinking about this problem.
x, y, err := baz(); return (x? && y?) || err?
Here I show that instead of using ? in the assignment of variables, you use the ? to change a statement into what I essentially like to think of as the program asking permission.
_, err := foo(); return err?
The default behavior for the ? operator in this implementation is the same as before. We check to see if we aren't nil and if we aren't then we return. Otherwise we move on like normal.
x, err := bar(); return x? || err?
x, y, err := baz(); return (x? && y?) || err?
And you can use logical and/or (and the bitwise ops) to define the conditions that need to be satisfied for the return to execute.
x, y, err := baz(); return (x? && y?) || err?
Now when using a return statement like this, as before the ? designates which variables get sent back as return arguments and the alignment is left to right. It is assumed that everything after ? is regarded as a ',' until another ? is encountered and everything in between (including identifiers without a ?) is not relevant to an assignment.
I realize that this is a rather drastic change to the syntax of statements. Which is why I propose that we add another builtin interface.
The new interface which for now I just refer to as interface{?}, can be defined in two different ways.
The first way it can be defined is
and then you can go about
However, I don't think this is the best way, rather I would
create a magic switch case construct.
`
And this way we can make super simple and easy to read calls like
which is a condensed form of
And that is basically what I propose.
This proposal is still very much a WIP and any feedback (positive or critical) would be greatly appreciated. Thanks!
The text was updated successfully, but these errors were encountered: