This is a followup to the final section of A Rubyist has Some Difficulties with Go called “Limits to Polymorphism and Method Dispatch in Go”.
I believe I have developed an understanding for what functionality here Go is providing. I’ve written another little test program, executable version is here and the gist:
What does the use of A in the following definition mean?
type A struct{}
type B struct {
A
}
It’s described in various ways, such as “struct A is embedded in B” or “A is an anonymous member of B”, as an example of “composition rather than inheritance”. Not having a useful mental model of what it means has obviously been the source of my confusion (even I knew that much :-)
The turning point for me was the realisation that the following two lines are essentially identical:
v.SomeAMethod()
v.A.SomeAMethod()
Since they are essentially the same, then it shouldn’t be any surprise when the implementation of SomeAMethod doesn’t know anything about v. Without know that they are the same, I was thinking that the receiver of had to be v and since it wasn’t was at a bit of a loss. That what appears to OO-centric me as a loss of information about the receiver of the method call is really a misunderstanding, by me, of what the receiver actually is. It’s not v, it’s v.A.
So it seems that v.SomeAMethod() is a syntactic convenience provided by Go for the programmer. And maybe a little more.
So this leaves polymorphism in Go as pretty much entirely provided by interfaces. Interfaces consist of a set of named methods. It is not possible to explicitly state that a type satisfies an interface, the compiler works that out. The convenience is extended a little by applying the syntactic trick of dropping the member name for anonymous members when deciding what methods are implemented by the type. And so SomeAMethod is a method offered by B because it’s offered by A.
This kind of thing is often referred to as delegation. In this case, B delegates the SomeAMethod to it’s anonymous A. The trick with delegation is what is the receiver (i.e. self) in the method. In Go the receiver is the anonymous A instance of the B instance v. If, instead, the receiver was v, then we’d have the delegation found in programming languages like self and even JavaScript. And that kind of delegation is equivalent to inheritance. The important difference is where methods called within the delegated method are looked for.
[...] I’ve posted a followup Followup to A Rubyist has Some Difficulties with Go: Limits to Polymorphism and Method Dispatch in G… that addresses my current thoughts about the last point in this [...]
I think the Go way of doing this makes sense. A lot of languages provide ways to do code reuse through inheritance, but the rules for it always end up incredibly complicated and confusing. Then if it doesn’t do exactly what you want, you end up having to rewrite things yourself. Compare that to Go. It gives you two options: embed it (easy case) or do it yourself (everything else). You could have something like this:
type Doer interface {Do()
}
type Base struct {}
func (b Base) Do() {
fmt.Println("Base did.")
}
type SubSimple struct {
Base
}
type SubComplex struct {
Base
}
func (s SubComplex) Do() {
fmt.Println("Preparing for Base doing.")
s.Base.Do()
fmt.Println("Tear down after Base did.")
}
func main() {
var doers []Doer = []Doer{*new(Base), *new(SubSimple), *new(SubComplex) }
for _, doer := range doers {
doer.Do()
}
}
When I tested it, the result was what you would expect:
Base did.
Base did.
Preparing for Base doing.
Base did.
Tear down after Base did.
Nice and simple.