Ways of dealing with destructive functions.

xuanbao · · 399 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;m a little unsure what the most idiomatic way of dealing with destructive functions in Go is.</p> <p>Let&#39;s say I have the following:</p> <pre><code>func ShuffleA(slice *[]Card) { // shuffling code here, unimportant for the issue at hand } func ShuffleB(slice []Card) []Card { // shuffling code here, unimportant for the issue at hand } </code></pre> <p><code>ShuffleA</code> is destructive, while <code>ShuffleB</code> is not.</p> <p>The question is, which one is the more idiomatic to use in Go, and more importantly, if I have one, is there an idiomatic name for the other?</p> <p>In <em>Scheme</em> destructive functions are suffixed with a <code>!</code>, so there you&#39;d have <code>shuffle</code> and <code>shuffle!</code>, which gives a clear indicator as to what&#39;s destructive and what isn&#39;t. Now, I know Go isn&#39;t Scheme, and I&#39;m not looking to compare them. I&#39;m just asking in case other people are going to use my code; I don&#39;t want the functions to go against what they expect them to do.</p> <hr/>**评论:**<br/><br/>Fwippy: <pre><p>I think the destructive version is more idiomatic.</p> <p>If you wanted to provide both, I&#39;d suggest</p> <pre><code>func Shuffle(slice []Card){} func Shuffled(slice []Card) []Card{} </code></pre> <p>(inspiration from python&#39;s <code>sort</code> and <code>sorted</code>).</p></pre>anotherdonald: <pre><p>I&#39;m just a go beginner, but they have different uses, don&#39;t they? ShuffleA affects all data structures pointing at slice, while ShuffleB doesn&#39;t (assuming it just shuffles the contents of the array).</p> <p>I don&#39;t remember having seen this mentioned in the context of naming, though.</p></pre>TheTimegazer: <pre><p>well A and B only affect slices of that particular type, the only real difference between them is that one destroys the original slice by modifying it; while the other returns a new slice, leaving the original untouched</p></pre>mcouturier: <pre><p>Everywhere in core I have seen the second way. Check append(), bytes.Replace, etc...</p></pre>ultra_brite: <pre><p>It doesn&#39;t matter, none is more idiomatic than the other. Suffixing a function with a ! is a matter of convention in Scheme. Go doesn&#39;t support ! in variable names.</p></pre>TheMerovius: <pre><p>When dealing with slices, it&#39;s pretty unidiomatic to use a pointer, but it still would be idiomatic to do a shuffle &#34;destructively&#34;, so my answer is, neither, but instead</p> <pre><code>func ShuffleC(slice []Card) { // shuffling code here, unimportant for the issue at hand } </code></pre> <p>Shuffling doesn&#39;t need to change a slice, just it&#39;s contents, so don&#39;t take a pointer. See, for example, all the <code>sort.Interface</code> implementations.</p> <p>Only return a new slice, if what you want can not be done well in-place anyway. <code>string.Replace</code> is an example of something that <em>cant</em> be done in-place, so returning the new string is fine. Same for <code>bytes.Replace</code>: In general, the resulting slice will have a different lengths from the input, so the slice would need to be modified.</p> <p>Another example of how to deal with this is <code>io.Reader.Read</code>. Strictly speaking it would need to modify the slice (because it only fills part of it). Instead, it does a completey different thing, which is to return an integer with the number of bytes filled.</p> <p>Overall, there is no convention around any of this. I&#39;d encourage you to lean on the destructive side, though, because that way you give the choice of whether or not to allocate to the user of your package. Go is not a functional language, so the compiler will do less work around optimizing out such allocations. But in the end, you should do what makes most sense for your API. The signature will mostly make it clear whether the function is destructive or not, otherwise the documentation should tell you.</p></pre>danredux: <pre><p>Mutate your arguments, it&#39;s more efficient.</p> <p>The caller will copy anything it needs to keep.</p></pre>Nathanfenner: <pre><p>If you&#39;re only shuffling, and not changing the length of the slice or replacing it with a slice allocated elsewhere in memory, there is no reason to pass a pointer to the slice. Slices are just (data, length, capacity) tuples (in some order). If you modify the values it points to, the &#34;original&#34; slice (which is an exact, shallow copy of the original) would be modified.</p> <p>For example:</p> <pre><code>function Shuffle(slice []Card) { for i := range slice { j := rand.Intn(len(slice) - i) + i slice[i], slice[j] = slice[j], slice[i] } } </code></pre> <p>will work as expected and modify the passed-in slice.</p> <p>Pointers to maps, slices, and interfaces aren&#39;t used much (although for example if you want to modify the length of a passed in slice, you would need to pass a pointer to it).</p> <p>I would say that usually, if the function is simple, such as shuffling, it doesn&#39;t really matter what you do. If someone doesn&#39;t want their slice modified, they can just copy it before calling.</p> <p>If the function is complicated, and, for example, also returns some other value, then it is probably undesirable to modify the arguments if it would be surprising.</p></pre>NeverUse-YouPromised: <pre><p>Slices are reference types, kind of like hidden pointers. You don&#39;t need a pointer to a slice to be able to modify it within a function.</p></pre>tdewolff: <pre><p>You <em>do</em> need a pointer if you add or remove items</p></pre>

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

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