Generics: specific use cases

agolangf · · 399 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>There&#39;s been a <a href="https://www.reddit.com/r/golang/comments/6n2g8w/go2_is_real/">lot</a> <a href="https://www.reddit.com/r/golang/comments/6n31hj/toward_go_2/">of</a> <a href="https://www.reddit.com/r/golang/comments/6na5sy/it_came_to_them_with_a_message/">discussion</a> over the last few days about generics in Go 2. I&#39;d like to present one axiom and one suggestion for a discussion here.</p> <p>The axiom: the best aspect of Go is that it minimizes the WTF/hr rate when reading other peoples code. What ever changes are made to the language, this above all else should be preserved. And the only way to test that is by reading other peoples code with generics.</p> <p>The discussion: find examples in public repos of code other people wrote that you think could be improved by generics and provide some examples of what you think they would have written. This means that the style should be consistent with the original author, not how you would have written it. Doesn&#39;t need to be a full re-write, just enough to show what the use of generics would look like. This gives us a place to explore different models of how generics could be implemented in Go 2. </p> <p>There are already several forums for discussing the merits and disadvantages of generics I don&#39;t think we need another. I&#39;d suggest we keep this to a collection of specific use cases and possible implementations that can be referenced in those and future discussions. And it&#39;s consistent with the request from the core team.</p> <p>For the sake of being up front, I&#39;m personally in the camp that does not want generics, but I could change my mind on the matter - I did once before. But I need to see a model that is highly readable across most cases and many skill levels.</p> <p>Update: I appreciate the interest in the topic, but let&#39;s try and keep this thread limited to code and move the discussions to the other threads on the topic. Thanks to everyone that&#39;s posted code or repo links.</p> <hr/>**评论:**<br/><br/>Akira1364: <pre><p>Here&#39;s an example of something I can do in Object Pascal (which has my favorite take on generics out of all compiled languages that implement them):</p> <pre><code>unit Example; interface uses Classes, Generics.Collections; type TExampleRecord = record IntField: Integer; StringField: String; BooleanField: Boolean; end; TExampleRecordRecord = record InternalExampleRecord: TExampleRecord; end; TExampleRecordList = class(specialize TList&lt;TExampleRecord&gt;) end; TExampleRecordRecordList = class(specialize TList&lt;TExampleRecordRecord&gt;) end; implementation end. </code></pre> <p>That code (which is somewhat silly by design to show that generics allow for easy handling of complex structures) gives me two fully functional list classes that have all the necessary methods (i.e. Add, Remove, Insert, Sort, IndexOf, e.t.c.) implicitly pre-implemented and that automatically return (and take) the correct types from and to each of those methods. (As in, if you try to add a TExampleRecordRecord to a TExampleRecordList, the application will simply fail to compile due to the incorrect type. There is absolutely no risk of the boogeyman &#34;type unsafety&#34; that some people seem to think is a thing that actually exists with properly implemented generics.) Now, think about how you&#39;d implement something like the above in Go... suffice to say it would take a <em>little more work</em>, at the very least. </p> <p><strong>TLDR:</strong> The introduction of generics into any given language will never have any downsides when done by developers who are remotely competent. It&#39;s not like you even <em>have</em> to use them if/when available! The fact that we&#39;re actually at a point where we&#39;re needing to have this ridiculous discussion about providing &#34;real use cases&#34; is, well, ridiculous.</p></pre>floatdouble: <pre><p>Easy example. Everything in this repo would be type safe:</p> <p><a href="https://github.com/emirpasic/gods">https://github.com/emirpasic/gods</a></p></pre>beeker1121: <pre><p>This is coming from my own experience, so please take my thoughts with a grain of salt.</p> <p>I think a library I wrote awhile back is a great example of what Rob Pike and others are trying to allude to when they&#39;re asking the community to show real world use cases of generics.</p> <p>I wrote <a href="https://github.com/beeker1121/goque" rel="nofollow">goque</a>, which is centered around embedded and persistent stacks and queues. Since the stacks and queues are persistent and not memory based, the Go port of LevelDB which is a simple key-value store is used for that. LevelDB is awesome in that both keys and values (both in Go and other language implementations, afaik) are treated as arbitrary length byte slices. In a way, this is really just a... generic... way of storing data. Storing an integer, string, or a complete struct is possible by simply converting it to a byte slice representation.</p> <p>Real world, this package as well as the LevelDB package do not require generics.</p> <p>Let&#39;s say too that I absolutely needed to store a definitive type using Go and LevelDB. Well, even then, my real world use case of it being used in production in our custom web crawler, I&#39;m only storing strings, nothing else. All of the methods I use could just take strings as parameters.</p> <p>I think this is more what the Go devs are referring to. It seems like it would be nice to have generics just for the options, but in reality, would the majority of uses cases would simply copy the code over from the GoDS source and replace <code>interface{}</code> with whatever type they need?</p> <p>Again, own personal real world example, not meant to take away from the use cases of anyone else.</p></pre>Sythe2o0: <pre><p>I think this is true except for when you are doing a lot of intensive algorithmic work with these types of data structures. I tried building some <a href="https://github.com/200sc/go-compgeo">computational geometry tools</a>, and the interfaces for searchable trees is terrible without generics. I had to use BSTs for a lot of types and I didn&#39;t want to duplicate the code five times so I tried doing it a Go way and it fell over constantly due to the lack of type safety. </p></pre>callcifer: <pre><blockquote> <p>Storing an integer, string, or a complete struct is possible by simply converting it to a byte slice representation.</p> </blockquote> <p>For a persisent store, like in your use case, this is pretty much unavoidable (and therefore acceptable), but for in-memory lists/sets/trees converting values to/from <code>[]byte</code> usually involves allocating memory which might not be acceptable on hot paths.</p></pre>adamcolton: <pre><p>Cool, now lets see if we can get some examples of how that might look using different with ideas for how generic could work in Go.</p></pre>callcifer: <pre><p>I&#39;m not sure what you are asking? Syntax maybe? Just replace <code>interface{}</code> with, say,<code>T</code>:</p> <pre><code>type Set&lt;T&gt; struct { Add(element T) Remove(element T) Contains(elements T) bool } func main() { mySet := new(Set&lt;int&gt;) mySet.Add(1) // works mySet.Add(&#34;1&#34;) // compile error var foo interface{} foo = 1 mySet.Add(foo) // also compile error mySet.Add(foo.(int)) // compiles and works fine, but potentially a panic risk at runtime } </code></pre> <p>Obviously syntax can be whatever, but you get the basic idea.</p></pre>ctbel: <pre><p>Good job but I think you meant</p> <pre><code>mySet.Add(&#34;1&#34;) // compile error </code></pre></pre>callcifer: <pre><p>Yes, sorry. Fixed.</p></pre>ctbel: <pre><p>No need to apologize mate :)</p></pre>adamcolton: <pre><p>Thanks. That&#39;s what we&#39;re looking for. It&#39;s a good example of one way it could be done.</p></pre>vAltyR47: <pre><p><a href="https://github.com/golang/proposal/blob/master/design/15292/2013-12-type-params.md" rel="nofollow">The last generics proposal</a> is basically this. The summary at the end talks about why they didn&#39;t implement it.</p></pre>zemo: <pre><p>what about implementation of the actual methods? </p> <p>that&#39;s a struct type, but it defines no fields, and it defined methods in its type declaration? That looks an awful lot like an interface type.</p></pre>: <pre><p>[deleted]</p></pre>zemo: <pre><p>I&#39;ve used generics in java and c++ and c#, I&#39;m not confused about that.</p></pre>callcifer: <pre><p>Yes, that&#39;s basically pseudo code, I didn&#39;t want to write down the actual implementations (because they are pretty basic for a set), but imagine something like this:</p> <pre><code>type Set&lt;T&gt; struct {} func (s *Set) Add(e element&lt;T&gt;) { // do stuff } </code></pre> <p>Hope this clarifies things a bit.</p></pre>titpetric: <pre><p>How would you handle the declaration for Add for T in [int,string]? Overloading? select e.(type)? Yes, I know it&#39;s pseudo code, I&#39;m just interested in your thoughts :D</p></pre>callcifer: <pre><p>That&#39;s a pretty good question actually. I would <em>prefer</em> overloading because the code would look much cleaner, but I think the Go developers would prefer a type switch, simply because it would be easier to read. But honestly, I&#39;m fine with both :)</p></pre>titpetric: <pre><p>If you&#39;re using switch, you can create your own generics implementation (<em>ahem, at least for this example</em>) today, here&#39;s a <a href="https://play.golang.org/p/4DLhRqjnQ6" rel="nofollow">playground link</a>. If you would literally have to implement the same body for generics, what does it change if there&#39;s an <code>T</code> instead of <code>interface{}</code> there? If you miss-type a field in the Capsule case, you still get type safety and a compile-time error, <a href="https://play.golang.org/p/n5EC2vop_7" rel="nofollow">playground example</a>, so I&#39;m still missing a real answer for &#34;what&#39;s the point?&#34;. </p> <p>Best guess: If you had overloading it would give you a compile-time error for a method that wouldn&#39;t be declared for any &lt;T&gt; based struct which you create. This would beat the switch/default: panic() and move the error of an unsupported type from runtime to compile time. Also, it would give you a way to <em>extend</em> generic structures from external packages supporting your own concrete T&#39;s. Not that you couldn&#39;t do some of this with embedding?</p> <p>Maybe there&#39;s a finer/bigger point that I&#39;m missing? Edit: <em>yes, I&#39;m aware that this might be a limited example of what the generics discussion as a whole is about, so please don&#39;t rip me a new one</em>. Edit 2: added extend/concrete T&#39;s line on end of second paragraph.</p></pre>callcifer: <pre><blockquote> <p>Best guess: If you had overloading it would give you a compile-time error for a method that wouldn&#39;t be declared for any &lt;T&gt; based struct which you create</p> </blockquote> <p>Yes, <a href="https://play.golang.org/p/GLvCnTadxI" rel="nofollow">exactly</a>.</p> <blockquote> <p>This would beat the switch/default: panic() and move the error of an unsupported type from runtime to compile time.</p> </blockquote> <p>Yes, and compile time type safety is the single most important feature of generics, in all languages.</p> <blockquote> <p>so please don&#39;t rip me a new one.</p> </blockquote> <p>Heh, no worries. I actually think this is a very useful discussion.</p></pre>Emacs24: <pre><blockquote> <p>type Set&lt;T&gt; struct { Add(element T) Remove(element T) Contains(elements T) bool }</p> <p>func main() { mySet := new(Set&lt;int&gt;)</p> <pre><code>mySet.Add(1) // works mySet.Add(&#34;1&#34;) // compile error var foo interface{} foo = 1 mySet.Add(foo) // also compile error mySet.Add(foo.(int)) // compiles and works fine, but potentially a panic risk at runtime </code></pre> <p>}</p> </blockquote> <pre><code>type Set[T] struct { Add(element T) Remove(element T) Contains(elements T) bool } func main() { mySet := new(Set[int]) mySet.Add(1) // works mySet.Add(&#34;1&#34;) // compile error var foo interface{} foo = 1 mySet.Add(foo) // also compile error mySet.Add(foo.(int)) // compiles and works fine, but potentially a panic risk at runtime } </code></pre> <p>&lt;&gt; template/generic parameters proved to be highly unreadable</p></pre>MatthiasLuft: <pre><p>How would this look like using go generate?</p></pre>callcifer: <pre><p>It would look horrible. You would have N structs and N*M functions (where M is the number of methods of the datastructure) for N different data types and 99% of it would be redundant, ugly code duplication.</p></pre>adamcolton: <pre><p>I&#39;d rather avoid this here. Weighing the differences between generics and code generation is both important and complex, but there are other discussions already started. The repo provided is a good case for consideration. I&#39;d like to see submissions showing how it might be adapted to work with generics and I wouldn&#39;t be opposed to submissions comparing that code generation. But I really think we keep this a collection of examples and let the discussions happen elsewhere.</p></pre>callcifer: <pre><blockquote> <p>I&#39;d like to see submissions showing how it might be adapted to work with generics</p> </blockquote> <p>I agree, I&#39;ve given one such example above.</p> <p>By the way, I&#39;m curious what you mean by this:</p> <blockquote> <p>I need to see a model that is highly readable across most cases and many skill levels.</p> </blockquote> <p>As far as I&#39;m concerned, there is only one &#34;skill level&#34; here. Those who have learned Go, and those who have not. Otherwise, we&#39;d go down an endless rabbit hole. What skill level does <code>make</code> target? How about <code>new</code>? Or how about pointers <code>*</code>?</p> <p>The point is, you either learn them - and hence use them - or you don&#39;t.</p> <p>On readability I agree with you. Though I would consider generics implementations like Java and C# to be readable enough.</p></pre>adamcolton: <pre><p>Skill level varies. Not everyone is a great programmer and that is ok. It&#39;s important that code written by below-average developers is still easy to read and maintain because statistically, half of us are below average. Only considering the best case would look like is mis-guided, we need to consider what the majority of code will look like.</p></pre>callcifer: <pre><blockquote> <p>It&#39;s important that code written by below-average developers is still easy to read and maintain</p> </blockquote> <p>But wouldn&#39;t that also mean if a feature is &#34;too complex&#34; for below average developers, they would either:</p> <ul> <li>not use it</li> <li>use an already implemented solution by people who know better</li> </ul> <p>There is a reason &#34;do not invent your own crypto&#34; is a thing. Most developers can&#39;t write high quality crypto code and that&#39;s perfectly fine. Every software, every feature has its <em>users</em> and <em>developers</em> and there is nothing wrong with that.</p></pre>DualRearWheels: <pre><p>Do not invent your crypto isn&#39;t because developers can&#39;t write quality code, but because that code hasn&#39;t been in wild long enough getting dissected by large number of smarter and more motivated people searching for holes in it.</p> <p>Even crypto designed by most capable cryptologist don&#39;t mean much before it is time tested by peers.</p></pre>callcifer: <pre><blockquote> <p>Do not invent your crypto isn&#39;t because developers can&#39;t write quality code, but because that code hasn&#39;t been in wild long enough getting dissected by large number of smarter and more motivated people searching for holes in it.</p> </blockquote> <p>Oh it&#39;s definitely for both. The sheer number of &#34;secure password libraries&#34; (where, by &#34;secure&#34; they mean double md5 etc.) you can find online is proof of that.</p></pre>DualRearWheels: <pre><p>Double MD5 is not quality code. In this context quality has more to do with developers understanding of cryptology than with programming practices used to write source code.</p></pre>floatdouble: <pre><p>I agree that it&#39;s important that code written by below-average developers is easy to read and maintain, but below-average developers will always make bizarre design decisions and abuse features and make code hard to read. </p> <p>I think Go is a really simple language and I love Go because of it, but an ex-coworker of mine still managed to write a lot of totally unmaintainable and unreadable code.</p></pre>boompleetz: <pre><p>We should just think about &#34;what is likely to be copied and pasted from Stack Overflow questions to the most frequent cases&#34;.</p></pre>julesjacobs: <pre><p>Below average developers won&#39;t be implementing data structure libraries so they don&#39;t need to know the details of generics. They only need to know how to use libraries that use generics, and they already do because it&#39;s the same as arrays of a type T. In fact, generics would make their life easier. I don&#39;t think anybody would argue that below average developers are better served by having all their arrays be of type interface{}, because that makes code unreadable, not type safe, and slow. The same applies to other uses of generics.</p></pre>shovelpost: <pre><blockquote> <p>Though I would consider generics implementations like Java and C# to be readable enough.</p> </blockquote> <pre><code>public static &lt;I, O&gt; ListenableFuture&lt;O&gt; chain (ListenableFuture&lt;I&gt; input, Function&lt;? super I, ? extends ListenableFuture&lt;? extends O&gt;&gt; function) </code></pre> <p><a href="https://youtu.be/5kj5ApnhPAE?t=2m8s" rel="nofollow">dear god make it stop</a></p></pre>floatdouble: <pre><p>I don&#39;t think the problem here is generics exactly? </p> <p>Part of why this looks so horrible is that Java decided to implement Functions using generics. Another part is the confusing nature of inheritance and wildcards in Java, so the keywords like ?, super and extends make this very hard to read.</p> <p>This particular example wouldn&#39;t look nearly as bad in Go.</p></pre>Femaref: <pre><p>C# has generics on functions as well and it doesn&#39;t look as bad as java does.</p></pre>callcifer: <pre><p>That&#39;s a funny example, and yes it&#39;s possible in those languages, but it doesn&#39;t have to be like that for Go. Just <code>type Foo&lt;T&gt; struct</code> would cover 90%+ of the cases.</p></pre>matttproud: <pre><p>Seconded. Java was my daily driver before coming to Go. Each time I have to touch a piece of code that is Future-heavy, my remaining joy for the language dissipates rapidly.</p></pre>saturn_vk: <pre><p>What do you find unreadable about this? It looks perfectly readable to me</p></pre>floatdouble: <pre><p>Assuming you are not being sarcastic, the idea here is really simple yet Java makes it seem like something very complicated is happening. In Haskell, this would look something like this:</p> <pre><code>chain :: ListenableFuture i -&gt; (i -&gt; ListenableFuture o) -&gt; ListenableFuture o </code></pre> <p>And the type signature almost tells you what the function definition is going to be.</p></pre>kybin: <pre><p>So I guess library makers could have some problem, because their library should cover as many situation as possible. (safely)</p> <p>On the other side, application makers maybe not that bother. Because they have very specific problem.</p></pre>callcifer: <pre><p>Even in applications, if I have a method that needs to work with integers, I&#39;d still like it to work - in a type safe manner - with int8, int16, int32, int64 and int without code duplication.</p></pre>AluekomentajaArje: <pre><p>This does raise a potential source of problems, though. Right now, we get the benefit of compile-time checking of types so for example int8(1) &amp; 0xFF00 results in a compile error (constant 65280 overflows int8). If one were to make a generic function that accepted all sorts of ints as an input but was expected to result int8, int16 etc, you would lose that type checking and I can see that causing bugs down the road. The other option would be that the compiler would enforce it through generics, too, but then you&#39;d still have to pretty much write all the separate versions anyway, but through a type-switch I imagine? </p></pre>callcifer: <pre><blockquote> <p>you&#39;d still have to pretty much write all the separate versions anyway, but through a type-switch I imagine?</p> </blockquote> <p>Yes, I think you are right, but personally I would still prefer to have a single type switch instead of many different functions.</p></pre>floatdouble: <pre><p>I think you raise a good point. I feel like data structures are generally when I feel the pain of lack of generics in Go because Go forces me to write boilerplate and doesn&#39;t give me type safety that I want from a statically typed language. </p> <p>For applications, it would be an extraordinary scenario for me to genuinely need generics which is maybe why most gophers don&#39;t feel like they need generics. I would even argue that liberal use of generics in applications generally makes code harder to understand, so I see the appeal of wanting to keep it out of the language.</p> <p>Maybe a good compromise would be to have a standard library of all of the commonly used data structures be implemented generically like slices and maps.</p></pre>kaeshiwaza: <pre><p>It&#39;s not because a problem can be resolved with generics that it&#39;s the only way to do it. With closure for example, with interface (i don&#39;t mean interface{})... For this kind of features it&#39;s maybe better to add them in the stdlib like map and slice than add generics for everything.</p> <p>I think the difficulties when we come from a language where we use generics, or dynamic language, is that we want to do exactly the same. But when we can think a little bit different we can find other ways and maybe better way. Generics has a lot of advantages but it&#39;s also very difficult to maintain in the long term.</p></pre>callcifer: <pre><blockquote> <p>With closure for example, with interface</p> <p>But when we can think a little bit different we can find other ways and maybe better way</p> </blockquote> <p>Can you give some for examples for this? I would be really interested to see what an alternative to generics would be that keeps the baseline properties:</p> <ul> <li>compile time type safety</li> <li>no code duplication</li> </ul></pre>champioj: <pre><p>I think it&#39;s a great exemple of why there need good use cases before implementing generics. And more importantly, stick to it. The repo given by the OP is one set of problem: containers and algorithms. </p> <p>Yet, the solution proposed (full generics) is really complicated and uneeded if you want to solve that specific problem. A better solution to that problem would more coarse generics, restricted to the package definition.</p> <p>How would it work? Define the generics type for the package like this:</p> <pre><code>package list { Value interface{} } </code></pre> <p>Here you define a type that is exported and the value can be used through the list package as if you would have defined <code>type Value interface{}</code></p> <p>Then to use it in another package you would specialize it in the import section like this :</p> <pre><code>import &#34;container/list&#34; { Value : int } </code></pre> <p>I really like this implementation over full generics like in java because</p> <ul> <li><p>It doesn&#39;t change the current way you code in go</p></li> <li><p>Most of the code would be unchanged, only the package line and the import would have to be changed</p></li> <li><p>It&#39;s really simple to understand and has a really low cognitive load.</p></li> <li><p>You don&#39;t have to understand ten&#39;s of different function with slightly different generic signature for a package, when you define a generic package, it has to have a clear meaning</p></li> <li><p>It is more orthogonal: It would be far easier to implement in the compiler and have way less impact with the different feature of go</p></li> </ul> <p>If 80% of the use cases with generics in go are data structure, I really think that would be the way to go.</p></pre>callcifer: <pre><p>Wow, that definitely looks like an interesting solution. I do hope something like that would <em>also</em> be considered, along with my Java-esque one above.</p> <p>Though one thing I worry about with your implementation, is that the imports would get messy very quickly, for example:</p> <pre><code>import ( intList &#34;container/list&#34; { Value : int } int32List &#34;container/list&#34; { Value : int32 } int64List &#34;container/list&#34; { Value : int64 } // ... and so on ) </code></pre></pre>champioj: <pre><p>Yes, that could be a problem. That&#39;s why the go team asked for real word examples so you could have some hints if it&#39;s just a minor point or if it snowball into a pile of mess.</p></pre>floatdouble: <pre><p>not to mention that the lack compile time safety comes with small runtime overhead</p></pre>kaeshiwaza: <pre><p>For example <a href="https://golang.org/pkg/sort/#Sort" rel="nofollow">https://golang.org/pkg/sort/#Sort</a></p></pre>callcifer: <pre><p>That only satisfies one of the conditions (compile time type safety), not the other (no code duplication).</p></pre>kaeshiwaza: <pre><p>You mean code duplication in Len, Less and Swap ?</p></pre>callcifer: <pre><p>Yes. If you have 3 types (say, <code>int16</code>, <code>int32</code> and <code>int64</code>) you need to write 9 functions, whereas a generic solution would only have 3.</p></pre>kaeshiwaza: <pre><p>You&#39;re right. But &#34;a little copying is better than a big dependency&#34;...</p> <p>I see a problem with duplication of &#34;sort&#34; algorithm because if there is a bug in it or an enhancement it&#39;s better to correct it one time. But for Less, Len and Swap i don&#39;t see a problem when they are duplicate. Or they are so simple that everybody can understand and it&#39;s very easy to copy, or they are not simples then it&#39;s better if they are not part of the sort algorithm.</p></pre>matttproud: <pre><p>Curious: How many people use Gods in practice in production code? Were it possible to use with generics how many of you would actually use this package?</p> <p><a href="http://users.ece.utexas.edu/%7Eadnan/pike.html" rel="nofollow">Rob Pike&#39;s rule of programming</a> seems pretty profound for promoting Go&#39;s traditional universal comprehensibility. If you haven&#39;t read this before, I would encourage you to do so. Kind of hits at the problem of day-to-day industrial usage as opposed to hypothetical missed opportunities.</p></pre>floatdouble: <pre><p>We use it in production and the lack of type safety is really annoying. It&#39;s not as big of a problem if you have an IDE, I suppose, but not everyone at my workplace uses one.</p> <p>I don&#39;t disagree with the quote, but I don&#39;t see how that&#39;s relevant... There are still very legitimate reasons to use binary trees/heaps/LRU-caches/etc to improve performance without writing fancy algorithms.</p></pre>callcifer: <pre><blockquote> <p>How many people use Gods in practice in production code?</p> </blockquote> <p>A <a href="https://github.com/search?q=%22github.com%2Femirpasic%2Fgods%22&amp;type=Code&amp;utf8=%E2%9C%93">fair bit</a> actually and this is just open source projects on one website.</p> <blockquote> <p>Were it possible to use with generics how many of you would actually use this package?</p> </blockquote> <p>Many, many more. See how popular datastructures libraries in other languages are. Hell, some of them include it in their standard libraries.</p> <blockquote> <p>Rob Pike&#39;s rule of programming seems pretty profound</p> </blockquote> <p>I really find these appeals to authority, these &#34;more royalist than the king&#34; attitudes, really confusing. Even Pike himself admits there are many design mistakes in Go. What makes you so sure that he was wrong about all those mistakes but is actually right on this one? Can you present technical arguments, without reaching for &#34;profound quotes&#34;, why generics would impose some insurmountable comprehension problem?</p></pre>cameronjerrellnewton: <pre><blockquote> <p>Even Pike himself admits there are many design mistakes in Go</p> </blockquote> <p>To my knowledge, not having generics is not one of those.</p> <blockquote> <p>I really find these appeals to authority, these &#34;more royalist than the king&#34; attitudes, really confusing</p> </blockquote> <p>What confuses me is when people throw popular logical fallacies out there and then somehow think they&#39;ve made a point. Appealing to the authority when the said person is very clearly an authority can be very useful, and is certainly not a fallacy in the correct context. </p> <p>The American Justice system, for example, can be viewed as a system built on appealing to authorities. When a lower district or circuit court cannot decide a case, it quite literally appeals to authority. When no one can decide, it is place upon the ultimate judicial authority in the Union, the Supreme court.</p> <blockquote> <p>The valid form of [the appeal to authority] argument is one in which a recognized and knowledgeable authority on the relevant subject is appealed to by citing a statement by that authority. </p> </blockquote> <p>Just like the slippery slope fallacy is not always a fallacy, and sometimes appeal to emotion is often the only mechanism to get human beings to move and achieve something -- we are not robots with infallible logic, and sometimes we need to take shortcuts -- and there are contexts where those shortcuts are necessary, cogent, and valid.</p></pre>JokerSp3: <pre><p>Would the go ast package be simpler with generics?</p></pre>saturn_vk: <pre><p>It would be simpler with sum types.</p></pre>itsmontoya: <pre><p>Some of the data structure libraries I wrote would be so much cleaner with proper generics. Right now I have to use codegen to achieve proper type safety (with optimal speed)</p></pre>adamcolton: <pre><p>The point of the thread it&#39;s to provide examples of code code other people wrote. This comment does neither. It belongs in one of the many other threads on the subject.</p></pre>itsmontoya: <pre><p>I don&#39;t need to provide examples if I say data structure. Hash map, linked list, btree, you name it. If you want to write ANY data structure without codegen, you&#39;re fucked. </p></pre>tscs37: <pre><p>Trees.</p> <p>Lots of trees.</p> <p>I&#39;ve recently designed a tree structure for usage as a key-value database.</p> <p>The problem is that I had to resort to using byte-slices as backing store, there is no way I can reliably and with type-safety make a Tree structure, with disk-paging, without having access to generics.</p> <p>Sadly, the code isn&#39;t public (yet), but I think having special datastructures being type-safe would be a huge improvement. Simple maps or slices don&#39;t cover nearly all use-cases with good performance.</p></pre>shark1337: <pre><p>As the time passes, Go will become more and more mature. With Go 2, people might have to learn/unlearn some things and possibly new stuffs will be added.</p> <p>Most people like Go because it&#39;s very easy to learn and simple to work with (I like it because it compiles directly to machine code, has a very good standard lib and so on), but as a developer with a c++ background, I would really like Go2 to deliver a lot more low level stuffs that would offer better performance and scalability.</p> <p>At the end of the day, if the creators of the Go decide to implement new features (I&#39;m not saying generics) that will offer better performance and maybe will make the language harder to learn I&#39;ll take the deal.</p> <p>Maybe you don&#39;t realize it yet, but when working in production, you&#39;ll see that Go still needs improvements..</p></pre>mixedCase_: <pre><blockquote> <p>I would really like Go2 to deliver a lot more low level stuffs that would offer better performance and scalability.</p> </blockquote> <p>I&#39;m not sure Go is the language you should be looking at for that. Go is an attempt to get as close as possible to systems-level performance without going down the complexity hill that languages like C++ and Rust have to deal with to get there, yet without the verbosity of Java or the ceremony of Haskell (or rather, the ceremony of GHC to get those sweet optimizations).</p> <p>If you want more power without the traditional pitfalls of systems languages, Rust is your best bet. Otherwise, do make sure that Go <em>really</em> makes it too hard to hit the performance requirements your project has.</p></pre>shark1337: <pre><p>Happy birthday! Tbh, I feel c++ easier than rust, but that&#39;s just me ;)</p></pre>Sythe2o0: <pre><p>I don&#39;t see why it would be complicated for Go to support something like CUDA or SIMD operations for users that want to dig into a library to gain that performance. Can you give an example of the added complexity you expect?</p></pre>kaeshiwaza: <pre><p>If it&#39;s harder to learn it&#39;s not more Go...</p></pre>binaryblade: <pre><p>The obvious first case is reusable data structures.</p></pre>ar1819: <pre><p>I&#39;m still waiting for someone to at least remember that any non-stack interface value is creating additional pressure on GC and ruins cache locality. This is why writing lock free or fast data structures is currently relying on code generation. However - the code generation doesn&#39;t allow you to implement the proper set of constraints on the type before allowing you to place it inside function or struct (or more precisely it would require some non standard thinking - see taylorchu GitHub for solution). </p> <p>And now try to create cache of caches... </p></pre>fuckthememeculture: <pre><p>In a language where so much of the feature set relies on pointers cache locality doesn&#39;t seem like much of a concern. consider how slices are implemented, or how maps are really just pointers. It just doesnt seem like cache locality is much a concern.</p></pre>Elelegido: <pre><p>I think generics is ALSO a way to fix tedious error handling. Instead of:</p> <pre><code>func process() int, error { myNum, err := numFromDb() if error != nil { return nil, err } index, err:= storeInDb(myNum+ 1) if error != nil { return nil, err } return index, nil; } </code></pre> <p>you could have:</p> <pre><code>func process() result&lt;int, error&gt; { return numFromDb().map(func (int myNum) { return storeInDb(myNum + 1); }); } </code></pre> <p>that with minimal sugar a la rust/async-await syntax, would become very easy to read, something like:</p> <pre><code>func process() result&lt;int, error&gt; { myNum := try numFromDb(); return try storeInDb(myNum + 1); } </code></pre> <p>And of course, some type-safe FP classic constructs, like that one I just used (map), are also a nice add.</p></pre>bonekeeper: <pre><p>Great example of what not to do, IMO. If you want to code in Rust, you can always try Rust! What you did kills readability IMO (in the sense that now there&#39;s some magic I need to understand before knowing how errors are handled - maybe it would be clearer for me if I knew Rust?)</p></pre>lonahex: <pre><p>Agree. I don&#39;t see the point of adding this mental overhead when reading code just to avoid typing a few lines. Go gets this right at the moment. I hope it doesn&#39;t change.</p></pre>Elelegido: <pre><p>I think that&#39;s actually a fairly easy abstraction. But of course I might be wrong. It&#39;s shown in Rust but also in many other languages, like C# or JavaScript. I don&#39;t think there should be any problem about taking inspiration from other languages, you should get used to hear references like that in these kind of threads. Telling somebody to go try X sounds a bit rude IMO</p></pre>bonekeeper: <pre><p>I apologize for it, I didn&#39;t mean to sound rude at all - what I meant by it is that different languages have different idioms that stem from the nature of the language (like using macros for error handling, etc) - like I&#39;ve seen people needlessly use callbacks for things due to them coming from JS/Node. Go&#39;s error handling (tedious as it is, and I agree) is how it is because the language values explicit behavior over implicit behavior (i.e., less expressiveness at the cost of higher readability). No matter how comfortable you are with the &#34;try&#34; idiom from Rust, an explicit &#34;if err != nil {}&#34; will always be clearer, specially to a newcomer to the language. Imagine now if every framework decides to have their &#34;try-like&#34; idioms for error handling! It doesn&#39;t happen with Rust because that was already formalized from its inception, which is not the case with Go.</p></pre>Elelegido: <pre><p>Thank you for the apology, that&#39;s very appreciated for having a healthy discussion. I disagree about which idiom is clearer. One problem about go error handling is that a little modification in the &#34;if&#34; idiom is IMO easy to overlook, and that could be a source of frustration, also because tedious typing is a common source of typos. But I get your point, current solution is indeed very explicit. Don&#39;t buy the equivalence between explicitness and clarity, though. Could you imagine how much bigger would your code look with a more explicit version of goroutines where u would have to manually pin down all the low level mechanisms involved in the magic of concurrency? I agree, though, that having result types would not be really useful for all the existing API that use multiple return values. I still hope that something can be done in order to ease error handling somehow.</p></pre>Someguy2020: <pre><blockquote> <p>This means that the style should be consistent with the original author, not how you would have written it.</p> </blockquote> <p>That would be different if they had generics. </p></pre>fuckthememeculture: <pre><p>The introduction of an abstract type would solve almost all use cases. Lets call the abstract building block &#39;model&#39;. This model type would define traits. Go already has the notion of traits. The idea of a model based on traits could be modeled around the already existing and feature-rich reflection library.</p> <p>Interfaces, conversion and assertion provide runtime polymorphism. Now, what is needed is a compile time equivalent. A zero cost runtime mechanism, with full costs realized at compile time. The two concepts should complement each other without overlap. </p> <p>people have suggested something along the lines of:</p> <pre><code>type Set&lt;T&gt; struct { Add(element T) Remove(element T) Contains(elements T) bool } func main() { mySet := new(Set&lt;int&gt;) mySet.Add(1) // works mySet.Add(&#34;1&#34;) // compile error var foo interface{} foo = 1 mySet.Add(foo) // also compile error mySet.Add(foo.(int)) // compiles and works fine, but potentially a panic risk at runtime } </code></pre> <p>But nobody has given much thought how one might define &#39;T&#39;.</p> <p>This set type is a very simple use case where the algorithm within the func/methods would be easy to define in the language with comparable, but what about very complex container types with must define there own definition of comparable or orderable, etc. The fmt package gives a good starting point about how to define func/methods to help define behavior.</p></pre>tdewolff: <pre><p>I think the (non-empty) interface idiom is very important in Go. Define the behaviour of your type! As such it would be nice to make it work with arrays more easily. Something along the following lines, the main idea is that the compiler can handle arrays of interfaces more naturally.</p> <pre><code>package main import &#34;fmt&#34; type Animal interface{} type Barker interface { Bark() } type Dog struct{} func (a *Dog) Bark() { fmt.Println(&#34;BARK!&#34;) } type Cat struct{} func (a *Cat) Miauw() { fmt.Println(&#34;Miauw?&#34;) } func main() { x := []Animal{&amp;Dog{}, &amp;Dog{}} bark(x) // works, implicit cast because all elements implement Barker y := []Animal{&amp;Dog{}, &amp;Cat{}} bark(y) // compile error, not all elements implement Barker } func bark(barkers []Barker) { // do something } </code></pre> <p>This doesn&#39;t cover all generics use cases though, but I feel like Go shouldn&#39;t implement generics a la Java.</p></pre>amapatzer: <pre><p>Guys, problem first solution later. </p> <p>This thread is unfortunately not about the problems we want to solve, instead the focus is on one solution which is great in some languages but a little bit awkward in go imho.</p></pre>callcifer: <pre><p>The top comment shows a very real problem, and a few comments down we have a very real potential answer. I think this thread is pretty much on point, especially since most of the objections are mere variations of &#34;bit awkard imho&#34;.</p> <p>Personally, I&#39;d much rather see <em>technical</em> objections, like effects on the compiler, the runtime etc.</p></pre>HoldenDapenor: <pre><p>Are you guys serious? The only &#34;specific use case&#34; needed is &#34;not having eight million list implementations that are completely identical besides their type, all of which were generated via some hacky script, which is fine because code generation is totally a perfectly normal and acceptable thing to have to do when coding in a supposedly modern language.&#34;</p></pre>dc0d: <pre><p>One thing I like to see in Go 2.0 - and like it more than generics - is the ability to assign an instance of a type to an instance of a second type, in which, the set of the fields of the second type, is a subset of the set of the fields of the first type.</p> <p>It is preferred to not overload the equal sign for this. Maybe a &lt;== b?</p></pre>floatdouble: <pre><p>You can already kind of do this, no?</p> <pre><code>import ( &#34;fmt&#34; ) type foo struct { x int y string } type bar struct { foo z float64 } func main() { var a = foo{x: 1, y: &#34;a&#34;} var b = bar{z: 2.5} b.x = 2 b.y = &#34;b&#34; fmt.Println(b.x, b.y, b.z) b.foo = a fmt.Println(b.x, b.y, b.z) } </code></pre></pre>dc0d: <pre><p>Set of fields of <em>foo</em> is a subset of set of fields of <em>bar</em>, but we can not assign an instance of <em>bar</em> to an instance of <em>foo</em> - like as suggested by @comrade_donkey using some statement like <em>copy(_bar, _foo)</em> for avoiding overloading <em>=</em> sign - but we have to explicitly say <em>_foo = _bar.foo</em>.</p></pre>Sythe2o0: <pre><p>This is what interfaces are for. You have functions accept interfaces instead of strict struct types, so you can pass in <code>bar</code> to anything you want to accept <code>foo</code> because <code>bar</code> implements all the interfaces that <code>foo</code> does.</p></pre>dc0d: <pre><p>Yes, interfaces can be used for matching <em>behavior</em>, as you mentioned, for what a struct <em>does</em> not <em>has</em>.</p> <p>Here we are talking about the <em>fields</em> that hold data, not <em>behavior</em>. </p></pre>Sythe2o0: <pre><p>So you want an interface equivalent for the fields a struct has? I&#39;m not sure what you&#39;re getting at-- you can get the component parts of a struct when you need them, but you want to avoid writing<code>Get_()</code> boilerplate so you want to implement new syntax so you can cast types down to types they are composed of? All to replace the <code>.foo</code> portion of syntax that already exists?</p> <p>So if I do this:</p> <pre><code>var b bar var f foo f &lt;== b </code></pre> <p>What information is stored in <code>f</code>? Does it know that it is not actually a <code>foo</code> but it is a <code>bar</code>? Can it be cast back to <code>bar</code>? </p> <p>As far as I can tell, you&#39;re really proposing making Go&#39;s type system like ML&#39;s type system which is painfully complicated to learn and has multiple ways of achieving the same functionality all over the place. Otherwise, you&#39;re trying to turn the <code>struct</code> keyword into <code>class</code>.</p> <p>Look, I admit I&#39;ve wanted this feature for a long time too! It looks like it&#39;d be really nice to have and would make some things a lot neater, but everything it can do can already be done through interfaces, and if you&#39;re composing your types anyway you already have syntax that lets you do this without the additional questions rising about what type this new construct has to be. </p> <p>What about this:</p> <pre><code>type Bar struct { Foo Biz } type Biz struct { Foo } type Foo struct { field int } var b Bar var f Foo f &lt;== b </code></pre> <p>From what type does f come? <code>Bar.Foo</code> or <code>Bar.Biz.Foo</code>? Interfaces solve this by requiring that you specify subtypes in ambiguous cases. Does this operator also require specifying subtypes? Then, because we want to be able to cast back to <code>Bar</code>, we need syntax for that subtype specification that isn&#39;t a cast or a field access.</p> <p>If you can&#39;t cast back to <code>Bar</code>, then its just renaming <code>f = b.Foo</code> to <code>f &lt;== b</code>, except this syntax actually is less useful! with the <code>.Foo</code> syntax we can say <code>f := b.Foo</code> so we don&#39;t need to care about / import types, but in this system you need to know the type of what you&#39;re pulling, because <code>b</code> could be composed of multiple things and there&#39;s no way to know what sub-struct you want to pull in a <code>:=</code> equivalent. </p></pre>dc0d: <pre><p>ML type system is far more complicated and has a different purpose. I&#39;m not looking for that. <code>f &lt;== b</code> simply means <code>b</code> has all fields of <code>f</code>, nothing more.</p> <p>And that can be checked at compile time, by the Go compiler; nothing runtime here.</p></pre>Sythe2o0: <pre><p><del>So this is something that replaces composition?</del> What about the diamond problem? I don&#39;t think I said anything about runtime versus compile time. If you mean casting, then how is this different than b.Foo?</p> <p>Alright, I understand you weren&#39;t really thinking about composition. But the problem with this proposal is the diamond problem: what happens when Bar has access to multiple fields named the same thing with the same type? If this doesn&#39;t work on composed structs, then I&#39;d recommend going and looking up composition in Go, it is extremely powerful and if this doesn&#39;t work for them this feature is worth very little.</p> <p>This is also unintuitive:</p> <pre><code>type Position struct { x, y int } type AlphabetRepresentation struct { a,b,c,d...,x,y,z int } var ar AlphabetRepresentation var p Position p &lt;== ar // I shouldn&#39;t be able to do this, but I can </code></pre> <p>To demonstrate the diamond issue:</p> <pre><code>type ScreenAlphabet struct { Position AlphabetRepresentation } var sa ScreenAlphabet p &lt;== sa // What happens here? </code></pre> <p>Using <code>sa.Position</code> or whatever isn&#39;t sufficient either. Consider:</p> <pre><code>type HasX struct { x int } type HasY struct { y int } type Amalgam struct { HasX x int y int } var am Amalgam p &lt;== Amalgam // What happens here? </code></pre> <p>This can actually get even more confusing, if we follow current Go rules for ambiguous selectors</p> <pre><code>type SecondPosition struct { Position x, y int } sp := SecondPosition{Position{5,5},0,0} p &lt;== sp // This will have {0,0}, not {5,5}! </code></pre></pre>dc0d: <pre><p>It&#39;s not always possible to have <code>bar.Foo</code> like when it&#39;s defined in another package; not containing our <code>foo</code>, explicitly, as an embedded part. And it happens plumbing big structs become cumbersome at some point. There are many packages with big structs out there.</p> <p>About the Diamond problem; you are right. I did not thought about that.</p></pre>floatdouble: <pre><p>Okay... This doesn&#39;t seem like that big of a deal to me compared to generics.</p></pre>comrade_donkey: <pre><p>Or extend the copy function to do this.</p></pre>dc0d: <pre><p>You&#39;re right! That would be the most rational.</p></pre>dc0d: <pre><p>And if they are going to generics, I prefer package wide type variables. For example one could have a generic package this way:</p> <pre><code>package gpkg type T type U </code></pre> <p>And in the package we use it, we could fix down T and U:</p> <pre><code>import &#34;gpkg&#34; type gpkg.T int type gpkg.U SomeStruct </code></pre></pre>Sythe2o0: <pre><p>What if you wanted to use the same package twice, with different types, in the same function?</p></pre>dc0d: <pre><p>I didn&#39;t really think this through. It just seems (IMHO) this way compiler would be able to provide smarter assistant. Maybe those type definitions could move right before where they are used? Inside a <em>with</em> statement?</p> <p>I just have a feeling that with how interfaces work in Go, adding classic (C#, Java, ...) generics might not be something that straightforward.</p></pre>tmornini: <pre><blockquote> <p>find example in public repos of code other people wrote that you think could be improved by generics</p> </blockquote> <p>Great idea. Perhaps better if limited to the standard library.</p></pre>gott_modus: <pre><p>why not just write in c then, man.</p></pre>

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

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