我在Go中编写解释器,并且正在寻找惯用的方式来存储AST。我阅读了Go编译器的源代码,似乎他们使用了带有空方法的接口来表示AST。例如,我们具有以下层次结构,
Object --Immovable ----Building ----Mountain --Movable ----Car ----Bike
这就是上述层次结构以“空方法”方式实现的方式。
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() {}
上面的代码是一个人为的示例,这就是Go编译器如何使用许多空方法来实现 AST的方式。但为什么?注意定义了多少个空方法。随着层次结构深度的增加,它可能会变得非常复杂。
注释中指出,空方法禁止分配不兼容的类型。在我们的示例中,*Car不能将a分配给*Immovable例如。
*Car
*Immovable
在其他支持继承的语言(例如C ++)中,这是如此容易。我想不出代表AST的任何其他方式。
Go编译器AST的实现方式可能是惯用的,但不是那么简单吗?
Go 不是(相当)一种面向对象的语言:它没有类,也没有类型继承。但它支持类似的构造,称为在层和层上 嵌入 ,并且确实具有方法。struct``interface
struct``interface
Go中的接口只是固定的方法集。如果类型的方法集是该接口的超集(没有意图的声明),则该类型 隐式 实现接口。
如果要 明确 记录 或 声明 您的类型确实实现了接口(因为未明确声明),则空方法非常有用。官方Go常见问题解答:如何保证我的类型满足界面要求?
type Fooer interface { Foo() ImplementsFooer() }
如果你想在你的类型层次区分(例如,你不想让一个对象既Movable和Immovable),它们必须有不同的方法集(必须有至少1方法在每个方法集的Movable和Immovable是不存在于另一个Movable变量中),因为如果方法集将包含相同的方法,那么一个方法的实现也会自动实现另一个方法,因此您可以将一个对象分配给type变量Immovable。
Movable
Immovable
假设您不会将此类方法添加到其他类型,则向具有相同名称的接口添加一个空方法将为您提供这种区别。
就个人而言,我对空方法没有任何问题。有一种减少它们的方法。
如果您还为层次结构中的每种类型创建一个struct 实现 ,并且每个实现 将 实现 嵌入 到struct更高一级,则更高级别的方法集将自动出现,而无需费心:
struct
Object接口和ObjectImpl实现:
Object
ObjectImpl
type Object interface { object() } type ObjectImpl struct {} func (o *ObjectImpl) object() {}
Immovable接口和ImmovableImpl实现:
ImmovableImpl
type Immovable interface { Object immovable() } type ImmovableImpl struct { ObjectImpl // Embed ObjectImpl } func (o *Immovable) immovable() {}
注意ImmovableImpl仅添加immovable()方法,object()是“继承”的。
immovable()
object()
Building 实施:
Building
type Building struct { ImmovableImpl // Embed ImmovableImpl struct // Building-specific other fields may come here }
注意Building 不会添加 任何新方法,但它自动是一个Immovable对象。
如果“子类型”的数量增加或接口类型具有不止一种“标记”方法(因为“继承”了所有方法),则该技术的优势将大大增加。