for Go 2.0 - what would you take out?

polaris · · 3536 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;d like to see making new things streamlined in Go 2.0 (although I admit it&#39;s not easy).</p> <pre><code>&amp;thing{} new(thing) NewThing() </code></pre> <p>All of these are ways of creating new things in Go. In particular, &amp;thing{} and new(thing) are the same. new(thing) is easier to read, but &amp;thing{} is more powerful (since you can set fields etc.)</p> <p>In the spirit of simplicity, what would you <strong>take out</strong> of Go if backwards compatibility wasn&#39;t an issue?</p> <p>MR</p> <hr/>**评论:**<br/><br/>homa_rano: <pre><p>I&#39;d explicitly disallow variable name shadowing. Notably in cases when you have multiple nested x,err:=stuff(), surprising things can happen about which err you&#39;re referring to. Forbidding shadowing would remove any possible confusion.</p></pre>cheesechoker: <pre><p>FWIW <code>go vet</code> does support <a href="http://golang.org/cmd/vet/#hdr-Shadowed_variables">flagging shadowed variables</a>, but it&#39;s considered &#34;experimental&#34; and is disabled by default.</p> <p>Not sure why. I&#39;m a beginning Go programmer and I&#39;ve already shot myself in the foot twice with accidental shadowing. Go&#39;s short declaration syntax makes it way too easy.</p></pre>THUNDERGROOVE: <pre><p>This has caught me several times. I&#39;d completely agree.</p></pre>TheMerovius: <pre><p><code>&amp;thing{}</code> and <code>new(thing)</code> are not the same. They are only the same for struct types. <code>NewThing()</code> is misplaced in the list, IMO, because it is not part of the language, it is a pattern. Other languages call this a factory function and it is useful if there is some initialization you <em>must</em> do beforehand.(like, say, <code>os.Open</code>, which is the same thing, just differently named). There are also three things missing from this list: <code>var foo = bar</code>, <code>foo := bar</code> and <code>make</code> (which is kinda special). Go doesn&#39;t distinguish between stack and heap values, so creating a variable via <code>var</code> or <code>:=</code> and then returning an address to it is the same as using <code>new()</code>.</p> <p>I don&#39;t think anything of these <em>needs</em> to be taken out. The problem is kind of overblown, I use all of them often. If anything <em>needs</em> to be taken out, I would take out <code>new</code>. And possibly <code>:=</code>, though I&#39;m ambivalent towards that one. It makes some code shorter/nicer, but it&#39;s semantics are sometimes confusing (especially to beginners) and you can get type inference with <code>var</code> too.</p> <p>There is also <code>make</code>, which needs to be taken into consideration somewhat.</p> <p>If I would take one thing out of go, it would be naked returns. I think they are the completely useless and a lot of the time actively harmful to readability.</p> <p>The second thing I might consider taking out are arrays. They have confusing semantics too (they are copied, unlike slices) and are awkward to use (if you have a <code>var hash [40]byte</code> and something wants a <code>[]byte</code>, you need to use <code>hash[:]</code>).</p> <p>I would also like to take out <code>string</code>, but that would need to be replaced by a <code>const</code>-like keyword for slices, pointers and maps. So it would trade a complication for a simplification and add a lot of power in the process. This would also work well together with taking out arrays.</p> <p>But: go 2.0 will likely never come (for appropriate values of &#34;never&#34;). It is completely futile to have this discussion until there is a RFC in regards to backwards incompatible changes.</p></pre>BESSEL_DYSFUNCTION: <pre><blockquote> <p>The second thing I might consider taking out are arrays. They have confusing semantics too (they are copied, unlike slices) and are awkward to use (if you have a var hash <code>[40]byte</code> and something wants a <code>[]byte</code>, you need to use <code>hash[:]</code>).</p> </blockquote> <p>Arrays are really useful for some applications. For example, let&#39;s say I want a slice of 3D <code>float32</code> vectors. I have three options for representing this:</p> <ul> <li><code>[][]float32</code></li> <li><code>[][3]float32</code></li> <li>Define <code>type Vector struct { X, Y, Z float32 }</code>, then <code>[]Vector</code>.</li> </ul> <p>The third option is fine, but it means I can&#39;t iterate over the components with a for loop which makes a lot of function implementations much more annoying (and it also wouldn&#39;t be convenient to use for, say, a 10D vector), so the choice is really between the first two. There are some cases where the first one is better, but the second has some nice properties:</p> <ul> <li><code>[][3]float32</code> takes up about half the memory that its slice equivalent does.</li> <li>Fewer allocations. On my machine <code>make([][3]float32, 1000 * 1000)</code> runs in 3 ms, but <code>vs := make([][]float32, 1000 * 1000)</code> <code>for i := range vs { vs[i] = []float32{0, 0, 0} }</code> runs in 70 ms.</li> <li><code>[][3]float32</code> has better cache properties. Iterating over and zeroing a length-1000,000 <code>[][3]float32</code> takes 2.8 ms on my machine, but doing the same for a <code>[][]float32</code> takes 5.5 ms (with <code>gcflags=-B</code> at least, the difference is even bigger when bounds checking is turned on).</li> <li><code>[][3]float32</code> can be read from and written to binary directly with <code>binary.Read()</code> and <code>binary.Write()</code> (or, more likely, with <code>unsafe</code> tricks, since the <code>binary</code> package is an abomination ;-)).</li> <li>It&#39;s possible to get a small <code>[][3]float32</code> allocated on the stack, but the data potion of a <code>[][]float32</code> is guaranteed to be heap allocated.</li> </ul></pre>tedreed: <pre><p>You could also:</p> <pre><code>type Vector [3]float32 </code></pre> <p>And then define methods .X(), .Y(), and .Z() if you still really want to use those designations.</p></pre>TheMerovius: <pre><p>Agreed, haven&#39;t thought about that apparently :)</p></pre>ansible: <pre><p>There are various proposals for 2-D and higher dimension matrix support for Go. This is something that needs to be baked deep into the language for the best end result.</p> <p>I do hope that something makes the cut for v2.0. This is a sore point among some developers. The current solutions are dropping into C (using external libs) or such, and aren&#39;t all compatible with each other. So you can write code against one library, but you can&#39;t use the data with functions from another.</p> <p>N-dimensional matrix support built into the language would serve to unify all these efforts, allowing more code-sharing.</p></pre>BESSEL_DYSFUNCTION: <pre><p>I&#39;d be very sympathetic to any effort to have language-level matrices in Go. (If for no other reason than that I&#39;m getting pretty tried of writing <code>grid[y*width + x]</code> two dozen times per day, but the reason you mention is better.)</p> <p>I&#39;d be interested in seeing some of these proposals. Do you have any links to them? (and to subsequent discussion?)</p></pre>ansible: <pre><p>Here&#39;s some stuff to keep you busy.</p> <p>This one I like:</p> <p><a href="https://docs.google.com/document/d/1eHm7KqfKP9_s4vR1zToxq-FBazdUQ9ZYi-YhcEtdfR0/edit" rel="nofollow">https://docs.google.com/document/d/1eHm7KqfKP9_s4vR1zToxq-FBazdUQ9ZYi-YhcEtdfR0/edit</a></p> <p>golang group and reddit discussion here:</p> <p><a href="https://www.reddit.com/r/golang/comments/21fyta/proposal_to_add_tables_twodimensional_slices_to_go/" rel="nofollow">https://www.reddit.com/r/golang/comments/21fyta/proposal_to_add_tables_twodimensional_slices_to_go/</a></p> <p>One of the issues used to track it:</p> <p><a href="https://code.google.com/p/go/issues/detail?id=6282" rel="nofollow">https://code.google.com/p/go/issues/detail?id=6282</a></p></pre>BESSEL_DYSFUNCTION: <pre><p>Thanks!</p></pre>yiyus: <pre><p>I took part in those discussions, and even wrote a proposal. In my opinion, the problem was that different people had different requirements, and there was an implicit pressure to make any language change as small as possible.</p> <p>For example, I was on the side who thought any proposal should take into account the n-dimensional case (even though it was implemented in steps, starting with n=2), while other developers thought that covering the 2D case was good enough and more likely to be accepted (this is the position that gonum eventually took).</p> <p>It was not possible to find an agreement even between the people who wanted to make the change, so the lack of consensus made the more reluctant ones (in particular, the Go team) to not take any proposal too seriously.</p></pre>BESSEL_DYSFUNCTION: <pre><p>Hmmmm. On one hand, that&#39;s frustrating because I&#39;d really like to have access to language-level grids (even just 2D would be great, although I find myself working with 3D grids just as often, and higher dimensional grids are more annoying to manually index). On the other hand, the Go team&#39;s reluctance to add features that are less than perfect to the language is something I&#39;ve always really liked.</p></pre>comrade_donkey: <pre><blockquote> <p>Other languages call this a factory function</p> </blockquote> <p>Or, maybe more commonly, a <em>constructor</em>.</p></pre>TheMerovius: <pre><p>Not really. A constructor is usually a class method, not a function and serves the expressed purpose to make inconsistent instances of a class impossible. The difference is admittedly subtle, but there is a difference.</p></pre>comrade_donkey: <pre><p>I understand what you mean. But it&#39;s <a href="http://golang.org/doc/effective_go.html#composite_literals">really called a constructor</a>.</p></pre>TheMerovius: <pre><p>Okay.</p></pre>atelic: <pre><blockquote> <p>I would also like to take out string, but that would need to be replaced by a const-like keyword for slices, pointers and maps. So it would trade a complication for a simplification and add a lot of power in the process.</p> </blockquote> <p>Interesting. Can you expand on this? Why don&#39;t you like <code>string</code>? How do you envision the <code>const</code>-like keyword being used? What power would it add?</p></pre>TheMerovius: <pre><blockquote> <p>Why don&#39;t you like <code>string</code>?</p> </blockquote> <p>a) It requires a lot of duplication (see <a href="http://golang.org/pkg/strings">strings</a> and <a href="http://golang.org/pkg/bytes">bytes</a>) and it often leads to additional copies, because a package takes a <code>string</code>, but you read a <code>[]byte</code>, or because you have a <code>[]byte</code> and a package needs a <code>string</code>… For example, the leveldb-APIs I&#39;ve seen usually take <code>[]byte</code> as key and value, but if you want your key be something you usually have as a string (say, a url or a path or whatever), that involves a copy.</p> <p>Some of these can be alleviated by a clever compiler, but not all.</p> <blockquote> <p>How do you envision the const-like keyword being used?</p> </blockquote> <p>I don&#39;t have precise semantics (I started writing something up, but then realized that&#39;s idiotic right now), but I envision it as a compiler-checked modifier. An example usage (apart from obviously the <code>strings</code> package) is in <code>io.Writer</code>: The signature would change to <code>Write(b const []byte)</code> to create a compiler checked assurance that <code>Write</code> won&#39;t change <code>b</code>. Similarly, key-value stores like leveldb APIs could change their Signatures to <code>Store(key, val const []byte)</code>, <code>Get(key const []byte) (val []byte)</code>.</p> <p>I think a <code>const []byte</code> would have a lot of the main advantages of a string, though it is still possible in theory, to change the underlying array in a separate goroutine (i.e. there can be multiple mutable references to the same underlying array), so you don&#39;t get exactly the same immutability semantics as a string (that would require a copy anyway). If you think the precise immutability semantics of <code>string</code> are needed, you could also leave strings in, but still use it with most of the <code>bytes</code> package (as <code>string</code> is assignable to <code>[]byte</code>, you can also assign it to <code>const []byte</code>, but as a <code>string</code> is immutable, that doesn&#39;t require a copy. <a href="http://golang.org/pkg/io#WriteString">io.WriteString</a> and it&#39;s implied StringWriter interface could go away).</p> <blockquote> <p>What power would it add?</p> </blockquote> <p>Giving most of the advantages of <code>string</code> also to other slice types. &#34;A lot of power&#34; was probably overstated :) I&#39;d mostly want to get rid of the uncertainty of whether to use a <code>string</code> or a <code>[]byte</code>.</p> <p>Also note (so discussion is kind of… useless) that the go team has already contemplated added const (I saw a post of Ian Lance Taylor somewhere) and decided against it. And, for some semantics discussions, that we have a similar-ish thing with directional channels :)</p></pre>dmikalova: <pre><p>How would that work with runes that are more than one byte long? Strings aren&#39;t really []bytes.</p></pre>TheMerovius: <pre><p>Yes, strings are <code>[]bytes</code>. The differences are: a) Certain operations are disallowed on strings, like using an index expression as an lvalue. b) Thus, strings are missing a capacity-field in the in-memory representation, as reslicing them doesn&#39;t make any sense (you couldn&#39;t change the resliced contents so you would end up with zero bytes anyway). And c) the language provides you with syntactic sugar to range over the runes in a string.</p> <p>I assume c is what you are talking about? My answer to that would be, that this might be better done via the stdlib anyway. Treating slicing and indexing as a byte-wise operation but ranging as a rune-wise operation is a bit weird and inconsistent, IMHO (especially as strings don&#39;t <em>have</em> to contain UTF-8, for good reasons). I think an iterator in the utf8-package would be a better design for that, it makes the language simpler (hey, another thing I would take out!) and <a href="http://play.golang.org/p/3yOsNkJy8S" rel="nofollow">is not a <em>lot</em> more difficult to use</a>.</p> <p>[edit] Yes, my implementation of RuneIterator is not correct. Making it correct is left as an exercise to the reader :)</p></pre>dmikalova: <pre><p>You&#39;re right, strings are bytes. I meant that a character is not a byte (c). Thanks for the insight.</p></pre>: <pre><p>[deleted]</p></pre>PaulSnow: <pre><blockquote> <p>When you expound an idea you are explaining it. Theoretically you could expound an idea that you don’t personally espouse. “Expound” was traditionally used mainly to refer to detailed examinations of complex or obscure systems of thought, but it is most often used today to mean “to speak at length about” and frequently occurs in the phrase “expound on&amp;rdquo: “the senator expounded on his love for the traditional family farm.”</p> <p>Sometimes in such contexts it would be more appropriate to use “expand on,” which means “to speak at further length about.” “Expand” in this sense lacks the systematic analytical connotations of “expound.”</p> </blockquote> <p><a href="https://public.wsu.edu/%7Ebrians/errors/espouse.html">https://public.wsu.edu/~brians/errors/espouse.html</a></p></pre>TheMerovius: <pre><p>a) That&#39;s obnoxious behavior and b) <a href="https://en.wiktionary.org/wiki/expand#Verb">wiktionary</a> disagrees.</p></pre>earthboundkid: <pre><p>Named returns are okay, but bare return is the devil. </p></pre>nate510: <pre><p>This is minor, but I&#39;d like to see <code>log.Logger</code> become an interface rather than a struct.</p></pre>jerf: <pre><p>Why wait? Declare one, and <em>voila</em>, it is.</p> <p>That&#39;s why the standard library is so short on interfaces overall. If the stdlib doesn&#39;t take the interface as an argument, there&#39;s little point in declaring it. I&#39;ve declared half-a-dozen interfaces for stdlib code at least and consider it a low-cost engineering move (i.e., not a &#34;code smell&#34;, just a perfectly standard thing to do in Go).</p></pre>TheMerovius: <pre><blockquote> <p>Declare one, and voila, it is.</p> </blockquote> <p>No. Because if some package I use declares a *log.Logger for logging, I can&#39;t assign my interface value to it. I think declaring it as an interface (and taking out the default Functions) would encourage much more modular logging.</p> <p>The solution to this, currently, is to make your logger an io.Writer instead and then use log.New. But that makes it a lot harder to get meta-information and so on in.</p></pre>nate510: <pre><p>Oh I have, and it works great for my own code. </p> <p>What I&#39;d like more specifically is for there to be some mechanism of capturing the log messages of a package I&#39;m using and sending them into my log configuration. It&#39;s not a huge deal, and low-level subpackages shouldn&#39;t log, but it does come up.</p></pre>Growlizing: <pre><p>I would only keep the first one:</p> <ul> <li>&amp;thing{}</li> </ul> <p>:(</p> <p>The others just feel redundant, though you could always make a function that behaves like the 3rd alternative.</p></pre>upboatact: <pre><p>so how would you make a pointer to an int? (aka new(int))</p></pre>SupersonicSpitfire: <pre><pre><code>ip := &amp;0 sp := &amp;&#34;&#34; tp := &amp;thing{} </code></pre></pre>recadra: <pre><p>For the sake of consistency, it should be possible to do it like this:</p> <p><code>i := &amp;int(0)</code></p> <p>But at the moment, the &#34;cleanest&#34; one-line solution I could come up with was this:</p> <p><code>i := func(i int) *int { return &amp;i }(0)</code></p></pre>TheMerovius: <pre><blockquote> <p>For the sake of consistency, it should be possible to do it like this: <code>i := &amp;int(0)</code></p> </blockquote> <p>That isn&#39;t consistent at all. If anything, it should be <code>&amp;int{0}</code>, but that&#39;s also quite weird and has some implications (for example: What if I do <code>type int struct{ …}</code>?).</p> <blockquote> <p>But at the moment, the &#34;cleanest&#34; one-line solution I could come up with was this: <code>i := func(i int) *int { return &amp;i }(0)</code></p> </blockquote> <p>I think being &#34;one-line&#34; is a pretty useless restriction.</p></pre>xargon7: <pre><blockquote> <p>(for example: What if I do <code>type int struct{ …}</code>?)</p> </blockquote> <p>You&#39;d get the same thing you get now with <code>new</code>: <a href="http://play.golang.org/p/zYx43z7NG9" rel="nofollow">http://play.golang.org/p/zYx43z7NG9</a></p></pre>boomshroom: <pre><p>That&#39;s confusing. Why would you ever shadow a builtin type? If you need your own int type, just call it myInt or Int.</p></pre>xargon7: <pre><p>I agree completely, which is why I think that <code>&amp;int{0}</code> is a perfect way to allocate and initialize a new int pointer.</p></pre>zond: <pre><p>In two steps:</p> <p>i := 0</p> <p>ip := &amp;i</p></pre>Growlizing: <pre><p>You have a point.</p></pre>tty5: <pre><p>I&#39;d remove <code>database/sql</code> package - it&#39;s quite bad and due to its restrictions makes the underlying drivers equally bad. </p> <p>As long as it remains a part of standard library nobody is going to touch it, let alone fix it.</p></pre>TheMerovius: <pre><p>Can you elaborate? Because I never saw anything wrong with it.</p></pre>tty5: <pre><p>For one it doesn&#39;t even support database drivers returning all types go has. Let&#39;s say you have a field that is an array of ints in your db. Now you have to return it as <code>[]byte</code> and create a custom scanner on top of <code>database/sql</code>.</p></pre>TheMerovius: <pre><p>But most SQL databases don&#39;t support that anyway, right? So, in the end, you would still need to do precisely the same thing for most of the databases. And nothing prevents the <em>drivers</em> from doing that implementation for you (akin to <a href="https://godoc.org/github.com/lib/pq#NullTime">pq.NullTime</a>).</p></pre>tty5: <pre><p>MySQL doesn&#39;t support it. Postgresql, Oracle, MSSQL do.</p> <p>The problem is that <code>database/sql</code> arbitrarily restricts what a driver can return and imo it should just allow everything and let driver, which is database specific, decide what should be supported or not. It makes no sense enforcing it on a generic sql level.</p></pre>dvirsky: <pre><p>I haven&#39;t worked much with the sql package, but I think the standard library can benefit from a little cleanup. We have obscure packages like <code>index/suffixarray</code>that from a little godoc search appears as if only one place in docker uses it and that&#39;s the full extent of it&#39;s usage. And a few packages that could use a bit of an overhaul - flag and log come to mind as too spartan (if there are hundreds of alternative loggers and flag parsers on github, it says something. there aren&#39;t alternative http clients AFAIK)</p></pre>kortemy: <pre><p>Yes yes thousand times yes.</p></pre>f2u: <pre><p>The return value inconsistency in <code>io.Reader</code>. The specification should say that implementations may return either a positive number, or an error, but not both at the same time. The current lack of that guarantee breaks the usual error handling pattern.</p></pre>threemux: <pre><p>Disagree. I think it&#39;s useful to know that some bytes were successfully read before an error. Depending on what you&#39;re reading, those bytes could be useful. I guess you could get that info from a custom error type, but that seems less optimal when you already have the bytes read return value.</p></pre>daydreamdrunk: <pre><p>If you&#39;re wrapping it in something like bufio.Reader it&#39;s not a problem, it provides the stronger guarantee. It&#39;s also really easy to make a wrapper type that does this, too, like: <a href="https://github.com/jimmyfrasche/simple" rel="nofollow">https://github.com/jimmyfrasche/simple</a> <a href="https://godoc.org/github.com/jimmyfrasche/simple" rel="nofollow">https://godoc.org/github.com/jimmyfrasche/simple</a></p></pre>champioj: <pre><p>I would remove naked return, I&#39;ve never felt like they were adding anything to the language. I wanted to say, remove[1] := but it would need a replacement. Actually, it should be reworked with &amp;/new and the multiple way to declare a variable.</p> <p>[1]I don&#39;t like := because it does not mix well with the composition of multiple return value and shadowing. You can&#39;t simply explain one of these 3 concept without explaining the two other.</p></pre>_medved_: <pre><p>I&#39;d remove</p> <p><code>u := new(User)</code></p> <p>and only allow</p> <p><code>u := &amp;User{}</code></p></pre>besna: <pre><p><code>errors.New()</code> and <code>fmt.Errorf()</code></p> <p>I really would like Go to have a better error system, where I can atleast switch over returned errors. It is hard to handle errors accordingly if all you have is dynamic string. It results more often in dropping and ignoring errors as I would like to.</p></pre>james-h: <pre><p>There is a better way. Don&#39;t limit yourself to using <code>errors.New()</code> and <code>fmt.Errorf()</code>. You can (and often should) create your own error types that convey more information. For example, if you&#39;re implementing a parser, you might want to return something like the following.</p> <pre><code>type ParseError struct { Expected []Token Found Token Position int } </code></pre> <p>Give that thing an <code>Error()</code> func and it&#39;s an error. </p></pre>i_regret_most_of_it: <pre><p>Sure, but it seems that the dominant pattern is to return an <code>error</code> even if you are returning a concrete error type, so they are both hard to discover and also a pain in the ass (syntactically) to then do a type assertion on. Pattern matching please.</p> <p>I know that I regularly rage at the stdlib for either returning stringly-typed errors or having to dig through the source of the stdlib to figure out if there is a concrete error type I can get at (and when there is, often it is not exported). I&#39;m going to use a different tool if I&#39;m made to compare strings as error-handling code.</p> <p>I need reliable information about the error! I need to differentiate between timeouts and DNS problems and other problems. I feel like the language has assumed that that information isn&#39;t part of my business logic, but it is.</p> <p>It&#39;s slightly disappointing that a language that puts such a strong focus on explicit error handling sort of half-assed the error interface.</p></pre>TheMerovius: <pre><blockquote> <p>a pain in the ass (syntactically) to then do a type assertion on. Pattern matching please.</p> </blockquote> <p>Huh? What&#39;s wrong with type-switches?</p> <blockquote> <p>I feel like the language has assumed that that information isn&#39;t part of my business logic, but it is.</p> </blockquote> <p>Not the language, only some APIs. And I think the most common reason, for not returning concrete error types, is that you then make them part of your API, which makes it harder to change internals, sometimes. Also, you might have highly system dependent possible types of errors (for example in the os package), so on Windows the type of an error signalling a timeout may be different than on unix.</p> <blockquote> <p>t&#39;s slightly disappointing that a language that puts such a strong focus on explicit error handling sort of half-assed the error interface.</p> </blockquote> <p>I like the error interface. I dislike that a lot of libraries fail to properly distinguish between returning just some string-error, but that is not a failure of the language, IMO, but a failure of the libraries (and sometimes it just has to be that way).</p></pre>besna: <pre><p>Yes, I know I can do it for my code, but I would like to see also in other packages. <code>os</code> for example tries to solve it, but is ugly and not common. </p></pre>qudat: <pre><p>How do you read the data from an <code>error</code> type? I couldn&#39;t find any information on getting the actual string in the error.</p></pre>TheMerovius: <pre><p><code>err.Error()</code> returns the error string.</p></pre>SupersonicSpitfire: <pre><p>It&#39;s weird that it&#39;s not <code>.String()</code> instead.</p></pre>mdempsky: <pre><p>That&#39;s intentional to keep <code>error</code> and <code>fmt.Stringer</code> as logically distinct types. Lots of values have a <code>String</code> method so they can be printed, but don&#39;t make sense as an <code>error</code> value.</p></pre>SupersonicSpitfire: <pre><p>I think that&#39;s an inconsistent way to tell them apart. If it can be converted to string, I think the method should always be named .String().</p></pre>boomshroom: <pre><p>Most error implementations have String() functions, though Error() is what specifically allows it to be used as an error.</p></pre>dilap: <pre><p>For questions about built-in types, there&#39;s a dummy package &#34;builtin&#34; which exists just to give documentation:</p> <pre><code>$ godoc builtin error type error interface { Error() string } The error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error. </code></pre></pre>tty5: <pre><p>Shameless plug: <a href="https://github.com/goware/errorx" rel="nofollow">https://github.com/goware/errorx</a></p></pre>dchapes: <pre><p>Among other issues, your use of a package global <code>verbosity</code> (and the user callable <code>errorx.SetVerbosity</code>) makes your type unsafe for concurrent use. An <code>error</code> type that is unsafe for callers (without doing their own synchronization) to do a simple <code>log.Println(err)</code> isn&#39;t terribly useful.</p></pre>causal_friday: <pre><p>I don&#39;t really like switching on errors (if the program could fix the error, why didn&#39;t it do that the first time?) but detecting them is easy if you do:</p> <pre><code>var SomeError = errors.New(&#34;some error&#34;) func Foo() error { return SomeError } if err := Foo(); err != nil { if err == SomeError { ... { } </code></pre> <p>Or</p> <pre><code>func IsSomeError(error) bool {...} if err := Foo(); err != nil { if IsSomeError(err) { ... } } </code></pre> <p>The standard library tends to take this approach where you might care (ENOENT for file operations, for example).</p></pre>TheMerovius: <pre><blockquote> <p>if the program could fix the error, why didn&#39;t it do that the first time?</p> </blockquote> <p>Because for different use cases different errors are &#34;fixable&#34; and the fixes differ. For example I am often displeased by http.Client&#39;s behavior in regards to redirects. For some use cases, that should be treated as an error. By deciding to &#34;fix&#34; this error upstream they made it a lot more complicated for me to handle this condition.</p></pre>miket21: <pre><p>I&#39;ve never really understood why both &#34;new&#34; and &#34;make&#34; exist. Is there a way to consolidate them into just one keyword?</p></pre>TheMerovius: <pre><p>I think the reason is, that <code>new(T)</code> creates a Pointer to a <code>T</code>, whereas <code>make()</code> does not create Pointers, but values. I don&#39;t think this is a <em>good</em> reason, but I think it is the historical explanation why there were two different keywords and then, with go1, no one thought about conflating them.</p> <p>One way to unify them, would be e.g. replacing <code>new(T)</code> with <code>make(*T)</code> (or <code>make(T)</code>, but I think the former is better understandable, because then <code>make(T,…)</code> always returns a <code>T</code>, no matter what kind of type <code>T</code> is).</p></pre>dilap: <pre><p>replacing new(T) with make(*T) is basically a great idea, it was even discussed at some point on the godev list, but they decided not to do it because they thought it would look too confusing to people coming from other languages.</p> <p>but honestly I think it lessen confusion, since coming from another language if you see new(type) is a thing, you&#39;re going to think it&#39;ll do constructor-y stuff and is the main way to allocate new types, when in fact in go you barely ever use it.</p></pre>comrade_donkey: <pre><p>I would allow var to be used only on the package level. Inside function bodies the short declaration syntax should be the only one allowed, in my opinion. Reason being that many people get confused about initialization and zero-values with the var-syntax without an immediate right-hand-side. In that aspect, the := operator forces you to be explicit about the value of whatever you&#39;re declaring.</p></pre>naturalselection: <pre><blockquote> <p>he only one allowed, in my opinion. Reason being that many people get confused about initialization and zero-values with the var-syntax without an immediate righ</p> </blockquote> <p>But then I think this would force memory allocation for maps, slices etc. I&#39;d rather this not be the case.</p></pre>comrade_donkey: <pre><p>That&#39;s not the case. See that it&#39;s confusing?</p> <pre><code>m := []string(nil) </code></pre> <p>Is more explicit than</p> <pre><code>var m []string </code></pre> <p>Maybe not as pretty, but clearer.</p></pre>naturalselection: <pre><p>And for maps, I assume you&#39;d propose a new nil instantiser syntax?</p></pre>comrade_donkey: <pre><pre><code>m := map[string]int(nil) </code></pre> <p>works.</p></pre>naturalselection: <pre><p>indeed, interesting</p></pre>SupersonicSpitfire: <pre><p>The optional semicolons could be removed. So could the support for single-line imports.</p></pre>TheMerovius: <pre><blockquote> <p>The optional semicolons could be removed.</p> </blockquote> <p>What do you mean? They are already optional, no need to remove them? On the other hand they serve a purpose by disambiguating the grammar (the spec would explode if you removed them and try to remove any ambiguities by comment, not to mention a lot harder to read and implement). So, I don&#39;t see any downsides to them, but I do see upsides.</p></pre>SupersonicSpitfire: <pre><p>You could keep the spec, but disallow them in actual code (wherever it is practical) to make Go code more similar. Who writes Go code with semicolons in the first place? </p></pre>ptman: <pre><pre><code>if err := maybeError(); err != nil { panic(err) } </code></pre></pre>SupersonicSpitfire: <pre><p>That semicolon could be part of the <code>if</code> syntax, though.</p> <p>It could even be replaced with <code>&amp;&amp;</code> (and the assignment could evaluate to true).</p></pre>ItsNotMineISwear: <pre><p>slices being invariant</p></pre>divoxx: <pre><p>The way assignment works, = vs := as well as if/for scopes and shadowing. I think it overly complicates something that should be really simple.</p></pre>lapingvino: <pre><p>I love this for the same reason I love the way Go handles pointers: it prevents magic from happening and keeps you in control of things that matter.</p></pre>shazow: <pre><p>I keep going back and forth on this, but I&#39;d propose grouped type declarations as a candidate for removal:</p> <pre><code>var foo, bar, baz int // Rather than... var foo int, bar int, baz int </code></pre> <p>Or</p> <pre><code>func Add(a, b, c int) int // Rather than... func Add(a int, b int, c int) int </code></pre> <p>I find myself relying on it when I&#39;m lazy, which feels counter to the kinds of habits Go generally encourages.</p></pre>headhunglow: <pre><p>Oh, that&#39;s easy: complex numbers. Is anyone using these?</p></pre>sacado: <pre><p>I did.</p></pre>klaaax: <pre><p>I heard go 2.0 will break, is that true ? do they really want to break compatibility like Python 2 and 3 ? </p></pre>szabba: <pre><p>That would be the point of changing the major version (assuming the Go-team would follow something roughly semver-like).</p> <p>That the Go team doesn&#39;t seem to have any intention of ever making a version 2.0 is a separate issue.</p> <p><strong>EDIT:</strong> Just to clarify: I don&#39;t think that that we&#39;re unlikely to see a version 2.0 is a <em>problem</em>.</p></pre>sedmonster: <pre><p>lol</p></pre>szabba: <pre><p>What&#39;s funny?</p></pre>TheMerovius: <pre><p>That&#39;s generally the point of a major version number increasing. That being said: go 2.0 will, for all intents and purposes, never happen (for appropriate values of &#34;never&#34;). If it <em>will</em> ever happen, I assume there will be an equivalent of <code>go fix</code> to do the heavy lifting of porting code over (that will work better than python&#39;s version of the same thing, because go is statically typed and can be refactored safer).</p> <p>In any case, spending even a second of worrying about this now is completely useless. :)</p></pre>saturn_vk: <pre><p>I&#39;d try to get rid of the negative effects of null. Make the pointers act as if they were initialized with a zero value, like their non pointer counterparts.</p></pre>TheMerovius: <pre><p>But that would negate every advantage of Pointers. If there is no meaningful nil, you could just always use Values. Think about how to implement a linked list with that. It&#39;s just weird. Even python has nil (or rather None, which is the same thing).</p> <p>[Edit] Come to think of it, I really can&#39;t see any language that doesn&#39;t have nil in some form or other. Haskell maybe. But I&#39;d argue that even Nothing is just a way to write nil…</p></pre>jahayhurst: <pre><p>There&#39;s a lot of things in go that I like, and I don&#39;t really think I&#39;m smart enough to complain about the things I don&#39;t like - based on what I&#39;ve seen so far, I&#39;m sure they&#39;re there for a reason.</p> <p>That aside, there is one thing I would be interested in seeing changed. Well, it&#39;d probalby take two changes actually. I don&#39;t know that it&#39;d break anything either - I think it could be done, but... once again, not smart enough - the go core devs are VERY smart.</p> <p>I would like to be able to put a function on the left side of an assignment operator. Whatever is returned by the right side of the operator would be passed to whatever on the left side. The second change would be that functions could accept values like that. And, of course, somewhere in there, we&#39;d have to be able to break up the values - route them to two different things.</p> <p>So, for instance, let&#39;s say I&#39;ve currently got a line like:</p> <pre><code>if numberToAdd, err = strconv.Atoi(inputString); err != nil { log.Fatal(err) } previousValue += numberToAdd </code></pre> <p>I would instead be able to do:</p> <pre><code>numberToAdd, log.Fatal = strconv.Atoi(inputString) previousValue += numberToAdd </code></pre> <p>Further, if I wrote a function:</p> <pre><code>var GlobalPreviousValue int func AddSomethingToIt(input int, err error) { if err != nil { log.Fatal(err) } GlobalPreviousValue += input return } </code></pre> <p>I could then do:</p> <pre><code>AddSomethingToIt = strconv.Atoi(inputString) </code></pre> <p>IDK if this would actually work. And I&#39;d bet this would lead to daisy chaining multiple assignment operators on the same line. The point of this is, it seems cleaner than the idiomatic way of doing this - with the if statement like that. Plus, when you do this the idiomatic way, you can&#39;t use <code>:=</code> - if you do, it&#39;ll redeclare numberToAdd within the scope of that if statement, then discard it, which just always felt self defeating.</p> <p>Once again, idk, maybe this is already done? I think it&#39;s cleaner though.</p> <p>I&#39;ve also thought about doing something with <code>+=</code> in that scenario, but that&#39;s just... awkward - what all gets appended? what gets incremented? IDK, that seems... awkward.</p> <p>[edit]:</p> <p>And now, 10 seconds later, I already realize there are problems :p</p> <p>You can do the second part. It&#39;s not hard. You just don&#39;t use an assignment operator:</p> <pre><code>AddSomethingToIt(strconv.Atoi(inputString)) </code></pre> <p>However, for the first attempt, if <code>strconv.Atoi</code> happened successfully, <code>nil</code> would still be passed to <code>log.Fatal()</code>, which would still kill the thing.</p> <p>We&#39;d have to do assignment like.. right to left? and only do assignment if not nil?... That&#39;s just where the rabbit hole starts. :/</p> <p>Once again, the go core devs have outsmarted me :-)</p></pre>gsscoder: <pre><p>I hope to see a thread on what Go 2.0 should add to the language, but saying more here will be out of topic. So I&#39;ll put my though when the post (and if) will be created.</p> <p>If already exists please link it in the reply.</p></pre>DeedleFake: <pre><p>The GC and data races. I&#39;d love to see Go implement some form of Rust&#39;s ownership system. If they could do it without all the annoyance Rust comes with (Semicolons, horrific error messages, lack of tooling, awkward naming conventions, the trait system, lacking stdlib, confusion between language features and stdlib, etc.), I think that would pretty much be the perfect language.</p> <p>I also agree on <code>new(T)</code>. I&#39;ve never been sure why that isn&#39;t <code>make(*T)</code>.</p> <p>Edit: Judging from the downvotes, I think people are misunderstanding this comment. Would someone who&#39;s downvoting please reply and tell me what you disagree with?</p></pre>TheMerovius: <pre><blockquote> <p>Edit: Judging from the downvotes, I think people are misunderstanding this comment.</p> </blockquote> <p>Or they simply disagree.</p> <blockquote> <p>Would someone who&#39;s downvoting please reply and tell me what you disagree with?</p> </blockquote> <p>a) The question was about removing stuff, not adding stuff :) b) A go with rusts ownership model is a go I would stop using. It is incredibly tedious to need to worry about this kind of stuff and it involves an insane amount of bookkeeping for even trivial stuff. I don&#39;t want to worry about whether values are on the heap or the stack and who &#34;owns&#34; them (or rather, how to convince the compiler that I have thought about that and have an answer). In my experience, issues from this kind of stuff are rarely a huge problem and complicating 95% of code just to catch a rare and obscure bug isn&#39;t worth it, IMHO.</p> <p>I prefer the go way of giving you good tooling to catch these kinds of issues (and of course, you know &#34;not communicating by sharing memory, but sharing memory by communicating&#34;).</p></pre>: <pre><p>[deleted]</p></pre>cathalgarvey: <pre><p>Downvoting-as-disagreement is considered poor reddit form but is very common on <a href="/r/Golang" rel="nofollow">/r/Golang</a> IME.</p></pre>: <pre><p>[deleted]</p></pre>cathalgarvey: <pre><p>I don&#39;t know it, I couldn&#39;t know for sure of course. But I see lots of cases like this: on-topic, not false, cogent, civil, but downvoted for expressing an unpopular opinion.</p> <p>It&#39;s endemic to reddit but more obvious here and in <a href="/r/linux" rel="nofollow">/r/linux</a>, I think.</p></pre>b4ux1t3: <pre><p>I know this is silly, and something that can be fixed with a good IDE, but I am a </p> <pre><code>function() { //stuff } </code></pre> <p>Kinda guy. I&#39;m not trying to start a holy war, and I know WHY Go forces a stylistic choice on you, I just really want that option. Maybe even as just a compiler option.</p> <p>Personally, I think problems like this that are silly and small are what&#39;s keeping a lot of people away from a lot of languages. As programmers, we like our creature comforts and our habits, and when a language doesn&#39;t cater to them, we don&#39;t like to use that language. I&#39;ve been soldiering through this petty grievance because I really like Go and I think it will go far.</p></pre>bmurphy1976: <pre><p>Disagree. They made a decision, it&#39;s good enough, so stick with it. No more bike-shedding and that&#39;s a wonderful thing.</p></pre>TheMerovius: <pre><blockquote> <p>Personally, I think problems like this that are silly and small are what&#39;s keeping a lot of people away from a lot of languages.</p> </blockquote> <p>I believe more people will be turned off by having no universal style for all go code, than will be turned off by not having their own pet style be universal. If that is really something that bothers you enough not to use the language you&#39;d have to be pretty petty, IMO.</p></pre>b4ux1t3: <pre><p>Considering I had to preface my comment with &#34;I don&#39;t want to start a holy war&#34;, I disagree about it not being a sticking point for a lot of people. I do not, however, disagree that it is a petty thing.</p></pre>mdempsky: <pre><p>I think you&#39;ll enjoy using Go more if you learn to not sweat the small stuff like brace placement.</p></pre>kunos: <pre><p>meh, I&#39;ve been that kind guy too for 20+ years, but after a couple of years in Go I now write my C++ in &#34;gofmt&#34; style. It&#39;s really not a big deal and I am really glad the Go team went for that decision.</p></pre>borring: <pre><p>I think the first brace is required to not be on its own line due to the way semicolons are injected</p></pre>b4ux1t3: <pre><p>Yeah, I know. It saves a lot of time in large projects, and overall it&#39;s a good thing. </p> <p>It actually doesn&#39;t even affect me all that much, because I wrote a little script that goes through and deletes the newline character after all my function headers and then passes the resulting file over to Go. It keeps my original intact for readability purposes. Eventually I&#39;ll probably just get the hell over it and just write it correctly, but, for now, it works for me.</p></pre>nate510: <pre><p>I think default values for function parameters would be a nice addition. The inability to overload functions has the drawback of sometimes causing code duplication. This would help with that.</p> <p>I&#39;m guessing this won&#39;t happen, though, by the same reasoning that outlaws function overloading.</p></pre>CaptainShawerma: <pre><p>I wish they would take out nil and support optional types; null checks feel primitive and old-fashioned and I don&#39;t see any reason to continue supporting the Billion Dollar mistake. Plus, optional types might streamline Gos error handling.</p> <p>Operator overloading would be really nnice too! </p></pre>ItsNotMineISwear: <pre><p>I wouldn&#39;t count on Options in Go. It&#39;d require either another builtin generic or full-fledged support for generics. Also, once you have options, you need option combinators like map, flatMap/bind/&gt;&gt;=, getOrElse, orElse otherwise it&#39;s just kludgey. And getOrElse and orElse don&#39;t even work without easy call-by-name. And all of these things would surely be deemed as Too Complicated by the people in charge of Go.</p></pre>TheMerovius: <pre><blockquote> <p>Operator overloading would be really nnice too!</p> </blockquote> <p>Strong disagreement. There are <em>very</em> narrow domains where this is useful (I can think of Computer Algebra and Scientific Computing. Not really anything else) and for these domains, some DSL is better suited anyway. For all the other code, operator overloading just makes code surprising and complicated.</p></pre>kunos: <pre><p>TBH those things don&#39;t bother me at all. I don&#39;t mind having 2 ways to new something, as you&#39;ve pointed out, one gives you the possibility to initialize the fields. The third isn&#39;t really a language feature, it&#39;s a guideline. For 2.0, I&#39;d love to see the addition of operator overloading to allow 3D linear algebra code to look nicer that it does now. I understand the scare of seeing operators abused but the stdlib can and will set the tone for right usage of the feature. And, if I am allowed to dream, I&#39;d like a GC that can run, on request for a given amount of time. GC.run(2) runs for 2ms and comes back. I am not a compiler/runtime dev so I have no idea how hard this is, but I really don&#39;t understand how this is not the standard for GC.</p></pre>matttproud: <pre><p>I would propose removing the <a href="https://golang.org/doc/go1compat" rel="nofollow">source compatibility guarantees</a> for <a href="https://golang.org/pkg/runtime" rel="nofollow">pkg/runtime</a> since it is implementation-specific of the runtime. Core example: <a href="https://golang.org/pkg/runtime/#MemStats" rel="nofollow">runtime.MemStats</a>; this struct is loaded with implementation-specific detail that may neither apply to alternative runtimes nor ought it be generalized behind an API that abstracts the runtime&#39;s implementation. This is <a href="https://github.com/golang/go/issues/11890" rel="nofollow">already causing problems today</a>. The Java approach of the JMX beans for instrumentation are an example of what not to do, and I am writing this as someone who likes Java.</p></pre>kortemy: <pre><p>To semantically answer your question, I would take out non-pointer method receivers.</p> <p>Non-pointer method receiver is just another function argument. It&#39;s redundant and confusing sometimes. It also fits the ideologically, by declaring a method receiver you are doing it because receiver should be mutable data the method operates on. What&#39;s the point in having immutable method receiver? Method receiver should always be mutable.</p> <p>It would clear confusion. For example when you implement an interface with pointer receivers, but then the value of your struct does not satisfy the interface. Pointer to it does.</p> <p>Solution? Method receiver must be pointer. And make both pointer and value satisfy the interface implementation. It is already working for non-interfaces, where pointer receiver automatically takes the address of that value (&amp;foo).bar() == foo.bar().</p></pre>TheMerovius: <pre><blockquote> <p>Non-pointer method receiver is just another function argument.</p> </blockquote> <p>In <em>precisely</em> the same way as a pointer method receiver is (which is &#34;pretty much in no way&#34;).</p> <blockquote> <p>What&#39;s the point in having immutable method receiver? Method receiver should always be mutable.</p> </blockquote> <p>Knowing that a value <em>can not change</em> in a method enables a lot of compiler optimizations, that are not possible otherwise. For example, a value receiver can not be changed by a different goroutine, so the compiler can just load the value into a register and do operations there, instead of having to hit the memory every time something is read from (or written to) the receiver.</p> <p>It also is a signal to the user: A method on a value receiver, for example, is automatically safe for concurrent use.</p></pre>thockin: <pre><p>const pointers would have been a way better design than value methods and would have achieved BETTER semantics. Today if you have a value receiver and you modify it, nothing warns you. If instead you had a const pointer the compiler could throw a tantrum.</p> <p>Additionally, there are times where Go&#39;s &#34;helpfulness&#34; in automatically converting values to pointers and vice-versa for the purpose of method calls breaks down (e.g. maps of values) and this makes the language inconsistent and puts landmines in front of users.</p></pre>kortemy: <pre><p>Hm, yes, I see your point.</p> <p>But riddle me this - how do you, the method caller, know if a method has a pointer or a non-pointer receiver? If (&amp;foo).bar() == foo.bar(), and I am just calling foo.bar(), I am not aware if foo is mutable or not. Only if I look at the source code. Which I shouldn&#39;t have to. Knowing if foo is mutable or immutable is very beneficial, but I know people wouldn&#39;t like the -&gt; operator :D</p> <p>And what are your thoughts on interface satisfaction? If interface is implemented with pointer receivers, should value be considered as interface implementation?</p></pre>TheMerovius: <pre><blockquote> <p>how do you, the method caller, know if a method has a pointer or a non-pointer receiver?</p> </blockquote> <p>You don&#39;t, I agree.</p> <blockquote> <p>but I know people wouldn&#39;t like the -&gt; operator :D</p> </blockquote> <p>I don&#39;t know. It is a convenience to not have a <code>-&gt;</code> operator, but I don&#39;t see a good way around requiring it, if it is really such a problem to not see whether a method uses a pointer or a value. It certainly is better than having no value receivers.</p> <blockquote> <p>And what are your thoughts on interface satisfaction? If interface is implemented with pointer receivers, should value be considered as interface implementation?</p> </blockquote> <p>I think it shouldn&#39;t. I think <code>Foo(bar)</code> shouldn&#39;t be able to change <code>bar</code>, unless <code>bar</code> is a pointer. But I have to say that I am not very passionate about this :) Overall, I probably didn&#39;t spend more than a handful of seconds due to the confusion of this, either way. :)</p></pre>kortemy: <pre><blockquote> <p>It certainly is better than having no value receivers. I can agree with that.</p> <p>I think it shouldn&#39;t. I think Foo(bar) shouldn&#39;t be able to change bar, unless bar is a pointer. But I have to say that I am not very passionate about this</p> </blockquote> <p>You are right, <code>Foo(bar)</code> should never change a <code>bar</code> unless its a pointer, I am very passionate about that. :) And I never thought about it that way. It would turn a value into a pointer without my knowledge. </p> <p>Thank you for this.</p></pre>bmurphy1976: <pre><blockquote> <p>I don&#39;t know. It is a convenience to not have a -&gt; operator, but I don&#39;t see a good way around requiring it, if it is really such a problem to not see whether a method uses a pointer or a value. It certainly is better than having no value receivers.</p> </blockquote> <p>Personally, I&#39;d rather have the confusion over pointer and non-pointer receivers than to ever have to type <code>-&gt;</code> again. I have a limited amount of mileage left in my wrists. I&#39;d like to preserve my ability to type as long as I can. :)</p></pre>jerf: <pre><blockquote> <p>Non-pointer method receiver is just another function argument.</p> </blockquote> <p>No, it&#39;s not. It&#39;s what decides whether a value implements an interface, and that&#39;s how Go achieves polymorphism.</p> <p>Remove interfaces and polymorphism from the language and <em>then</em> they would just be arguments... but... uhh... that&#39;s a pretty different language. Much less useful.</p></pre>Jaydeving: <pre><p>Syntax sugar for anonymous functions/closures I guess</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3536 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传