About the "comma ok" idiom

blov · · 1594 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I originally posted this on <a href="http://stackoverflow.com/a/30135334/455678">Stack Overflow</a> but I thought others may find this interesting. I&#39;ve always wondered myself why the <code>ok</code> part of indexing a map is optional, and I didn&#39;t even know that you could use it for channels! For context if you don&#39;t feel like going to Stack Overflow, OP was wondering why he could not return multiple values with:</p> <pre><code>func FindUserInfo(id string) (Info, bool) { return all[id] } </code></pre> <hr/> <p>To elaborate on my <a href="http://stackoverflow.com/questions/30129206/golang-return-multiple-values-issue?noredirect=1#comment48368941_30129206">comment</a>, the <a href="https://golang.org/doc/effective_go.html#maps">Effective Go</a> mentions that the multi-value assignment from accessing a map key is called the &#34;comma ok&#34; pattern.</p> <blockquote> <p>Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for &#34;UTC&#34; or is that the empty string because it&#39;s not in the map at all? You can discriminate with a form of multiple assignment.</p> </blockquote> <pre><code>var seconds int var ok bool seconds, ok = timeZone[tz] </code></pre> <blockquote> <p>For obvious reasons this is called the “comma ok” idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false.</p> </blockquote> <p><a href="http://play.golang.org/p/cgtysKkLEp">Playground demonstrating this</a></p> <p>We can see that this differs from calling a regular function where the compiler would tell you that something is wrong:</p> <pre><code>package main import &#34;fmt&#34; func multiValueReturn() (int, int) { return 0, 0 } func main() { fmt.Println(multiValueReturn) asgn1, _ := multiValueReturn() asgn2 := multiValueReturn() } </code></pre> <p>On the <a href="http://play.golang.org/p/fe29S1706D">playground</a> this will output</p> <pre><code># command-line-arguments /tmp/sandbox592492597/main.go:14: multiple-value multiValueReturn() in single-value context </code></pre> <p>This gives us a hint that it may be something the compiler is doing. <a href="https://github.com/golang/go/search?utf8=%E2%9C%93&amp;q=commaOk">Searching the source code</a> for &#34;commaOk&#34; gives us a few places to look, including <a href="https://github.com/golang/go/blob/254964074f34dc1cb39693f3e23a95938092044f/src/go/types/call.go#L118-L134"><code>types.unpack</code></a></p> <p>At the time of writing this it this the method&#39;s godoc reads:</p> <pre><code>// unpack takes a getter get and a number of operands n. If n == 1, unpack // calls the incoming getter for the first operand. If that operand is // invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a // function call, or a comma-ok expression and allowCommaOk is set, the result // is a new getter and operand count providing access to the function results, // or comma-ok values, respectively. The third result value reports if it // is indeed the comma-ok case. In all other cases, the incoming getter and // operand count are returned unchanged, and the third result value is false. // // In other words, if there&#39;s exactly one operand that - after type-checking // by calling get - stands for multiple operands, the resulting getter provides // access to those operands instead. // // If the returned getter is called at most once for a given operand index i // (including i == 0), that operand is guaranteed to cause only one call of // the incoming getter with that i. // </code></pre> <p>The key bits of this being that this method appears to determine whether or not something is <em>actually</em> a &#34;comma ok&#34; case.</p> <p>Digging into that method tells us that it will check to see if the mode of the operands is <strong>indexing a map</strong> or if the mode is set to <code>commaok</code> (where this <a href="https://github.com/golang/go/blob/677839353e013b13e0fb3d28e01a01a5ccf0c7a1/src/go/types/operand.go#L28">is defined</a> does give us many hints on when it&#39;s used, but searching the source for assignments to <code>commaok</code> we can see it&#39;s used when <a href="https://github.com/golang/go/blob/677839353e013b13e0fb3d28e01a01a5ccf0c7a1/src/go/types/expr.go#L107">getting a value from a channel</a> and <a href="https://github.com/golang/go/blob/677839353e013b13e0fb3d28e01a01a5ccf0c7a1/src/go/types/expr.go#L1343">type assertions</a>). Remember the bolded bit for later!</p> <pre><code>if x0.mode == mapindex || x0.mode == commaok { // comma-ok value if allowCommaOk { a := [2]Type{x0.typ, Typ[UntypedBool]} return func(x *operand, i int) { x.mode = value x.expr = x0.expr x.typ = a[i] }, 2, true } x0.mode = value } </code></pre> <p><code>allowCommaOk</code> is a parameter to the function. Checking out where <code>unpack</code> is called in that file we can see that all callers pass <code>false</code> as an argument. Searching the rest of the repository leads us to <code>assignments.go</code> in the <a href="https://github.com/golang/go/blob/254964074f34dc1cb39693f3e23a95938092044f/src/go/types/assignments.go#L207-L208"><code>Checker.initVars()</code> method</a>.</p> <pre><code>l := len(lhs) get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 &amp;&amp; !returnPos.IsValid()) </code></pre> <p>Since it seems that we can only use the &#34;comma ok&#34; pattern to get two return values when doing a multi-value assignment this seems like the right place to look! In the above code the length of the left hand side is checked, and when <code>unpack</code> is called the <code>allowCommaOk</code> parameter is the result of <code>l == 2 &amp;&amp; !returnPos.IsValid()</code>. The <code>!returnPos.IsValid()</code> is somewhat confusing here as that would mean that the position <a href="https://github.com/golang/go/blob/6ffed3020c0fb749868b71d5b13fef65ff248902/src/go/token/position.go#L80-L83">has no file or line information associated</a> with it, but we&#39;ll just ignore that.</p> <p>Further down in that method we&#39;ve got:</p> <pre><code>var x operand if commaOk { var a [2]Type for i := range a { get(&amp;x, i) a[i] = check.initVar(lhs[i], &amp;x, returnPos.IsValid()) } check.recordCommaOkTypes(rhs[0], a) return } </code></pre> <p><strong>So what does all of this tell us?</strong></p> <ul> <li>Since the <code>unpack</code> method takes an <code>allowCommaOk</code> parameter that&#39;s hardcoded to false everywhere <em>except</em> in <code>assignment.go</code>&#39;s <code>Checker.initVars()</code> method, we can probably assume that you will only ever get two values when doing an assignment and have two variables on the left-hand side.</li> <li>The <code>unpack</code> method will determine whether or not you actually <em>do</em> get an <code>ok</code> value in return by checking if you are indexing a slice, grabbing a value from a channel, or doing a type assertion</li> <li>Since you can only get the <code>ok</code> value when doing an assignment it looks like in your specific case you will always need to use variables </li> </ul> <hr/>**评论:**<br/><br/>mdempsky: <pre><blockquote> <p>OP was wondering why he could not return multiple values with:</p> </blockquote> <p>Wouldn&#39;t it be easier to just reference the <a href="http://golang.org/ref/spec" rel="nofollow">Go Programming Language Specification</a>?</p> <blockquote> <p>An index expression on a map <code>a</code> of type <code>map[K]V</code> used in an assignment or initialization of the special form</p> <pre><code>v, ok = a[x] v, ok := a[x] var v, ok = a[x] </code></pre> <p>yields an additional untyped boolean value. The value of <code>ok</code> is <code>true</code> if the key <code>x</code> is present in the map, and <code>false</code> otherwise.</p> </blockquote> <p>The statement <code>return all[id]</code> is not of those special forms, so it doesn&#39;t yield the additional boolean value.</p> <p>Also, the go/types code that you&#39;re referring to isn&#39;t (at least currently) part of the gc compiler toolchain.</p></pre>Raiyni: <pre><p>I kind of assumed that is how it worked before reading the explanation. Originally I would have said that the compiler didn&#39;t check the all function could return two types (like a couple of the answerers suggest).</p></pre>

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

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