【问题标题】:Best practice for unions in GoGo 中联合的最佳实践
【发布时间】:2014-02-28 11:25:38
【问题描述】:

Go has no unions。但在许多地方工会是必要的。 XML 过度使用联合或选择类型。我试图找出,这是解决失踪工会的首选方法。例如,我尝试在XML standard 中为非终端Misc 编写Go 代码,它可以是commentprocessing instructionwhite space

为这三种基本类型编写代码非常简单。它们映射到字符数组和结构。

type Comment Chars

type ProcessingInstruction struct {
    Target *Chars
    Data *Chars
}

type WhiteSpace Chars

但是当我完成联合的代码时,它变得非常臃肿,包含许多冗余函数。显然必须有一个容器结构。

type Misc struct {
    value interface {}
}

为了确保容器只包含三种允许的类型,我将值设为私有,并且必须为每种类型编写一个构造函数。

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

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

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

为了能够测试联合的内容,有必要编写三个谓词:

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
}

为了获得正确类型的元素,需要编写三个 getter。

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)
}

在此之后,我能够创建 Misc 类型的数组并使用它:

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");
        }
    }
}

您会看到很多代码看起来几乎相同。任何其他工会也是如此。所以我的问题是:有什么方法可以简化 Go 中处理联合的方式?

Go 声称通过removing redundancy 简化了编程工作。但我认为上面的例子正好相反。我错过了什么吗?

这里是完整的例子:http://play.golang.org/p/Zv8rYX-aFr

【问题讨论】:

    标签: go unions


    【解决方案1】:

    我认为这个代码量可能会减少,例如我个人认为保护type Misc 以防止包含“非法”内容真的没有帮助:一个简单的type Misc interface{} 就可以,或者?

    这样你就省去了构造函数和所有Is{Comment,ProcessingInstruction,WhiteSpace} 方法归结为一个类型开关

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

    这就是 package encoding/xml 对 Token 的作用。

    【讨论】:

    • 如果我将所有内容都存储在interface{} 中,我可以前往Scheme。使用静态类型语言只有在使用类型时才有意义。
    • 如果您使用非空接口,那么它将限制可以存储的内容,类似于您对 Misc 结构所做的操作。如果接口需要一个在其签名中使用非导出类型的方法,您可以合理地确定它只包含您的包定义的类型。您还可以使用该接口来公开您正在处理的三种类型的通用功能。
    【解决方案2】:

    我不确定是否理解您的问题。做到这一点的“简单”方法就像带有 interface{} 的 encoding/xml 包。如果您不想使用接口,那么您可以像以前那样做。 但是,正如您所说,Go 是一种类型化语言,因此应该用于类型化需求。 如果你有一个结构化的 XML,Go 可能是一个不错的选择,但你需要编写你的模式。如果你想要一个可变参数模式(一个给定的字段可以有多种类型),那么你最好使用非类型化语言。

    非常有用的 json 工具,可以轻松地为 xml 重写: http://mholt.github.io/json-to-go/

    您提供一个 json 输入,它会为您提供确切的 Go 结构。你可以有多种类型,但你需要知道哪个字段有什么类型。如果你不这样做,你需要使用反射,实际上你会失去很多对 Go 的兴趣。

    【讨论】:

      【解决方案3】:

      您似乎是因为想要类型安全而问的,我首先要争辩说您的初始 解决方案已经不安全了

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

      如果您之前没有检查过IsComment,它会惊慌失措。因此,该解决方案没有任何好处 Volker 提出的类型开关。

      由于您想对代码进行分组,您可以编写一个函数来确定 Misc 元素是什么:

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

      然而,这也不会为您带来编译器类型检查。

      如果您希望编译器能够将某些内容识别为Misc,那么您应该 考虑创建一个接口,将某些东西标记为Misc

      type Misc interface {
          ImplementsMisc()
      }
      
      type Comment Chars
      func (c Comment) ImplementsMisc() {}
      
      type ProcessingInstruction
      func (p ProcessingInstruction) ImplementsMisc() {}
      

      这样你就可以编写只处理杂项的函数。对象并稍后决定 您真正想要在这些函数中处理的内容(评论、说明、...)。

      如果您想模仿联合,那么您编写它的方式就是我所知道的方式。

      【讨论】:

        【解决方案4】:

        TL;DR您不需要联合,interface{} 可以更好地解决这个问题。

        C 中的联合用于访问特殊的内存/硬件。他们还颠覆类型系统。 Go 没有语言原语访问特殊的内存/硬件,出于同样的原因,它也避开了volatile 和位域。

        在 C/C++ 中,联合也可以用于非常低级的优化/位打包。权衡:牺牲类型系统并增加复杂性以节省一些位。这当然伴随着所有关于优化的警告。

        想象一下 Go 有一个本地联合类型。代码如何更好?用这个重写代码:

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

        即使使用内置联合访问MiscUnion 的成员,也需要某种运行时检查。因此,使用接口并不会更糟。可以说,interface 更胜一筹,因为运行时类型检查是内置的(不可能出错),并且处理它的语法非常好。

        联合类型的一个优点是静态类型检查,以确保只有正确的具体类型放入 Misc。解决此问题的 Go 方法是“新...”功能,例如MiscComment, MiscProcessingInstruction, MiscWhiteSpace.

        这是一个使用 interface{}New* 函数的清理示例:http://play.golang.org/p/d5bC8mZAB_

        【讨论】:

        • 您的示例中Misc 的问题是,它不提供类型安全性。其目的是,Misc 的内容只能是注释、处理指令或空格。但这不是由语言强制执行的。相反,创建此杂项也是有效的:[]Misc{"kick", "ass", "this", "meaningless", "type"]。我所说的联合不是确切的 C 联合,而是 "choice type""sum type""variant type"
        • 哇!我真的很喜欢Sum Types in Go 的文章。很棒的发现,我认为这可能是您问题的最佳答案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-27
        • 2011-12-23
        • 2010-09-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多