문제

Go has no unions. But unions are necessary in many places. XML makes excessive use of unions or choice types. I tried to find out, which is the preferred way to work around the missing unions. As an example I tried to write Go code for the non terminal Misc in the XML standard which can be either a comment, a processing instruction or white space.

Writing code for the three base types is quite simple. They map to character arrays and a struct.

type Comment Chars

type ProcessingInstruction struct {
    Target *Chars
    Data *Chars
}

type WhiteSpace Chars

But when I finished the code for the union, it got quite bloated with many redundant functions. Obviously there must be a container struct.

type Misc struct {
    value interface {}
}

In order to make sure that the container holds only the three allowed types I made the value private and I had to write for each type a constructor.

func MiscComment(c *Comment) *Misc {
    return &Misc{c}
}

func MiscProcessingInstruction (pi *ProcessingInstruction) *Misc {
    return &Misc{pi}
}

func MiscWhiteSpace (ws *WhiteSpace) *Misc {
    return &Misc{ws}
}

In order to be able to test the contents of the union it was necessary to write three predicates:

func (m Misc) IsComment () bool {
    _, itis := m.value.(*Comment)
    return itis
}

func (m Misc) IsProcessingInstruction () bool {
    _, itis := m.value.(*ProcessingInstruction)
    return itis
}

func (m Misc) IsWhiteSpace () bool {
    _, itis := m.value.(*WhiteSpace)
    return itis
}

And in order to get the correctly typed elements it was necessary to write three getters.

func (m Misc) Comment () *Comment {
    return m.value.(*Comment)
}

func (m Misc) ProcessingInstruction () *ProcessingInstruction {
    return m.value.(*ProcessingInstruction)
}

func (m Misc) WhiteSpace () *WhiteSpace {
    return m.value.(*WhiteSpace)
}

After this I was able to create an array of Misc types and use it:

func main () {

    miscs := []*Misc{
        MiscComment((*Comment)(NewChars("comment"))),
        MiscProcessingInstruction(&ProcessingInstruction{
            NewChars("target"),
            NewChars("data")}),
        MiscWhiteSpace((*WhiteSpace)(NewChars(" \n")))}

    for _, misc := range miscs {
        if (misc.IsComment()) {
            fmt.Println ((*Chars)(misc.Comment()))
        } else if (misc.IsProcessingInstruction()) {
            fmt.Println (*misc.ProcessingInstruction())
        } else if (misc.IsWhiteSpace()) {
            fmt.Println ((*Chars)(misc.WhiteSpace()))
        } else {
            panic ("invalid misc");
        }
    }
}

You see there is much code looking almost the same. And it will be the same for any other union. So my question is: Is there any way to simplify the way to deal with unions in Go?

Go claims to simplify programing work by removing redundancy. But I think the above example shows the exact opposite. Did I miss anything?

Here is the complete example: http://play.golang.org/p/Zv8rYX-aFr

도움이 되었습니까?

해결책

As it seems that you're asking because you want type safety, I would firstly argue that your initial solution is already unsafe as you have

func (m Misc) Comment () *Comment {
    return m.value.(*Comment)
}

which will panic if you haven't checked IsComment before. Therefore this solution has no benefits over a type switch as proposed by Volker.

Since you want to group your code you could write a function that determines what a Misc element is:

func IsMisc(v {}interface) bool {
    switch v.(type) {
        case Comment: return true
        // ...
    }
}

That, however, would bring you no compiler type checking either.

If you want to be able to identify something as Misc by the compiler then you should consider creating an interface that marks something as Misc:

type Misc interface {
    ImplementsMisc()
}

type Comment Chars
func (c Comment) ImplementsMisc() {}

type ProcessingInstruction
func (p ProcessingInstruction) ImplementsMisc() {}

This way you could write functions that are only handling misc. objects and get decide later what you really want to handle (Comments, instructions, ...) in these functions.

If you want to mimic unions then the way you wrote it is the way to go as far as I know.

다른 팁

I think this amount of code might be reduced, e.g. I personally do not think that safeguarding type Misc against containing "illegal" stuff is really helpful: A simple type Misc interface{} would do, or?

With that you spare the constructors and all the Is{Comment,ProcessingInstruction,WhiteSpace} methods boil down to a type switch

switch m := misc.(type) {
    Comment: fmt.Println(m)
    ... 
    default: panic()
}

Thats what package encoding/xml does with Token.

I am not sure to understand your issue. The 'easy' way to do it would be like the encoding/xml package with interface{}. If you do not want to use interfaces, then you can do something like you did. However, as you stated, Go is a typed language and therefore should be use for typed needs. If you have a structured XML, Go can be a good fit, but you need to write your schema. If you want a variadic schema (one given field can have multiple types), then you might be better off with an non-typed language.

Very useful tool for json that could easily rewritten for xml: http://mholt.github.io/json-to-go/

You give a json input and it gives you the exact Go struct. You can have multiple types, but you need to know what field has what type. If you don't, you need to use the reflection and indeed you loose a lot of the interest of Go.

TL;DR You don't need a union, interface{} solves this better.

Unions in C are used to access special memory/hardware. They also subvert the type system. Go does not have the language primitives access special memory/hardware, it also shunned volatile and bit-fields for the same reason.

In C/C++ unions can also be used for really low level optimization / bit packing. The trade off: sacrifice the type system and increase complexity in favor of saving some bits. This of course comes with all the warnings about optimizations.

Imagine Go had a native union type. How would the code be better? Rewrite the code with this:

// pretend this struct was a union
type MiscUnion struct {
  c *Comment
  pi *ProcessingInstruction
  ws *WhiteSpace
}

Even with a builtin union accessing the members of MiscUnion requires a runtime check of some kind. So using an interface is no worse off. Arguably the interface is superior as the runtime type checking is builtin (impossible to get wrong) and has really nice syntax for dealing with it.

One advantage of a union type is static type check to make sure only proper concrete types where put in a Misc. The Go way of solving this is "New..." functions, e.g. MiscComment, MiscProcessingInstruction, MiscWhiteSpace.

Here is a cleaned up example using interface{} and New* functions: http://play.golang.org/p/d5bC8mZAB_

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top