naive "generics" proposals

agolangf · · 654 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I work daily in Java (sad, but true), and appreciate there are some cases where generics are really handy. Most examples, to my eye, are sinful. Here are a couple sinful examples:</p> <pre><code>Map&lt;String, List&lt;Map&lt;String, String&gt;&gt;&gt; public class FiniteStateMachine&lt;S extends Enum&lt;S&gt; &amp; State, T extends Enum&lt;T&gt; &amp; EventType, M extends EventMessage&lt;T&gt;, A extends ActionContext&lt;S, T, M&gt;&gt; </code></pre> <p>I won&#39;t waste time explaining why/what I hate about those. I&#39;m sure we can all mostly agree (given this is a go forum) that they are evil. (I realize that the first example can be replicated in Go with maps and slices, but I still think it&#39;s evil.)</p> <p>In any case, it seems to me there are 2 cases in go where we might want to use generics:</p> <ol> <li>to define a &#34;generic&#34; struct/func that operates on an input &#34;generic&#34; item (and maybe returns it)</li> <li>to define a &#34;generic&#34; struct/func that does no operations, but returns an input &#34;generic&#34; thing</li> </ol> <p>In case 1, the solution is to define an appropriate interface, and just use the interface in our new definition, and in our new methods. It&#39;s simple to define methods which return a defined interface type.</p> <p>Case 2 is the only problem. When we want to handle/store/return a thing of any type, upon which we will do no operations. We do not want to use the empty interface, <code>interface{}</code>, and we want to declare a return type that the compiler can do something useful with.</p> <p>So here is the proposal: Allow &#34;generics&#34; but only for a single level of type anonymity. The syntax might look something like this:</p> <pre><code>func MyFunc(i int, thing *&lt;Thing&gt;) *&lt;Thing&gt; { if i &gt; 5 { return thing } return nil } type MyThingContainer struct { thing &lt;Thing&gt; counter int } func MyThingContainer Get() &lt;Thing&gt; { return thing } </code></pre> <p>The main point is that generics are defined only a single level deep, and that it says nothing about any operations that may be performed on them. All it says is that you can receive and return a thing of any type, and that the type is preserved across that exchange.</p> <p>In cases where it&#39;s necessary to build a complicated object like <code>Blaft&lt;Glorp, Icker&lt;Blaft&lt;Glorp, Gaggle&gt;&gt;&gt;</code> each level would have it&#39;s own type definitions. Defining multilevel types would not be supported.</p> <p>Are there strong reasons to support something more complicated that this?</p> <hr/>**评论:**<br/><br/>devlambda: <pre><p>I think in order to have an in-depth debate about parametric polymorphism, it is a good idea to become familiar with not just the popular approaches used by Java and C++, but also with well-understood alternative forms. Most importantly:</p> <ol> <li>Modules parameterized by modules (ML functors, Ada generic packages, Modula-3 generic modules). Especially as they have a solid history in system programming languages.</li> <li>Typeclasses (Haskell&#39;s version in particular). Note while they are popular in functional programming, they are not inherently tied to functional programming in any way, shape, or form.</li> </ol> <p>Java generics and C++ templates have very specific and idiosyncratic characteristics that one should not generalize for all kinds of parametric polymorphism.</p></pre>colezlaw: <pre><p>To extend the thought - those who day &#34;Go lacks generics&#34; already have a solution in mind without articulating the actual goal. After asking vendors for features for much of my career, I&#39;ve learned to stop saying &#34;it must be red, have four doors, four wheels, have a Max speed of 80mph, get 30mpg, and an automatic transmission&#34; and rather to say &#34;I need to get from New York to Los Angeles&#34;. Often, the vendor can give me a private jet, which is far better than what I had in mind. </p></pre>dlsniper: <pre><p>You can have a read here about generics in Go <a href="https://github.com/golang/go/issues/15292" rel="nofollow">https://github.com/golang/go/issues/15292</a></p> <p>However, before jumping to the solution, make sure you define the problem, the examples show the problem (and are not forced to show it but rather they are natural, from code itself), think about solution and the ramifications of that solution.</p></pre>spaghettiCodeArtisan: <pre><blockquote> <p>I won&#39;t waste time explaining why/what I hate about those.</p> </blockquote> <p>Why not? That would make it easier to think about how to fix them.</p> <p>The first one, I&#39;d say, is a pretty common example of lack of custom types. It could probably be fixed pretty easily by introducing one or few newtypes / custom classes.</p> <p>The second one seems to be a generic type for an FSM. Defining FSMs generically is always going to be complex, that&#39;s not a problem with generics being complex but rather the problem itself being complex. People also use code generators to generate FMSs and they are complex too even when they use no generics. Also, the example is a generic definition, which is typically more complex than it&#39;s actual usage.</p> <p>So, I guess the corollary is &#34;not a problem with generics&#34;. Sure, the first one is needlessly nested, but then again you can needlessly nest functions or modules or other things as well. Not to mention that the first one can <em>already</em> be rewritten in Go verbatim :D</p> <p>The second is pretty complex indeed, but that&#39;s not something that can be feasibly avoided IMHO other than by shifting the objective.</p></pre>grutoc: <pre><p>I don&#39;t need this, show me an example where I can&#39;t use an interface.</p></pre>StyMaar: <pre><p>Why this much zealotry against generics ? Of course you can use an interface but in many cases it&#39;s just a hack : it&#39;s not type-safe, it can&#39;t be optimized (monomorphization + inlining) then it has a performance overhead.</p> <p>Generics are not more difficult to use than interfaces, it&#39;s just a better version of it most of the time. You should use generics almost all the time, and use interface only when you want type dynamism (you want to opt-out type-safety).</p></pre>forfunc: <pre><p>I think he means an interface not an interface{} </p></pre>thomasfr: <pre><p>I think that the most problematic thing about adding some kind of templates/polymorphic support is that it will be overused when interfaces (not emtpy ones) are good enough or better. Especially by people coming from C++-like languages.</p> <p>And it&#39;s not hard at all to show examples since there is a very good recent one in the standard library now: <code>sync.Map</code>.</p></pre>bascule: <pre><p><code>sync.Map</code> uses <code>interface{}</code> for all keys and values and is not type safe:</p> <p><a href="https://github.com/golang/go/blob/master/src/sync/map.go" rel="nofollow">https://github.com/golang/go/blob/master/src/sync/map.go</a></p></pre>thomasfr: <pre><p>Exactly, that was my point.. sync.Map is a great example of where some kind of templates-like feature would be useful. </p> <p>Rather than having &#34;generics&#34; in the compiler I would be interested in more exploration of go generate first. </p> <p>One exmple which I don&#39;t know if anyone has explored is an <code>eg</code> like tool for go generate which instead of doing an in place refactoring copies an implementation changing it&#39;s types or something based on example code.. You could then have <code>syncmap_generate.go</code> or something which contains some example code which copies and transforms the original source. I can&#39;t say I have thought about all the potential pitfalls of this yet but one of them is probably types/members private to another package.</p> <p>As long as something ends up inside the go tool chain at some point I will be happy, it doesn&#39;t have to be compile time as long as it can be statically verified in some way.. </p></pre>grutoc: <pre><p>I don&#39;t use <code>interface{}</code> either, deal with it.</p></pre>gott_modus: <pre><p>why not just write in c then, man.</p></pre>grutoc: <pre><p>because it&#39;s shit?</p></pre>gott_modus: <pre><p>what makes it so shit</p></pre>grutoc: <pre><p>It blows in your face before you know it every single time.</p></pre>gott_modus: <pre><p>you&#39;re gonna have to bring forth more than that, man. generally speaking, making the claim that something is &#34;shit&#34; warrants some concrete evidence of shittyness. </p></pre>thomasfr: <pre><p>Does that mean that you never use <code>fmt.Sprintf</code>, <code>json.Unmarshal</code>, ..., ...? <code>interface{}</code> is very useful in the right places.</p></pre>grutoc: <pre><p>Go is so unique and it dosen&#39;t have Generics, I fear it will mass destroy the language, my only argument.</p></pre>Sythe2o0: <pre><p><a href="https://github.com/emirpasic/gods" rel="nofollow">https://github.com/emirpasic/gods</a></p></pre>grutoc: <pre><p>Damn it, already forgot all this crap...</p></pre>Hauleth: <pre><p><code>Min</code> and <code>Max</code> functions. </p></pre>CountyMcCounterson: <pre><p>What&#39;s wrong with being able to pass a string to a math function and have it be assumed to be an integer? I see no problem there.</p></pre>Killing_Spark: <pre><p>Well on strings that only contain ascii its fairly ok to do so, but then there are those asian fuck tons of characters :D</p></pre>thomasfr: <pre><p>I rather handle parsing and math separately because of what happens when the assumption fails or what the returned type might be.</p> <p><code>value:=Max(&#34;asdf&#34;)</code>: If there is only one return value this has to panic which is very special case usage which can be implemented in application code when and if it&#39;s needed.</p> <p><code>value, err := Max(&#34;3.14&#34;)</code>: The type of <code>value</code> cannot be statically inferred because it can be either an int or float.</p> <p>IMHO both of those examples shows that it&#39;s easier to handle number parsing and max&#39;ing in separate steps in your program because otherwise would have to keep track of too many permutations of Max behavior.</p></pre>gott_modus: <pre><p>why not just write in c then, man.</p></pre>_axelp_: <pre><p>And honestly, the same thing should be said of ints to string functions. Something that REALLY irks me in python is the fact that I can&#39;t do</p> <pre><code>i = 2 s = &#34;This is Python version &#34; print s + i </code></pre> <p>But rather I have to </p> <pre><code>print s + str(i) </code></pre> <p>(or format or printf), but I still can&#39;t see why integers (or anything with __str__) can&#39;t be passed to print</p></pre>thomasfr: <pre><p>I would probably not be using python if + was supported like that.. It&#39;s just an endless source of bugs everywhere when things like that is allowed</p> <p>You still have several string printing functions to choose from which does the job just fine like <code>print(&#34;{}{}&#34;.format(&#34;string&#34;, 123)))</code> </p></pre>_axelp_: <pre><p>I do use format() for most things, but it&#39;s a little annoying</p> <p>I can see where some confusion may be initially seen from it, but after you learn about it could be easily adopted. I don&#39;t see how any interpretation from str+int could be seen <strong>unless</strong> it&#39;s a str of &#34;5&#34; + 3 which then you could as should it return int(8), str(8), str(&#34;5 + 3&#34;), or str(&#34;53&#34;) but I think if you could adopt an official stance it would be beneficail</p></pre>thomasfr: <pre><p>For me it&#39;s not so much about confusion, primarily it&#39;s about security and identifying bugs.. I want my code to fail as early as possible if a string by accident or malice has made it&#39;s way into some number computation code.</p></pre>_axelp_: <pre><p>Ah I didn&#39;t think about security, but I don&#39;t think I wrote my last comment right. Strings would <strong>never</strong> be converted to ints (I agree with that!!), but rather ints would be converted to strings when in a print (or other output) function.</p> <p>Something close to:</p> <pre><code>def print(*args): final_str = &#34;&#34; for arg in args: if not isinstance(arg, str): try: final_str+=arg.__str__ except: pass else: final_str+=arg return final_str </code></pre> <p>Or something</p></pre>Poddster: <pre><blockquote> <pre><code>print s + int(i) </code></pre> </blockquote> <p>You mean:</p> <pre><code>print s + str(i) </code></pre></pre>wicked-canid: <pre><blockquote> <p>but I still can&#39;t see why integers (or anything with __str__) can&#39;t be passed to print</p> </blockquote> <p>They can, but that&#39;s not what you&#39;re doing here: you&#39;re adding a string to an int, the print has nothing to do with it. </p></pre>_axelp_: <pre><p>You&#39;re totally right! I don&#39;t know what I was thinking :)</p></pre>

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

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