As instructed on Issue 391, I've brought this to the discussion list.
Note that I brought this to the #go-nuts channel yesterday and had a
fruitful discussion which ended with deciding that there is a solution
to this particular problem as continued below. But I still have some
issues.
=== ORIGINAL PROBLEM AS STATED IN THE ISSUE ===
Because Go lacks a means of inheritance/subclassing, it seems it is
impossible to achieve runtime polymorphism.
For instance, see the below code:
package main
import "fmt";
type Fruit interface {
Name() string;
Eat() int;
}
type FruitImpl struct {
calories int;
}
func (f *FruitImpl) Name() string { return "Fruit"; }
func (f *FruitImpl) Eat() int {
fmt.Println("Inside Eat(), Name is", f.Name(), "calories=",
f.calories);
c := f.calories;
f.calories = 0;
return c;
}
type Apple struct { *FruitImpl; }
func (a *Apple) Name() string { return "Apple"; }
type Banana struct { *FruitImpl; }
func (b *Banana) Name() string { return "Banana"; }
func main() {
a := &Apple { &FruitImpl{100} };
b := &Banana { &FruitImpl{200} };
fmt.Println("Apple Name is", a.Name());
fmt.Println("Banana Name is", b.Name());
fruits := [2]Fruit{a,b};
for i := 0; i < 2; i++ {
fruits[i].Eat();
}
}
In the above we have an interface (Fruit) and a base implementation
(FruitImpl). We then have specialized struct types (Apple, Banana)
that
embed FruitImpl but specialize the Name() method. Because the
receiver on
Eat() is always the inner type (FruitImpl) we do not get the expected
value
for Name().
Inheritance promotes code reuse. Without it, I have to provide the
full
implementation of FruitImpl in each of my structs (i.e. two copies of
the
Eat() function). In real problems, this could be a lot of code.
The other solution is to store specialized type information at the
FruitImpl level and have a series of switch statements in each method
to
check and execute the desired specialized behavior. This essentially
acts
like a bit of a vtable.
Does this mean that problems where inheritance / runtime polymorphism
are a
natural fit are not good for Go?
Is there a better way to achieve the desired result in Go? It's
probably
clear that I am not fully versed in all of Go's idioms.
=== END ORIGINAL PROBLEM ===
The optimal solution as it was proposed in IRC yesterday was:
- provide accessors to the 'calories' field in FruitImpl (i.e. Calories
() int, SetCalories(int))
- remove the Eat() method from the interface
- make the method _act_ on a Fruit :
func Eat(f Fruit) {
// call f.Calories(), f.SetCalories(), f.Name(), etc
}
I'll note that this solution does achieve the objectives of:
- achieving traditional specializing/overloading subclass behavior
(via the Banana and Apple types)
- achieving code reuse by taking the function out of the traditional
"base" class and moving it to a package-level function that acts on
types of the Fruit interface.
However, the solution does have a couple of "nits" :)
1) I've exposed the 'calories' property by providing accessors. Does
this mean they are exposed to people using the package? What's the
proper way to "package scope"?
2) The above has taken "Eat()" out of the interface and imposed a
different requirement that the "calories" property must have
accessors. Though this is a contrived example, it does demonstrate
that the Go language has forced me to change my desired API. Now
someone has a way to double the calories of any fruit :)
3) In some situations, I might not even have the luxury of changing
the API. For instance, these problems described in this email have
arisen as a result of working on the godom library:
http://godom.googlecode.com/
which seeks to build upon the XML parser and provide an implementation
of the DOM for Go.
The DOM itself is actually an API that uses a concept of interfaces
but these interfaces can inherit from other interfaces. i.e. an
Element _is_ a Node. As far as I can tell, without polymorphism,
there is no solid way to model this API in Go - would someone
disagree?
What we've done for now is "flatten" the API so that the Node type has
_all_ the methods of its subtypes (Element, Document, Text). While
this achieves the functionality of the DOM methods/properties we have
implemented, it leaves me with a bad taste in my mouth because someone
can call GetAttribute() on a Text node (for instance), which means
we've changed the API more than I'd like.
The benefit of providing a DOM library in a language is for users to
quickly pick it up and use it (being familiar with the DOM in other
context like web browsers, Python, PHP, etc).
The other option is to take the above technique that solves the fruit
problem and make all methods like so:
AppendChild(subject Node);
SetAttribute(subject Node, attrName string, attrValue string);
Where the first argument is always the type being acted upon. But
this also means that we've changed the API more than I'd like.
Sorry for the long email!
Regards,
Jeff Schiller