Go 中创建复杂结构层次结构的惯用方法是什么? [英] What is the idiomatic way in Go to create a complex hierarchy of structs?

查看:23
本文介绍了Go 中创建复杂结构层次结构的惯用方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用 Go 编写一个解释器,我正在寻找存储 AST 的惯用方法.我阅读了 Go 编译器源代码,似乎他们使用带有空方法的接口来表示 AST.例如,我们有以下层次结构,

对象--不动-  - 建造-  - 山- 活动-  - 车-  - 自行车

这就是上面的层次结构是如何以空方法"的方式实现的.

type 对象接口 {目的()}类型不可移动接口{目的不动()}类型构建结构{...}类型山结构{...}类型可移动接口{目的活动()}类型汽车结构{...}类型山结构{...}func (*Building) object() {}func (*Mountain) object() {}func (*Car) object() {}func (*Bike) object() {}func (*Building) immovable() {}func (*Mountain) immovable() {}func (*Car) 可移动 () {}func (*Bike) 可移动() {}

上面的代码是一个人为的例子,这是 Go 编译器如何实现 带有数十个空方法的 AST.但为什么?注意定义了多少空方法.随着层次结构深度的增加,它可能会变得非常复杂.

注释中指出空方法不允许分配不兼容的类型.例如,在我们的示例中,不能将 *Car 分配给 *Immovable.

这在支持继承的 C++ 等其他语言中非常容易.我想不出任何其他方式来表示 AST.

Go 编译器 AST 的实现方式可能是惯用的,但不是不那么直接吗?

解决方案

Go 是 不是(相当) 一种面向对象的语言:它没有类并且它没有类型继承一>;但它在 struct 级别和 interface 级别都支持称为 embedding 的类似结构,并且它确实具有 方法.

接口 在 Go 中只是固定的方法集.一个类型隐式实现一个接口,如果它的方法集是接口的超集(没有意图的声明).

如果您想记录明确声明您的类型确实实现了一个接口(因为它没有明确声明),则空方法非常有用.官方 Go FAQ:如何保证我的类型满足接口?

type Fooer interface {富()实现Fooer()}

如果你想区分你的类型层次结构(例如,你不想让一个对象同时是MovableImmovable),它们必须有不同的方法集合(MovableImmovable 的每个方法集合中必须至少有 1 个方法集合中不存在其他方法集合),因为如果方法集合将包含相同的方法,一个实现也会自动实现另一个,因此您可以将 Movable 对象分配给 Immovable 类型的变量.

向具有相同名称的接口添加空方法将为您提供这种区别,假设您不会将此类方法添加到其他类型.

减少空方法的数量

我个人对空方法没有任何问题.不过有一种方法可以减少它们.

如果您还为层次结构中的每个类型创建了一个 struct implementation 并且每个实现embeds struct执行上一层,上一层的方法集会自动来,不用多说:

对象

Object 接口和 ObjectImpl 实现:

type 对象接口 {目的()}类型 ObjectImpl 结构 {}func (o *ObjectImpl) object() {}

不可移动

Immovable 接口和 ImmovableImpl 实现:

type Immovable interface {目的不动()}类型 ImmovableImpl 结构 {ObjectImpl//嵌入 ObjectImpl}func (o *Immovable) immovable() {}

注意ImmovableImpl只增加了immovable()方法,object()是继承的".>

建筑

构建实现:

type 构建结构 {ImmovableImpl//嵌入 ImmovableImpl 结构//建筑特定的其他字段可能会来这里}

注意Building 不添加任何新方法,但它自动成为一个Immovable 对象.

如果子类型"的数量增加或接口类型具有不止 1 个标记"方法(因为所有方法都是继承的"),则这种技术的优势会大大增加.

I am writing an interpreter in Go and I am looking for the idiomatic way to store the AST. I read the Go compiler source code and it seems they used interfaces with an empty method to represent the AST. For example, we have the following hierarchy,

Object
--Immovable
----Building
----Mountain
--Movable
----Car
----Bike

This is how the above hierarchy is implemented in the "empty method" way.

type Object interface {
  object()
}

type Immovable interface {
  Object
  immovable()
}

type Building struct {
  ... 
}

type Mountain struct {
  ... 
}

type Movable interface {
  Object
  movable()
}

type Car struct {
  ...
} 

type Mountain struct {
  ...
} 

func (*Building) object() {}
func (*Mountain) object() {}
func (*Car) object() {}
func (*Bike) object() {}
func (*Building) immovable() {}
func (*Mountain) immovable() {}
func (*Car) movable() {}
func (*Bike) movable() {}    

The above code is a contrived example and this is how the Go compiler implemented the AST with dozens of empty methods. But WHY? Note how many empty methods are defined. It may get very complicated with the increase of the depth of the hierarchy.

It is stated in the comments that the empty methods disallow the assignment of incompatible types. In our example, a *Car can't be assigned to a *Immovable for instance.

This is so easy in other languages like C++ that supports inheritance. I can't think of any other way of representing the AST.

The way how the Go compiler AST is implemented may be idiomatic but isn't it less straight forward?

解决方案

Go is not (quite) an object oriented language: it does not have classes and it does not have type inheritance; but it supports a similar construct called embedding both on struct level and on interface level, and it does have methods.

Interfaces in Go are just fixed method sets. A type implicitly implements an interface if its method set is a superset of the interface (there is no declaration of the intent).

Empty methods are great if you want to document or state explicitly that your type does implement an interface (because it is not stated explicitly). Official Go FAQ: How can I guarantee my type satisfies an interface?

type Fooer interface {
    Foo()
    ImplementsFooer()
}

If you want a distinction in your type hierarchy (e.g. you don't want to allow an object to be both Movable and Immovable), they must have different method sets (there must be at least 1 method in each of the method sets of Movable and Immovable that is not present in the other's), because if the method sets would contain the same methods, an implementation of one would automatically implement the other too therefore you could assign a Movable object to a variable of type Immovable.

Adding an empty method to the interface with the same name will provide you this distinction, assuming that you will not add such methods to other types.

Reducing the number of empty methods

Personally I have no problem with empty methods whatsoever. There is a way to reduce them though.

If you also create a struct implementation for each type in the hierarchy and each implementation embeds the struct implementation one level higher, the method set of one level higher will automatically come without further ado:

Object

Object interface and ObjectImpl implementation:

type Object interface {
  object()
}
type ObjectImpl struct {}
func (o *ObjectImpl) object() {}

Immovable

Immovable interface and ImmovableImpl implementation:

type Immovable interface {
    Object
    immovable()
}
type ImmovableImpl struct {
    ObjectImpl // Embed ObjectImpl
}
func (o *Immovable) immovable() {}

Note ImmovableImpl only adds immovable() method, object() is "inherited".

Building

Building implementation:

type Building struct {
    ImmovableImpl // Embed ImmovableImpl struct

    // Building-specific other fields may come here
}

Note Building does not add any new methods yet it is automatically an Immovable object.

The advantage of this technic grows greatly if the number of "subtypes" increases or if the interface types have more than just 1 "marker" method (because all methods are "inherited").

这篇关于Go 中创建复杂结构层次结构的惯用方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆