A question about generics

xuanbao · · 482 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>(I posted this in an ironic sub, but I think the question is worth being asked) </p> <p>I&#39;m sure this debate has been had a trillion times by now, but how are generics necessary for reusability when we have interfaces. A reusable sort for instance, would just require the members of the input list to implement an IComparable or something of the sort. Is it really THAT hard to go without generics? People praise the simplicity and elegance of the tooling, but I can&#39;t imagine how elegant and simple things will be when everything falls into the ServiceResult&lt;T,V,X,Y,Z&gt; pattern and every type has three subtypes. Is there something I&#39;m missing? I program C# for a career and use go for my personal project, and I just don&#39;t understand the desire for generics when we can use interfaces. </p> <hr/>**评论:**<br/><br/>egonelbre: <pre><p>See <a href="https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX4pi-HnNjkMEgyAHX4N4/edit" rel="nofollow">Summary of Go Generics Discussions</a>.</p> <p>tl;dr; performance and type-safety are hard to accomplish (in some cases) without code generation or duplication.</p></pre>singron: <pre><p>Interfaces can&#39;t guarantee that you are using the same type in two places. Take this example binary tree code:</p> <pre><code>type Node struct { value interface{} left *Node right *Node } type Tree { compare func(interface{}, interface{}) bool root *Node } func (t *Tree) Insert(i interface{}) { // use t.compare to insert i } func main() { compare := func(l interface{}, r interface{}) bool { return l.(string) &lt; r.(string) } strTree := Tree{compare: compare} strTree.Insert(&#34;string1&#34;) strTree.Insert(1) // compiles, but panics in comparison function } </code></pre> <p>int and string both implement the interface, but the comparison function only makes sense if it compares apples with apples (i.e. the concrete type behind the interface needs to be the same). Generics or parametric types allow you to prove this in the type system. E.g.</p> <pre><code>type Tree&lt;T&gt; { compare func(T, T) bool ... } func (t *Tree&lt;T&gt;) Insert(i T) { ... } func main() { compare := func(l int, r int) bool { return l &lt; r } intTree := Tree{compare: compare} intTree.Insert(1) intTree.Insert(&#34;bad&#34;) // compile error // intTree has type Tree&lt;int&gt;, so intTree.Insert takes an int, not string } </code></pre> <p>There are a small number of cases where you can work around this issue with minimal boilerplate (e.g. the sort package), but sometimes it really sucks.</p></pre>DeedleFake: <pre><p>The biggest issue is that interfaces don&#39;t give enough compile-time control to the end-user, making it harder for the user to use a library in a way that the author didn&#39;t forsee. Interfaces help, definitely, and the way they work is a massive improvement over, for example, Java&#39;s interfaces, but there are areas where they just simply don&#39;t do enough.</p> <p>In general, the places where <code>interface{}</code> can be used could probably be argued to be &#39;good enough&#39;, by and large, although there&#39;s definitely quite a bit of grey area. But a lot of places where you have to use <code>reflect</code> is where things start becoming quite a bit more problematic. Try writing a reusable <code>sum</code> function that works on every number type. You basically have three options: Require the end-user to convert numbers to something that&#39;ll probably work for most cases, like <code>float64</code>, introducing possible loss of precision, use a massive type switch, which is not only tedious and annoying but, again, limits the types that can be used and also removes compile-time type safety, or use <code>reflect</code>, again removing compile-time type safety, making the code much harder to read, and also introducing a pretty big performance loss.</p> <p>Those problems are not in any way unique to <code>sum</code>. The entire standard library suffers from it pretty heavily. The example of <code>sum</code> could be applied to darn near every single function in the <code>math</code> package just as a start, but there are also packages that can&#39;t even be written because of it. Wouldn&#39;t it be nice to have a <code>chans</code> package as an analogue to <code>strings</code> and <code>bytes</code> with utility types and functions for channels?</p> <p>And you&#39;re right, badly done generics could cause the code to read like C++, Java, or Rust code, which would certainly be a disaster, but I think that it&#39;s worth the risk for at least some type of generics. The Go team has come up with nice clean solutions to existing problems before; maybe they&#39;ll be able to solve the problem of unreadable generics this time.</p></pre>SteveMcQwark: <pre><p>Interfaces are great when you only need to perform operations on individual values polymorphically. The problem is when you need to perform an operation involving multiple values, where you often need the types of the values to be related in some way.</p> <p>For your Comparable example, you actually can&#39;t do that without generics, since you need to operate on all lists whose elements implement Comparable, not just lists of Comparable. Even the idea of having operations on lists of different kinds of things requires some form of generics. If you&#39;re relying on the hypothetical ability to say &#34;List&lt;T&gt; where T implements Comparable is assignable to List&lt;Comparable&gt;&#34; so you can write functions in terms of List&lt;Comparable&gt; instead of making them generic over all such List&lt;T&gt;, that requires the generic type List with the type parameter T (and is unsound if the list is mutable and the original List&lt;T&gt; can be used after the assignment). This doesn&#39;t work in Go even for it&#39;s built in generic types because different types of slices (for example) have different layouts in memory.</p> <p>The way Go solves this is with sort.Interface. You have to create a named slice type and add some methods to it so that the sort.Sort function can operate on it using only indices, preventing it from having to interact with the elements directly, which would require some form of generics. This technique isn&#39;t generally applicable, though. You can come up with ways to create a single value that provides methods that allow calling code to manipulate multiple values indirectly whose types need to be related, but this gets convoluted fast and creates stilted, error prone code.</p></pre>

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

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