Closed vit1251 closed 3 years ago
Please fill out https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing language changes
@seankhliao ok.
Would you consider yourself a novice, intermediate, or experienced Go programmer?
I really like the simplicity and powerful of Go, but the ability crash on "null pointer" is push me away.
Right now I want use Golang and want make more usually for me language items liek protection in language more caring about memory protection and etc.
What other languages do you have experience with?
I meet with Assembler ( Intel ), Ada, Basic, C, C--, C++, Delphi, ECMAScript, Java, Lisp, Lua, Modula, Pascal, PHP, PostSript, Prolog, Python, Rust, Swift, SQL, TeX, Vala.
Would this change make Go easier or harder to learn, and why?
I think that match is similar to understand like switch. But actually only research may actually answer on this question.
Has this idea, or one like it, been proposed before?
I think it could be discusses somewher, but quick search does not provide such a proposal.
Who does this proposal help, and why?
The flow control syntax can be used by all developers in the language and no restrictions.
What is the proposed change?
Add match statement and provide more compact control flow syntax.
Please describe as precisely as possible the change to the language.
Original post provide two source code example. I welcome disscussion about patern match it may provide disadvantage this statement.
What would change in the language spec?
I think this may take article about flow control constructs will add new one statment and golang source code.
Please also describe the change informally, as in a class teaching Go.
If you want to process more complex case processing, then you can use the match statemnt expression. Let's look at smart home processing hub code example:
type struct Event {
Source string
Value int
}
for {
event := <- homeBridge
match (event.Source, event.Value) {
"iot.Temperature", x => {
// ... disable heating system ...
}
"camera.Activity", trushold => {
match (trushold) {
0 .. 5 => {
alarm.Stop()
camera.StopRecording()
}
5 .. 10 => {
match (secureMode) {
true => {
alarm.Start()
}
false => {
camera.StartRecording()
}
},
}
}
unknownEventName, _ => {
log.Printf("Unhandle event %s", unknownEventName)
}
}
Is this change backward compatible?
Yes.
Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit.
N/A
Show example code before and after the change.
See original message.
What is the cost of this proposal? (Every language change has a cost).
I'm not a pro at estimating the cost of language changes.
How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
At the option each one of developer. Use match pattern is optional construction.
What is the compile time cost?
Every match want to use additional variable scope, every pattern is want to have local variable name. Actually it may take some additional time. I assume some like O(N) where N is match cases.
What is the run time cost?
Every match case amy take a more than one comparision and may take new stack and push one or more variable name.
Can you describe a possible implementation?
No, sorry.
Do you have a prototype? (This is not required.)
No, sorry.
How would the language spec change?
Actually I think you add some additional PEG grammar and make AST processing. No idea actually.
Orthogonality: how does this change interact or overlap with existing features?
No overlapping.
Is the goal of this change a performance improvement?
No in term of computing. Yes in term devloper time.
Does this affect error handling?
Yep. match may catch unhandled variant / cases.
Is this about generics?
No.
Can you write down the precise semantics that you are proposing?
Why introduce a brand new syntax like =>
when we already have case
?
Why require a brace-enclosed block when the switch
statement does not work that way?
What are the exact semantics of ..
? What happens when multiple ..
cases overlap?
More generally, what big advantage do we get from match
that we can't already write with switch
?
@ianlancetaylor response as follow:
Can you write down the precise semantics that you are proposing?
You asking pattern match statement EBNF? I actually not ready to write it now but some like that:
control_flow_statement :== if_statement | switch_statement | match_statement
match_statement :== "match" SP pattern_match_expression SP { pattern_match_cases }
pattern_match_expression :== variable_name | expression
pattern_match_case :== pattern SP "=>" SP code_block
pattern :== new_variable_name | constant | ?expression?
Why introduce a brand new syntax like => when we already have case?
At match body we should separate the pattern from the lambda function being executed and here you need some kind of separator in switch you use keywork "case".
Main problem case on my option in Python syntax and Assembler symantics with non clear internal logic (i.e. using break in C and falltruth keyword in Golang). Brace-enclosed provide strict execution range and provide make "match" retun value. I does not know if it possible write some like:
var stable boolean = switch {
case version % 2:
return true;
default:
return fasle;
};
Why require a brace-enclosed block when the switch statement does not work that way?
Brace-enclosed provide human intuituve and well known variable scope and provide execution block bounds. For switch you should provide additional information about variable scope (how about variable created in case) and flow behavior (i.e. provide breake and falltruth keyword).
switch ... {
case ...:
var number int = 5
falltruth
case ...:
// How about .. number here ...
}
It make switch more complcated language unit and match look like more simple to understanding.
What are the exact semantics of ..? What happens when multiple .. cases overlap?
Actually I think that ".." is advanced math feature. Let's talk about this idea later.
More generally, what big advantage do we get from match that we can't already write with switch?
I suspect that switch statement may cased only one arguemnt, which requires building a lot of forest for switch checks. Of course, you can refactor the code and create more and more related functions with switch, but it create more and more line of code.
For example, in original message check structure two variable at same time.
See also #40353 for multivalue switch
Can you write down the precise semantics that you are proposing?
You asking pattern match statement EBNF?
No, that is the syntax. I'm asking for the precise semantics. What precisely does it mean?
Brace-enclosed provide human intuituve and well known variable scope and provide execution block bounds. For switch you should provide additional information about variable scope (how about variable created in case) and flow behavior (i.e. provide breake and falltruth keyword).
I'm sorry, but this is not a good argument. The switch
statement works a certain way. Perhaps you don't like the way it works, but it exists, it does the job, and people are accustomed to it. Changes to the language mst correspond to the existing language. We are not going to introduce a completely different syntax for something that is very similar to a switch
statement.
@ianlancetaylor
We moved from the language syntax advantages of match structure (explicit scoping, parallel case handling, more explicit execution block) to political reasons (historical). This is a completely different conversation in new mindset space.
I think that world already have more spartan language, remember the Assembler with direct cmp and jmp and I don't see much desire to use it today. I know that Golang community declare newcome welcome (i.e. learning simplicity) and at the same time have a unclear structure in the language guarded by historical reasons. Today is a great moment to annonce new language structure and Go 2 sounds like revolution as Python 2 and 3, Perl 5 and 6 and other large language changes.
Another part of you argument is classical ad hominem political argument.
Sorry to jump in, but I think some misunderstanding is brewing here. so let's stay calm and focus at the issue at hand.
I think what @vit1251 is proposing is the classic "pattern matching" feature which is present in many contemporary programming languages, often even in conjunction with a switch statement. For example Switch has both switch and match statements, (https://docs.swift.org/swift-book/ReferenceManual/Patterns.html), while C# has extended their if and switch statements to support pattern matching (https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching).
In these examples, the switch and the pattern match feature are significantly different, the one is not another way to write the other. So, when asking for what semantics are being proposed, we can look at these existing implementations of pattern matching and consider which of those could have semantics that are for inclusion in Go. Syntactical considerations come in later.
I am also jumping in because pattern matching is quite a popular programming feature nowadays, and I think we should at least consider whether or not they should be added to Go and if not, why, and perhaps even add a FAQ to that extent. Pattern match semantics might also help solving certain issues around type switches and generics, or type switches and the proposed union types. With a pattern match we could statically match not only an exact type but also similar types that match the pattern.
@beoran
How is that different from type assertions and type switches? In C#, there's an is
that can be used with an if
, similar to in Kotlin, but in Go you can do
if v, ok := v.(someType); ok {
// No magic required.
}
Similarly, you can use a type switch for pretty much the same thing but with multiple branches:
switch v := v.(type) {
case int:
// In this case, v is an int.
println(v + 2)
case fmt.Stringer:
// Works fine with interfaces automatically, too.
println(v.String())
default:
// This works just fine, too.
doSomething(v)
}
@vit1251 I am not making an ad hominem argument. Also, citing the historical development of the language is not making a political argument.
For better or for worse, it is extremely unlikely that we Go will ever significantly break backward compatibility. See https://go.googlesource.com/proposal/+/refs/heads/master/design/28221-go2-transitions.md .
@ianlancetaylor
I am not making an ad hominem argument.
Your write "Perhaps you don't like the way it works, ... people are accustomed to it". I see here "you" and "like" and "people" as result I feel that argument to my personal preferences between people. You weight my preferences with historical reasons. It argument definitely ad hominem. To be more precise, here is a variant: Argumentum ad populum ( ... most people walk street on red signal and it right ).
Citing the historical development of the language is not making a political argument.
Please watch on argument "... by hisorical reasons" from another side. Today we have a large count of experience by resolve errors in past ( .. by definition of expirience ). But people contain a an amazing feature to continue improves. Once we were young and did not know how to walk, but today we runing and jumping. Is the historical argument constituted an excuse for stay as children?
For better or for worse, it is extremely unlikely that we Go will ever significantly break backward compatibility ...
I don't quite understand why you talk about breaking backward compatibility. I talk about implement match as new one structure like threnary operator and you may already save "switch" as improve option for "if" and they all overlaps semantics. If "switch" little better than "if" why you prevent to resolve "switch" problems in new one construction?
Resume: I understand your argument as most people are used to the flaws of "switch", but in the next big promise to improve the language these problems will not be solved or improves.
Introducing a new keyword breaks backward compatibility. Match as a new key word is not possible because some people are now using the word "match" as a variable name. But there are other ways to get match semantics. It would be possible to enhance the current switch statement with the semantics you want, for examples by using two key words in a way that is not allowed now (e.g., "switch range") in such a way that it is backwards compatible, or perhaps with built-in functions. Which brings us to the important question: which semantics should this feature have?
@DeedleFake, Pattern matching generally allows not to match a single type but also ranges of types, sometimes with wildcards (looking as some functional languages). Especially when using a type switch in conjunction with generic types, this could be handy.
@vit1251 Thanks for the reply. I won't continue the exchange as it seems unlikely to be productive.
Let me just say two things as clearly as I can.
First, if we add a new language construct that is similar to switch
, it must use a syntax that is similar to switch
. That means using the case
keyword rather than introducing a new =>
token. It also means not introducing additional curly braces that are not present in switch
.
Second, you must document the precise semantics of the new language construct. You have not done so.
Thanks.
@ianlancetaylor
if we add a new language construct that is similar to switch ...
Perhaps this is another kind of unsolved problem - impossibility to extending. Computer language may provide some mechanisms for extending and enable addtional language features. Example:
If you have motivation to solve, then you could be found solution.
it must use a syntax that is similar to switch ...
In this case does not resolving problem with variable scope and syntax overload.
you must document the precise semantics of the new language construct ...
At the initial stage, we encountered a gap that there is simply no way to expand the language with new constructions: keywords and arrows.
Let's again watch simple example:
var original bool = true
var number int = 5
var result bool =
match original, number {
false, 1 => {
return true;
}
true, _ => {
var newNumber int = 5;
return false;
}
_, _ => {
return false;
}
};
An result boolean variable initialize by pattern match expression and step-by-step check with pattern until first match. Once pattern is match we execute code block and return value in variable result.
In this case newNumber
variable scope implicitly braced after pattern declaration "true, _".
When you want to implement some sort "falltruth" behavior it possible by use underscore "_" as universal value.
Let's on first version look at this variant (without range ".." and another one advanced pattern match feature).
This seems like a large number of changes, almost all of which introduce patterns that are completely different from any existing Go structures, for what is essentially a very light syntax sugar variant of a switch
or an if
-else
chain. In particular, it introduces a case in which a return
does not return from a function, it introduces a complex expression that can contain statements, it uses underscores in a way similar to being values, and it seems to handle an arbitrary number of comma-separated expressions. It introduces all of these things that don't fit the existing pattern of Go code in order to do something that can very easily be done with existing constructs.
Hmmm, well if those are the semantics you want, you can already do that with a different syntax: https://play.golang.org/p/Q_iv2EGmB3Z
package main
import (
"fmt"
)
func main() {
var original bool = false
var number int = 5
var newNumber int = 0
var result bool = func(b bool, i int) bool {
switch {
case !b && i == 1:
return true
case b:
newNumber = 5
return false
default:
return false
}
}(original, number)
fmt.Printf("%d %d %v\n", number, newNumber, result)
}
Admittedly, the anonymous func is needed to introduce a new scope, and the call of that function with (original, number) is somewhat unclear because it after the body of the anonymous function. But there are other issues about defining anonymous functions more easily.
For this feature a bit more ambitious semantics would be needed, not just syntactic sugar.
@beoran
You rewrite code to avoid intuitive unclean situation. Let's I return back more intuitive unclean situations in your code:
var original bool = false
var number int = 5
var newNumber int = 0
var result bool = func(b bool, i int) bool {
switch {
case !b && i == 1:
return true
case b:
var newNumber int = 5
falltruth
case b == 3:
newNumber = 10
default:
return false
}
}(original, number)
fmt.Printf("%d %d %v\n", number, newNumber, result)
}
It is clear that you can always rewrite or refactor the source code in accordance with the rules of the language, or even always use "if" for clean variable scope.
My point is switch create sub-scopes without brackets and it alient syntax (i.e. Pyhton syntax) in Golang in switch statement.
When we meet lexical brace in "switch {" we intuitive await new switch variable scope like we await it between "if !b {" and "}". But actually every case provide new one and in a "falltruth" situation it's continue worsen.
Let's little rewrite source code to mark scopes by lexical braces (i.e. since that time it stay more similar to proposal match design):
switch {
case !b && i == 1 {
var newNumber int = 5
return true
}
}
Actually does not matter switch or match and case or => we use when you return back lexical scope brackets switch stay overdesign at "if" construction and it create anoter one question about what mean bracket on switch it create new scope or provide group. For group in Golang in import use parentheses.
I talk about intuitive language design and most people right it sounds like sugar as an any language over Assembler.
So your point is more about the syntax of the current switch statement? The switch syntax of Go was taken by and large from the C/C++/Java family of languages, but then enhanced with more capabilities in the cases. but yes, in Go, every case statement introduces a new scope for variables, and you consider that counter-intuitive because there are no braces to signal that?
It is still possible to use braces to do that, although the fallthrough statement will have to come after, a bit like this:
package main
import "fmt"
func main() {
var original bool = false
var number int = 5
var newNumber int = 0
var result bool = func(b bool, i int) bool {
switch {
case !b && i == 1: {
return true
}
case b: {
newNumber = 5
}
fallthrough
case i == 3: {
newNumber = 10
}
fallthrough
default:
return false
}
}(original, number)
fmt.Printf("%d %d %v\n", number, newNumber, result)
}
Actually, I think that on the level of the lexer/parser, allowing the syntax of starting the body of a case with a { in stead of a : and ending it with a } would certainly be possible, because it produces a syntax error now (syntax error: unexpected {, expecting :), so changing that would be backwards compatible. The matter though, is then if such a small syntax changes is worth the effort, especially for the Go programmers who will have to read this code, and now will find that there are two ways in which you can write a case statement in stead of one.
After 10 years of programming Go, the current Go syntax has become intuitive for me. It seems you are relatively new to Go, so I would say it takes some time to get used to, like anything new really.
Actually, I think that on the level of the lexer/parser, allowing the syntax of starting the body of a case with a { in stead of a : and ending it with a } would certainly be possible, because it produces a syntax error now (syntax error: unexpected {, expecting :), so changing that would be backwards compatible
This is because you are using an int
. I can't think of any ambiguities myself, so it may still be possible. However it gets more complicated when you consider structs.
The proposal review committee appreciates the fact that the author prefers to use "flow-control syntax", but it's also straight-forward (if perhaps less elegant and less compact) to achieve the same result using existing Go mechanisms.
Any language addition needs to be considered in light of existing Go: not only should it (ideally) fit well with the existing language, but it also should provide a significant functional benefit not easily achieved (or impossible to achieve) with the existing language. The proposed change suggests a language feature that is quite different in style from what we have, and doesn't really provide a significant functional benefit. There's ways to write the equivalent code and those alternatives seem adequate. Finally, the fact that other languages provide mechanisms similar to the one suggested, or that other languages have a mechanism for extending the language does not mean that Go need to do the same.
Based on the discussion above, and the lack of strong support in the emoji voting, this is a likely decline. Leaving open for four weeks for final comments.
I've been trying to see if someone has already talked about it but I can seem to find it, so please let me know if this has already been discussed!
I think one of the good things pointed out in this match
example, is not the match itself, but the fact that you can assign its value to a variable. I think it would be useful to have if
and switch
return their value, like in functional languages, or languages that have the ?:
operator.
I know the idea behind Go is to have a simple language that doesn't need to be copying others, but there are some really good functional practices that can shorten code by a lot, while also making it more clear.
For example:
var x = if someVar > 0 {
"String A"
} else {
"String B"
}
var z = switch someVar {
case 1:
"something"
case 2:
"something 2"
default:
"oops"
}
I know you could do this, which is very similar.
var x string
if someVar > 0 {
x = "String A"
} else {
x = "String B"
}
However this can become tedious in some cases where you have a switch
with many case
statements. You need constantly repeat yourself, and it gets too clobbered if your variable name is somewhat long. Even just having the braces is like too much in some cases, especially when you have many different binary if
and you could write var x = if a > 1 then 1 else 0
, making the overall code much easier to read.
I find this to be very practical in the languages that support it, and I don't think this would be a breaking change either, but rather just an extension of if
and switch
.
This is not a must by any means and I can happily live without it, but I'd like to know if this has ever been a point of discussion.
Thanks!
@nferrario Your suggestion has come up before, at the very least in the form of a ":?" operator. It has been a point of discussion before. There are lots of constructs in other languages that may be very practical. That is not a strong reason for including such constructs in Go, especially if there are alternatives available that are almost equivalent.
@nferrario As @griesemer says, there are alternatives available. Just to explain how, you can already do this at the cost of defining closures like this:
https://play.golang.org/p/Wgo_YIBkvBB
package main
import (
"fmt"
)
func main() {
someVar := 1
z := func() string {
switch someVar {
case 1:
return "something"
case 2:
return "something 2"
default:
return "oops"
}
}()
x := func() string {
if someVar > 0 {
return "String A"
} else {
return "String B"
}
}()
fmt.Printf("Hello, %s %s\n", x, z)
}
This example is a bit artificial, but it might be useful to do it like this, since it allows the closures to be factored out later as separate functions.
Thank you @griesemer and @beoran. Yes I know we can easily solve it using closures and it's not a terrible solution either. It's just a little bit of code after all. But I don't also see a strong reason NOT to include such functional features. It's a nice to have that's useful, and it also avoids having a funcion call. I'm all in on keeping Go simple, I love the way it is and I wouldn't like it to become like other languages like Scala, where you have 5 different ways of writing the same thing. But I do believe we could be a little bit more flexible and allow/include more things. I've seen people saying they don't wanna use ?:
because it can lead to more difficult to read code, and that's not true. You can have an equally unreadable code just with if
s.
Just like Generics is gonna help simplifying code and avoid having a bunch of casts on interface{}
, I think we can come up with other ways of reducing the verbosity of Go code in some scenarios, without affecting its readability.
@nferrario With generics, we can implement the ?: operator very easily ourselves as a generic function, if you excuse my use of Canadian Aboriginal Characters: https://go2goplay.golang.org/p/wbueDIcvew_U
package main
import (
"fmt"
)
func ᕈ[T any](b bool, ifTrue T, ifFalse T) T {
if b {
return ifTrue
}
return ifFalse
}
func main() {
i := 7
v := ᕈ(i > 5, "greater", "lesser")
l := ᕈ(i > 5, 5, i)
fmt.Println("i > 5?", v, l)
}
In fact, I like this so much that on the 1st of April :clown_face: I will propose to make ᕈ a build-in generic function , then we can from now on say that yes, we do have ?: in Go language :wink: .
Seriously though, if this would be in generics package the name would likely to be gen.if, but this example does go to show that generics will be powerful enough to handle the ?: use case. As for the match, I'll leave that as an exercise for the readers.
@beoran lol no, that'd be terrible, no doubts! But that's a very good explanation since I wasn't aware we coule achieve such things with Generics. Thanks!
The usual name for ᕈ
is cond
, eg https://gohugo.io/functions/cond/
No change in consensus since https://github.com/golang/go/issues/44022#issuecomment-776355357.
The current state
Using "switch" provide torn syntax, reuse parent scope, may process part cases.
Problem statement
Source code with "switch" provide simple case processing.
Proposal
Using match statemtnt without destructurization:
Example:
There we have new variable scope, ability to checking all variants is checks, return value, more uniform syntax.
Using match expression: