Why do so many people complain about generics, yet nobody complains about a lack of inheritance ?

agolangf · · 718 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>You can&#39;t swing a dead cat in hacker news, or reddit for the most part without hitting someone with a complaint about a lack of templated types in Go.</p> <p>Yet by comparison, almost nobody complains about Go&#39;s decision to not support subtyping and inheritance. Why is this so ? </p> <p>Why are so many happy with the statement &#34;Go doesn&#39;t support subtyping, do it another way&#34;, yet so few can accept the identical statement &#34;Go doesn&#39;t support generic programming, do it another way&#34; ?</p> <hr/>**评论:**<br/><br/>djherbis: <pre><p>One possible explanation might be that you can get what you want out of sub classing and inheritance from Embedding in Go, which is pretty straightforward. Whereas solving generics problems effectively in Go requires thinking about them in a distinctly different way (like the sort package).</p></pre>ericanderton: <pre><p>I think this is it. </p> <p>Also, Interfaces are satisfied implicitly, rather than explicitly. This covers much of the territory that is classically covered by inheritance. Instead of saying that a Dog is an Animal (inheritance), you simply ask the compiler: Does this Animal <em>look like</em> a Dog?</p></pre>danredux: <pre><p>Duck typing.</p></pre>quiI: <pre><p>Inheritance is often seen as a bit of a broken feature actually. (see: Prefer composition over inheritance <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">http://en.wikipedia.org/wiki/Composition_over_inheritance</a>). Please note &#34;prefer&#34;, sometimes it <em>does</em> have it&#39;s place. </p> <p>Compared to generics which are very very useful. </p></pre>jerf: <pre><p>I think this is the key. Even before Go 1.0 came out, inheritance was on its way out. Even by Go 1.0, syntactically privileging composition instead of inheritance was a solid, tested idea than a crazy one.</p> <p>By contrast, people feel that generics are a solid, tested idea that Go for some reason does not include. (I split the difference on this myself... I agree that the idea isn&#39;t as solid as many people think it is and the vast bulk of implementations have serious issues that people are just so used to working around that they don&#39;t see anymore, but, on the flip side, I do sometimes really miss efficient custom containers.)</p> <p>Go will probably help inheritance on its way out the door, but it didn&#39;t create the direction inheritance was already headed.</p></pre>Feribg: <pre><p>Embedded types and the way interfaces work is a good enough solution for me in most cases. But the lack of generics is gruelling, just introduces way too much boilerplate and code repetition and goes against the purpose of Go being an expressive language. To be honest that&#39;s probably the biggest minus for me personally and it got to the point where i started moving away from the language. Hopefully in future versions the team comes up with a solution, but using code generators as part of your build process and telling me that that&#39;s perfectly fine is not OKyy. After all the purposes of choosing Go (or any other language) would be that you want nice, clean, maintainable and expressive code. But if you can&#39;t express something as common as &#34;I want a list of type&#34; in a type safe manner, you are bound to end up with something that&#39;s unpleasant to read and work with, hence the complaints.</p> <p>So to answer your question: The lack of sub typing is addressable by the tools Go provides, the lack of generics is not.</p> <p>The other thing i was really unhappy with despite the recent decision that vendoring is the way to go. I just think this is moving is backwards and not forwards. There are so many good dependency management utilities that could&#39;ve been used as an example. While i get the technical difficulty of implementing generics, i can&#39;t find a good enough explanation of why vendoring is the recommended approach to dep management.</p></pre>natefinch: <pre><p>I&#39;m curious to know what you need generics for so often that it&#39;s turning you away from the language. Certainly there are some domains that use a lot of algorithm-style programming that could be annoying without generics... but I find that most of the domains that Go targets (command line applications, server applications, and web servers) aren&#39;t actually that heavy on the algorithms.</p></pre>Feribg: <pre><p>Well not having generics makes any type of collections library non trivial to implement and that in it of itself is a huge issue. By non trivial I mean everything is an interface and then you cast and pray. This doesn&#39;t only hold to collections, but to anything that you want to abstract in a major way, but since collections are so widely used Im just throwing them as an example.</p> <p>And you are right maybe its not that big of a pain if you are willing to sacrifice type safety and you expect people to write decent code (and we all know how that ends up). But this notion of code repetition just comes up over and over again in the language and I couldn&#39;t swallow it. It seems that the community&#39;s response for the limitations of the type system is just use interface{}, aka bypass it altogether. I guess its a trade off, so if you want a simple language you will be bound to write boilerplate stuff. So personally I prefer something that allows me a bit more expressiveness and abstraction.</p></pre>natefinch: <pre><p>The community&#39;s response is not to use interface{}, generally. It&#39;s to simply not use generic containers other than a map or slice, or build on top of those. The Juju codebase (which I work on) (<a href="https://github.com/juju/juju">https://github.com/juju/juju</a>) is over 300k lines of code, and we have like 1100 uses of interface{}, mostly for printing or marhsalling/unmarshalling. Certainly not as a stand-in for generics in all but a very small number of cases. </p> <p>The most code repetition I&#39;ve experienced is stupid for loops, like what would be .Contains() method in other languages.... but that code is so trivial, I don&#39;t even consider it to be a problem.</p> <p>So my question is, what kind of containers were you creating that you really wanted to be generic? In my experience, most of the time, people write a generic container and then end up only using it with one type (or a few tightly related types, but they could make it one type if they tried).</p></pre>skinney: <pre><blockquote> <p>The most code repetition I&#39;ve experienced is stupid for loops, like what would be .Contains() method in other languages.... but that code is so trivial, I don&#39;t even consider it to be a problem.</p> </blockquote> <p>It starts trivial, but can grow ugly quick, especially if you want the for-loop to be performed concurrently. Something like pmap in Go would be nice, but cannot currently be done in a type safe way.</p></pre>Feribg: <pre><blockquote> <p>So my question is, what kind of containers were you creating that you really wanted to be generic? In my experience, most of the time, people write a generic container and then end up only using it with one type (or a few tightly related types, but they could make it one type if they tried).</p> </blockquote> <p>Please have a look below at my answer to Dave Cheney, I also address this point that you made. </p></pre>davecheney: <pre><p>Can you give concrete examples of collection types you need that could not be satisfied by maps and slices?</p></pre>skinney: <pre><p>OrderedMap, OrderedSlice, Set, OrderedSet, Trees, Tries, and immutable versions of these.</p></pre>: <pre><p>[deleted]</p></pre>theonlycosmonaut: <pre><p>Could one only provide an immutable API for a type that&#39;s internally mutable? I don&#39;t know Go well enough to know if that&#39;d be enforcible.</p></pre>skinney: <pre><p>You create a new data structure that only has an immutable api. While you could access the struct fields directly, that would be a pain in the case of a HAMT.</p></pre>skelterjohn: <pre><p>OrderedSlice? Slices are naturally ordered.</p> <p>OrderedSet makes no sense.</p> <p>Ordered maps, sets that aren&#39;t map[T]bool, trees and tries would certainly be nice.</p> <p>The easiest way to support immutable datastructures is to not mute your datastructures.</p></pre>danredux: <pre><p>Sets should be map[T]struct{}, by the way.</p></pre>skelterjohn: <pre><p>I like map[T]bool because then I can check &#34;if m[v] { ... }&#34; and it will be false if the item was not added.</p></pre>skinney: <pre><blockquote> <p>OrderedSlice? Slices are naturally ordered.</p> </blockquote> <p>I thought that if I appended something to a slice, it was appended at the end and not in order?</p> <blockquote> <p>OrderedSet makes no sense.</p> </blockquote> <p>OrderedSet makes perfect sense if you want to iterate the contents in, say, alphabetic order. I do this from time to time in Clojure(Script).</p> <blockquote> <p>The easiest way to support immutable datastructures is to not mute your datastructures.</p> </blockquote> <p>And we know how that works in a real project. The best way I think is to have a separate type for it. Preferable something that is efficient to create a new version of, like a persistent HAMT.</p></pre>skelterjohn: <pre><blockquote> <blockquote> <p>OrderedSlice? Slices are naturally ordered. I thought that if I appended something to a slice, it was appended at the end and not in order?</p> </blockquote> </blockquote> <p>Then you mean SortedSlice.</p> <blockquote> <blockquote> <p>OrderedSet makes no sense. OrderedSet makes perfect sense if you want to iterate the contents in, say, alphabetic order. I do this from time to time in Clojure(Script).</p> </blockquote> </blockquote> <p>OrderedSet makes no sense because a set is by definition an unordered construct. Otherwise you mean some sort of sorted unique-valued list.</p></pre>Feribg: <pre><blockquote> <p>Can you give concrete examples of collection types you need that could not be satisfied by maps and slices?</p> <p>So my question is, what kind of containers were you creating that you really wanted to be generic? In my experience, most of the time, people write a generic container and then end up only using it with one type (or a few tightly related types, but they could make it one type if they tried).</p> </blockquote> <p>Skinny already mentioned a few, but the list can go on and on really. You might want immutable ones, synchronized ones, different types of buffers and queues etc. And I get the argument that I can implement my OrderedSet for my custom type that I need and it would work. The problem is that everyone in their repository will have a slightly different implementation of OrderedSet that works with their slightly different types and thats where the mess begins. Instead of having a unified Go.collections where you get expected behaviour across projects, you end up with custom wrappers and custom collections implementations all over the place and Im sure you agree this cannot be a good thing in the long run if you want to have maintainable code. </p> <p>A typical example of what I mean is: <a href="https://golang.org/src/container/list/list.go" rel="nofollow">https://golang.org/src/container/list/list.go</a> So list is a list of Element, and Element is essentially a wrapper of interface{}. So let&#39;s say someone else creates MyListImpl, now doesn&#39;t take Element, but it takes MyElement. That&#39;s my main problem with portability because every library author will have their own Collections impl. What is inside of Element. For example a List&lt;Element&gt;, right now can mean a List of 1 Element with Value of type A, 1 Element with Value of type B and C. That&#39;s not great. Those are all questions that bother me and the compiler should know the answers.</p></pre>Nexus-uk: <pre><p>I&#39;ve needed a LLRB collection (<a href="https://github.com/petar/GoLLRB" rel="nofollow">https://github.com/petar/GoLLRB</a>) for <a href="https://github.com/Redundancy/go-sync" rel="nofollow">https://github.com/Redundancy/go-sync</a> </p> <p>I&#39;ve also implemented my own collections for time-bucketed events, and circular buffers with particular requirements, which could be useful to re-use without casting.</p> <p>In at least one of those cases, I could provide one specialization for byte slices, and an interface{} one, but it&#39;s worse on any API that could really do with me returning a slice of objects (since that then involves casting every single element).</p> <p>Most of the time, it doesn&#39;t bother me - slices and maps are great primitives for a lot of things, but I can also see an argument that if you need something more, you don&#39;t want to copy, paste, fix-up types on a complicated implementation or take a hit casting (under a certain assumption, you&#39;re looking to use a specific algorithm and data type because it has the right characteristics and performance, and you probably don&#39;t want an overhead on that).</p> <p>I&#39;d frequently be happy enough if I could wrap an implementation with casts that could be optimized away at compile time.</p></pre>Morningrise89: <pre><p>Absolutely agree,i have worked on generated code before and it is grueling and a bad decision every time I have seen it.</p> <p>Inheritance is a powerful feature, but too often it ends up being misused and you end up in a shoehorned mess. I prefer to only have embedding here... But I am not completely settled on that.</p></pre>natefinch: <pre><p>Really? every time I run into generated code, it&#39;s a huge blessing. It&#39;s guaranteed to conform to a standard. It&#39;s real actual code that is easy to reason about. If you trust the template, you don&#39;t even need to review it.</p> <p>For example, the syscall code in Go.... all the API calls are generated. It&#39;s a bunch of unsafe pointer conversion crap, and you can just totally ignore it and assume it&#39;s correct, because the template has been proven correct by people before you (and if there does end up being a bug, you fix it in the template and just regenerate).</p></pre>Rhodysurf: <pre><p>Because Go has inheritance with anonymous types </p> <p>Edit: i meant fields</p></pre>: <pre><p>[deleted]</p></pre>Rhodysurf: <pre><p>Thanks for the correction.. Yeah technically its delegation but it acts like inheritance, or its the closest thing to it ive found anyways</p></pre>davecheney: <pre><p>It does not <em>act</em> like inheritance, it <em>is</em> composition. </p></pre>skelterjohn: <pre><p>Negative, composition in go does not give you polymorphism.</p></pre>: <pre><p>[deleted]</p></pre>calebdoxsey: <pre><p>It&#39;s possible, but not likely to happen any time soon. I would suggest using a different programming language. </p> <p>Perhaps C# could do what you want. It has an asynchronous programming model using tasks (<a href="https://msdn.microsoft.com/en-us/library/dd537609(v=vs.110).aspx" rel="nofollow">https://msdn.microsoft.com/en-us/library/dd537609(v=vs.110).aspx</a>).</p> <p><code>Task.Run</code> is basically equivalent to the <code>go</code> statement. <code>Task.WaitAny</code> is like <code>select</code> and you can implement channels using a <code>BlockingCollection</code> and the <code>Add</code> and <code>Take</code> methods.</p> <p>C# has been ignored by many programmers because Microsoft made it, but it has a ton of features.</p></pre>mparker762: <pre><p>It&#39;s async system is very slow. Go&#39;s goroutine implementation is multiple orders of magnitudes faster. </p> <p>C# async code produces huge amounts of garbage due to a need to work around limitations of the CLR. It is using a surprisingly heavyweight thread pool system that was designed for parallelism and long-running tasks but was reused as-is for their urgent concurrency needs that they inflicted on themselves with WinRT, instead of building a suitably light-weight system that was appropriate for the task.</p> <p>Not to mention that the async and await keywords are unnecessarily leaking the fact that the C# runtime is using a CPS/callback implementation, further constraining future concurrency advances.</p></pre>skelterjohn: <pre><p>Because when swapping composition for inheritance you feel clever (and your code is better, too).</p> <p>When you swap boilerplate repetition for generics, you feel dumb, since writing the extra code is more-or-less mindless.</p></pre>yen223: <pre><p>Because we can live without inheritance, but generics are useful for reasons that have been repeated ad nauseam.</p></pre>drvd: <pre><p>Because we all think of our future self as &#34;the one guy who wrote this awesome do-it-all-with-anything framework which revolutionized web development&#34;?</p> <p>Today nobody takes pride in a nice piece of code which does one thing properly and solves a specific, maybe even tiny, business need. You gain fame (or think you gain fame) only if your solution is useful in every imaginable way and solves a broad spectrum of problems in all domains equally well: A generic framework useful to process anything from uint8 to large cardinal numbers, from short 7bit ASCII strings to terabytes of UTF-18 encoded Klingon language with a custom collator, from clay tablets to Twitter feeds, from a simple struct to some business-entity discover-able through micro services.</p> <p>And programming (in the sense of &#34;write code&#34;) is regarded as bad practice as it might not be DRY enough.</p></pre>egonelbre: <pre><p>IMO, it has to do with that inheritance is much much less useful than generics.</p> <p>I&#39;ve been searching for a few years and I still haven&#39;t found something that would benefit from inheritance (when you have interfaces and embedding). Most of the time I needed to use inheritance was because I wanted to easily embed something or wanted to use interfaces, but the language support for them is terrible (I&#39;m looking at you Delphi).</p></pre>comrade_donkey: <pre><p>What lead you to think that generic programming is inherently bound to subtyping? Don&#39;t take C++ or Java&#39;s implementations as references; they&#39;re off the table for a reason.</p></pre>davecheney: <pre><p>Nothing, they are independent, but I wonder why people are so happy to accept the position that Go does not support subtype inheritance, but are unwilling to accept the same position for templated types. </p></pre>comrade_donkey: <pre><p>Because implicitly implemented interfaces make up for the subtype polymorphism that doesn&#39;t come with embedding as it would with inheritance. The same could be true for a type-invariant generics implementation.</p></pre>lhxtx: <pre><p>Go has composition, which works better than inheritance IMNSHO. </p> <p>Animal Has a Dog instead of Dog is an animal is easier. </p></pre>calebdoxsey: <pre><p>I think most of those folks would see subtyping as a misfeature.</p> <p>Inheritance in classical OOP requires a virtual table indirection for method lookup. This is less efficient than direct function calls.</p> <p>As for generics, without them you either repeat yourself (<code>StringTree</code>, <code>IntTree</code>, ...) or lose efficiency and safety (boxed with runtime checks).</p> <p>The current ideal is both to move as much checking as possible to compile time, hence the push for sophisticated Haskellish type systems, to produce code that is as efficient as possible and therefore to give programmers as much control as possible over the execution of their program.</p> <p>But because of the language&#39;s design it will never meet that ideal.</p> <p>That&#39;s cool and all, the divide is ideological and probably fundamental, but the vitriol is kind of bizarre. (I mean if you don&#39;t like Go, and it&#39;s not going to change, why not use a different language?)</p> <p>Perhaps people are frustrated by the (apparent) waste of resources. With Google backing it, and the serious talent behind the language, they could&#39;ve made a real C++ language competitor, but instead they created this half-finished, poorly designed bastard-child-of-C language, which is sadly becoming somewhat popular. (Go pulled off in 3 years what Haskell couldn&#39;t do in a quarter century)</p></pre>Tacticus: <pre><blockquote> <p>Go pulled off in 3 years what Haskell couldn&#39;t do in a quarter century</p> </blockquote> <p>Because they settled on different goals and made the language useful.</p></pre>theonlycosmonaut: <pre><blockquote> <p>Avoid success at all costs!</p> </blockquote> <p>(EDIT: for the uninitiated, that&#39;s Haskell&#39;s tongue-mostly-in-cheek unofficial slogan.)</p></pre>storm14k: <pre><p>Or perhaps people aren&#39;t actually frustrated (as its apparently gaining popularity) and its just an argument in a sounding box often repeated by folks who haven&#39;t even touched the language. I&#39;m sure there are people that are using it that want generics but it doesn&#39;t seem like they are the ones carrying this argument.</p></pre>tealeg: <pre><p>Hmm, perhaps OOP is an overused paradigm, that contains a few good ideas, bundled in with things that make sense in a few cases, but actually create more problems than they solve.</p> <p>One of the reasons I <em>love</em> Go is that it seems like a distillation of the things that long experience shows are good, reliable, and understandable, and doesn&#39;t try to do <em>everything</em> that anyone ever thought of.</p> <p>Now, much of what I said also applies to the concept of &#34;generics&#34;, but refugees of the Java and C++ worlds probably have enough experience of the downside of inheritance to accept it as not necessary, but see generics as a saving grace of their previous experience. Those people are one good Lisp course away from clamouring for Lisp style Macros. I&#39;d join them, but if you really want to program that way, then you&#39;re probably going to have more fun with Lisp itself. </p> <p>Just my tuppence worth.</p></pre>howesteve: <pre><p>Actually, I miss methods overriding; i.e. A.foo() , B.foo(), B descends from A, calling A.foo() on a B instance, actually calls A.foo() while you want B.foo(). The rest is fair enough.</p></pre>Femaref: <pre><p>Considering there is no descends relationship in go (i.e. no inheritance; embedding is composition, not inheritance), method overriding makes no sense in go. </p></pre>howesteve: <pre><p>So what&#39;s the best way to solve that problem ? No matter how it&#39;s named.</p></pre>

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

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