How to create an array that can contain items of 2 different types, but only those 2 types?

xuanbao · · 487 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>For example, if I were to try and write this in Scala, I could do something like this:</p> <pre><code>sealed trait InputParameter case class InputArgument(value: String) extends InputParameter case class InputOption(name: String, value: Option[String]) extends InputParameter val params: List[InputParameter] = List( InputArgument(&#34;foo&#34;) InputOption(&#34;bar, Some(&#34;baz&#34;)) ) // ... </code></pre> <p>Placing any type other than <code>InputArgument</code> or <code>InputOption</code> in that List would be impossible. It&#39;d be caught at compile time.</p> <p>I&#39;ve just started with go, and have watched most of Essential Go on Kajabi Next, I&#39;ve looked in the FAQ for this, and I&#39;ve tried looking at examples of other people&#39;s code. The only thing I&#39;ve seen is something like this:</p> <pre><code>type InputParameter interface{} type InputArgument struct { InputParameter Value string } type InputOption struct { InputParameter Name string Value *string } params := []InputParameter{} </code></pre> <p>But... you are still completely open to do this:</p> <pre><code>params := []InputParameter{&#34;hello!&#34;} </code></pre> <p>I understand why this is, because the interface is empty, and so... any type implements that interface. This has left me totally stumped though; what is the most idiomatic way of doing what I&#39;ve shown above in Scala, in Go? Does this even matter?</p> <p>I&#39;d rather avoid writing some kind of pointless method adding a useless property to make the interface more relevant.</p> <hr/>**评论:**<br/><br/>lhxtx: <pre><p>Just make a struct to hold your two types and then make an array of your new struct. </p></pre>SeerUD: <pre><p>That is one solution I had considered, but in this case the position of the items is important (i.e. knowing the order all of the options or arguments as a whole collection). </p> <p>Would there be a way to keep that position information with that solution, without making the position be a part of the objects in the collection?</p></pre>mwholt: <pre><p>Only set one of the values in the struct for each item in the slice. Then you still have the order preserved.</p></pre>SeerUD: <pre><p>Hmm, I&#39;m not sure I understand. If I made a struct like this:</p> <pre><code>type InputParameters struct { arguments: []InputArgument options: []InputOption } </code></pre> <p>Then somewhere I use it like (assume the items being appended would be done in a loop as part of some condition, please excuse any incorrect syntax, I&#39;d also like to know if there&#39;s a more idiomatic way of doing this?):</p> <pre><code>params := InputParameters{} params.Arguments = append(params.Arguments, InputArgument{&#34;foo&#34;}); params.Options = append(params.Options, InputOption{&#34;bar&#34;, nil}); params.Options = append(params.Options, InputOption{&#34;baz&#34;, nil}); params.Arguments = append(params.Arguments, InputArgument{&#34;qux&#34;}); </code></pre> <p>How would I know the order of them? I mean, you&#39;d know the order the of the arguments and options respectively, but not all of them together, right?</p></pre>mwholt: <pre><p>Invert your structs/slices:</p> <pre><code>type InputParameter struct { argument *InputArgument option *InputOption } var params []InputParameter </code></pre> <p>I used pointers for the struct fields assuming that the types are structs, and you want to be able to detect a nil/zero value.</p></pre>SeerUD: <pre><p>Aah! Right! So then you can just check which type it is by checking which isn&#39;t nil - that makes a lot of sense, and has the advantage of avoiding making methods that intentionally do nothing. Perfect!</p></pre>lhxtx: <pre><pre><code>type Foo struct { Arg1 String Arg2 String } var theSlice []Foo // or if you knew you&#39;d have 5 and only 5 &#39;Foo&#39;s var theArray [5]Foo </code></pre> <p>And then your args are inside a struct? I&#39;m confused as to what else you would need? </p></pre>SeerUD: <pre><p><a href="/u/mwholt" rel="nofollow">/u/mwholt</a> already explained this one, I&#39;d never thought of it that way. :)</p></pre>gdey: <pre><p>No, in go an array can only have one type. There are couple way I can think of accomplishing what you want. The idiomatic way, is to use an interface with a dummy method that restricts the interface to the types you want. I have seen this in the standard library. It can take nothing and return nothing, and is really just to create the restriction.</p> <p>i.e. <a href="http://play.golang.org/p/F9KUVyELen" rel="nofollow">go play version</a></p> <pre><code>package main import ( &#34;fmt&#34; ) type InputParameterOption interface { InputParameterOption() } type InputParameter struct{} func (_ InputParameter) InputParameterOption() {} type InputArgument struct { InputParameter Value string } type InputOption struct { InputParameter Name string Value *string } func main() { inOpt := InputOption{ Name: &#34;Hello&#34;, } inArg := InputArgument{ Value: &#34;A Value&#34;, } // params := []InputParameterOption{inOpt, inArg, &#34;hello&#34;} // Does not compile, errors out with: // prog.go:33: cannot use &#34;hello&#34; (type string) as type InputParameterOption in array or slice literal: // string does not implement InputParameterOption (missing InputParameterOption method) params := []InputParameterOption{inOpt, inArg} fmt.Printf(&#34;%v&#34;, params) } </code></pre> <p>The second way would be to create a struct that has pointers to both types. Then have an array of that struct.</p> <p>i.e. <a href="http://play.golang.org/p/wilZoorMPy" rel="nofollow">go play version</a> package main</p> <pre><code>import ( &#34;fmt&#34; ) type InputParameter struct { inOpt *InputOption argOpt *InputArgument } type InputArgument struct { Value string } type InputOption struct { Name string Value *string } func main() { inOpt := InputOption{ Name: &#34;Hello&#34;, } inArg := InputArgument{ Value: &#34;A Value&#34;, } // []InputParameter{InputParameter{inOpt: &amp;inOpt}, InputParameter{argOpt: &amp;inArg}, &#34;Hello&#34;} // Does not compile, errors out with: // prog.go:30: cannot use &#34;Hello&#34; (type string) as type InputParameter in array or slice literal params := []InputParameter{InputParameter{inOpt: &amp;inOpt}, InputParameter{argOpt: &amp;inArg}} fmt.Printf(&#34;%v&#34;, params) } </code></pre></pre>SeerUD: <pre><p>Right, that is very interesting. I had feared that may be the case. Could you possibly point me to where in the standard library the first method is done? I&#39;m just curious to see it is all!</p> <p>Thanks for the insight, this has been very useful!</p></pre>idevxy: <pre><p>The solution to doing this the way you want to is to add a method to the interface. This is how you do explicit interface implementation in Go.</p> <pre><code>type InputParameter interface{ ImplementsInputParameter() } type InputArgument struct { Value string } func (_ InputArgument) ImplementsInputParameter() {} type InputOption struct { Name string Value *string } func (_ InputOption) ImplementsInputParameter() {} params := []InputParameter{} // params := []InputParameter{&#34;hello&#34;} compiler error: String does not satisfy interface InputParameter </code></pre></pre>pico303: <pre><p>You&#39;re still thinking in Scala. There&#39;s really no need for this kind of strict typing, and you&#39;re going to make your head hurt trying to implement it in Go. Who are you trying to protect?</p> <p>If I were implementing what you suggest, I&#39;d do this:</p> <pre><code>type InputParameter interface { Value() string } type InputArgument struct { Arg string } func (a InputArgument) Value string { return a.Arg } type InputOption struct { Name string Option *string // not sure why this is a pointer, but ok... } func (o InputOption) Value string { return *o.Option } params := make([]InputParameter, 0) // ... fmt.Println(&#34;Value is&#34;, params[0].Value()) </code></pre></pre>SeerUD: <pre><blockquote> <p>You&#39;re still thinking in Scala. ... Who are you trying to protect?</p> </blockquote> <p>I know, I know. And this is my concern, I don&#39;t know if I should just be more &#34;trusting&#34; when I&#39;m writing Go. Scala had many ways to keep things locked-down, that in a real application might not be that important.</p> <p>The reason the option has it&#39;s value be a pointer is because it may not have a value. For some context, I&#39;m just playing with the idea of a basic CLI parser to try and put some of the things I read about into use. So, an option would be something like <code>--foo</code> or <code>--foo=bar</code>. An argument, if it is there, will always just be whatever it is, so will always have a value.</p> <p>That is an interesting approach there, similar to what I had seen above in a way I suppose, though may not take into account the fact that the value could be nil when being accessed I guess (at least from what I currently know!)</p></pre>pico303: <pre><p>I get where you&#39;re coming from, but yeah, there&#39;s really no need to be so strict and protective. </p> <p>Idiomatic Go is to say a string with no value is &#34;&#34;:</p> <pre><code>if params[0].Value() != &#34;&#34; ... </code></pre> <p>That said, check out the Go &#34;flag&#34; package. It uses pointers in the way you describe.</p></pre>myg204: <pre><p>My version, not sure if that will help you. </p> <pre><code>// http://play.golang.org/p/lH3qx0U6IO package main import &#34;fmt&#34; type T interface { T() } type X int type Y string func (X) T() {} func (Y) T() {} func main() { x1, x2 := X(10), X(20) y1, y2 := Y(&#34;str1&#34;), Y(&#34;str2&#34;) arr := []T{x1, x2, y1, y2} fmt.Println(arr) } </code></pre></pre>bigpigfoot: <pre><p>Make a custom type of interface slice and then code ur own setters and getters to include type asserts for the 2 types </p> <p>type urtype []interface{}{}</p> <p>func (u *urtype) insert(type1) *urtype {} func (u *urtype) insert(type2) *urtype {}</p> <p>// ... Other set get functions</p></pre>hegbork: <pre><p>Why? I&#39;m not being difficult here, but that&#39;s the primary question you should be asking in this situation. If you want to store some things in the same storage to me it means one of two things:</p> <ol> <li>You just want to store things and for some reason don&#39;t want to store those things in two different containers. This is a bad idea, types matter.</li> <li>You want to store things in the same container because you&#39;ll be processing/using them in the same way somehow. This means that there will be some common access function. That means that they have the same interface for doing things. So just make an array of that interface.</li> </ol></pre>SeerUD: <pre><p>Option 2 is where I would like to be, but I haven&#39;t yet thought of a viable way of making this particular problem work that way.</p> <p>Option 1 on the other hand, I have got working with the help of other folks in this thread, and the types can be retrieved and handled with reflection in what seems to be a safe way. </p> <p>I understand your point, and I&#39;m going to keep exploring options, mainly just because it&#39;s fun to keep exploring the different solutions. I am only a couple of days into using Go, but would like to make sure I get started with it on the right foot! So, I&#39;m trying my best to write idiomatic code.</p></pre>threemux: <pre><p><a href="http://www.jerf.org/iri/post/2917" rel="nofollow">This post</a> may be helpful to you. It goes through several approaches to the problem you&#39;re describing and details the pros and cons of each. All of the Go posts on that blog are worth reading really.</p> <p>Edit: one of the important parts of that post is the section on when a &#34;tag&#34; interface is necessary. If you intend to do something specific with the members of that list down the line, then perhaps there&#39;s an interface hiding in there you could define and use instead. </p></pre>waywardcoder: <pre><p>One option no one has mentioned is to use packages to control the interface. So you can still have a parameter slice of interface{}, but keep that private, and prevent the user from putting &#34;hello!&#34; in it by only providing AddArg() and AddOption() methods. For something like your exact example that could be overkill, but in many cases this is a way to achieve compile-time safety without needing an advanced type system. </p></pre>dc0d: <pre><p>Your interface InputParameter is public &amp; usable/implementable outside your package. Now; add a private func to that interface (look at the lower case letter m):</p> <p>type InputParameter interface{ markerFunc() }</p> <p>And implement it in your structs: InputArgument and InputOption.</p> <p>Now nobody out of you package can implement InputParameter!</p></pre>SeerUD: <pre><p>This is pretty much what I&#39;ve gone with for the time being:</p> <pre><code>type ParsedInputParameter interface { isParsedInputParameter() } type parsedInputParameterLike struct{} func (_ parsedInputParameterLike) isParsedInputParameter() {} type ParsedInputArgument struct { parsedInputParameterLike Value string } type ParsedInputOption struct { parsedInputParameterLike Name string Value string } </code></pre></pre>xsolarwindx: <pre><p>You can&#39;t. Go&#39;s type system (if you even want to call it a &#34;type system&#34;) is extremely primitive. You have to use workarounds like a struct with multiple return value getters and setters that are appropriately typed.</p></pre>

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

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