Achieving Polymorphism in Go

3,043 views
Skip to first unread message

codedread

unread,
Dec 8, 2009, 9:01:53 PM12/8/09
to golang-nuts
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

konrad

unread,
Dec 8, 2009, 10:02:51 PM12/8/09
to golang-nuts
This behavior is detailed in the spec and is the reason why Go is not
Object Oriented. You are not correct however is saying that it is not
Polymorphic. you are quite able to create an array of Fruit and have
different members of this array behave as Apples, Oranges etc.

What you can't do is provide a default implementation. So that every
specific fruit needs to provide an implementation of every method in
the Furit interface. As you noted there is no inheritance and you just
can't simulate inheritance by composition. Both are useful but they
are not the same thing.

To code in go you need to realise that you are not in Object Oriented
land from the begining. If you try to code as if you are in an Object
Oriented system you will end up with a suboptimal solution.

Other questions. A name that starts with a lower case letter will not
be exported from its package. So you can call the accessors
getCalories and setCalories and they will have package visability.

You could get the original interface by having a package private func
called eat

and then add stub methods to every implementation of Fruit that call
the function. As with the original problem you would need to do this
everywhere as a composed implementation will not do what you want.

i3dmaster

unread,
Dec 8, 2009, 10:28:10 PM12/8/09
to golang-nuts
I was thinking about this too. My understanding is that Go currently
does not support late binding mechanism, all method/object bindings
are done in compile time (early binding). Whatever object the methods
decl'ed against is *the object* the method will act on. The runtime
dose nothing about it. Originally I thought the anonymous field method
promotion is the way for rebind the methods but with a little
experimental code, I found out that its not doing what I was expected.
It merely just a pointer for runtime dispatching.

ziyu_huang

unread,
Dec 8, 2009, 10:31:23 PM12/8/09
to golang-nuts
Polymorphism is not a problem in Go. Go support it well.
I think you pick up the wrong title. Your polymorphism concept is too
tight to inheritance, that's the problem.
Code reuse is not only limit to inheritance. You can use Has-A
relation to reuse it.
Yes, it quite different to inheritance. But you can do the same thing.
It's not really mean to computer/hardware. Is only mean to how you
modeling it.
Since Go don't support Inheritance, I think Go has pretty much choose
to abandon inheritance. Don't try to emulate it, this will just burn
out your head.
Here is my example, quite close , no inherit involved. Code reuse ?
Yes. Polymorphism ? Yes. So why bother complex approach ?

package main

type Fruit struct {
name string;
}

func (f *Fruit) Name() string {
return f.name;
}

func (f *Fruit) Eat() {
println("Inside Eat(), Name is "+f.name);
}

type Apple struct {
Fruit;
}

func NewApple() *Apple{
a := new(Apple);
a.Fruit.name = "Apple";
return a;
}

type Banana struct {
Fruit;
}

func NewBanana() *Banana{
a := new(Banana);
a.Fruit.name = "Banana";
return a;
}

func main() {
a := NewApple();
a.Fruit.Eat();

b := NewBanana();
b.Fruit.Eat();

var f FruitIF;
f = a;
f.Eat();
f = b;
f.Eat();
}

type FruitIF interface {
Name() string;
Eat();

ziyu_huang

unread,
Dec 8, 2009, 10:46:20 PM12/8/09
to golang-nuts
Anonymous field promote the function inside the field. And with
interface you can make it works like inheritance.
Maybe you can give a code example why it don't works for you.
I my opinion, SuperClass implements SuperClassInterface, and SubClass
contains anonymous field of SuperClass then you can cast to
SuperClassInterface.
So for the others viewpoint SubClass support all function of
SuperClass. So it looks like inheritance. Although it's not quite the
same as *usual* inheritance.
But the actually is the inside is almost same as inheritance in C++
implementation. The difference is just how they dispatch method.

codedread

unread,
Dec 8, 2009, 11:08:11 PM12/8/09
to golang-nuts
ziyu_huang,

First, as you and konrad correctly point out - yes, I was confusing
polymorphism with inheritance.

Second, you will be surprised that you cannot cast from SubClass to
SuperClass in go - embedding is composition, meaning the address of
the 'inner' type is not the same as the address of the 'outer' type.
Casting will not really work.

Third, your solution has some errors (your structs must be composed of
anonymous pointers to the 'base' type), but even ignoring the errors,
the way you get around my problem by making 'name' a field on the base
struct. My contrived example is somewhat deceiving. Pretend that Name
() was more than just getting a property. - what if Name() really has
some real functionality that it must do. You don't provide
specialized functions for Apple/Banana, you only provide it at the
base struct (and all it does is return a field that is set by the
factory methods for the specialized structs).

Jeff
Message has been deleted

ziyu_huang

unread,
Dec 8, 2009, 11:48:19 PM12/8/09
to golang-nuts
You don't need to cast to SuperClass type, you cast to
SuperClassInterface
My suggest is still the same. Don't try to emulate inheritance in Go.
Try to change modeling approach.
As I said, my example is not exact the same as yours , but it do
archive "code reuse" you mentioned.
You have many way to solve problems. You really don't have to choose
inheritance. IMO.
> ...
>
> 閱讀更多 »

Jessta

unread,
Dec 9, 2009, 12:12:52 AM12/9/09
to codedread, golang-nuts
2009/12/9 codedread <code...@gmail.com>:
>  My contrived example is somewhat deceiving.  Pretend that Name
> () was more than just getting a property. - what if Name() really has
> some real functionality that it must do.

You should really stop trying to do inheritance, you're trying to
solve the wrong problem.
You should look at your actually problem and then create a solution to
that problem using the Go conventions.

On IRC I think you mentioned you were making a DOM library.
You could start by specifying an interface for DOM elements and define
the data structures.
Any shared functionality, (functions take an interface type) should be
in separate functions not connected to any data type.
You can then call these shared functions from your data type methods,
so your methods are really simple and mostly just call the shared
functions as well as doing data type specific things.
Most functionality should be tied to data structures, most of it
should be acting on an interface that way it's much more futureproof.

Have a look at how the standard libraries do this.

--
=====================
http://jessta.id.au

Jeff Schiller

unread,
Dec 9, 2009, 12:28:39 AM12/9/09
to Jessta, golang-nuts
Thanks - I think your advice is sound and will take it into
consideration. The only issues left for me to sort out is the
fidelity to the DOM API and just having to deal with a lot of
duplicated (albeit simple) functions.

Jeff

Jessta

unread,
Dec 9, 2009, 12:45:39 AM12/9/09
to Jeff Schiller, golang-nuts
2009/12/9 Jeff Schiller <code...@gmail.com>:
> Thanks - I think your advice is sound and will take it into
> consideration.  The only issues left for me to sort out is the
> fidelity to the DOM API and just having to deal with a lot of
> duplicated (albeit simple) functions.
>
You'll be happy for those 'duplicate' methods later when you want to
change your data structures in ways that don't make sense in a
hierarchy.

--
=====================
http://jessta.id.au

i3dmaster

unread,
Dec 9, 2009, 1:38:13 AM12/9/09
to golang-nuts
I happened to discover that by this following code:

type Foo interface {
Get() int;
Set(int);
}

type Bar interface {
Foo;
Add(int);
}

type MyFoo struct{
a int;
}

func (f *MyFoo) Get() int {
return f.a;
}

func (f *MyFoo) Set(i int) {
f.a = i;
}

// By promoting fields/methods from MyFoo, YourFoo objects
// have "a", and "Get", "Set" methods, which is really cool.
// It looked like I just did class YourFoo extends MyFoo {}
// But this is only when the subtype does not have its own
// internal state.
type YourFoo struct {
*MyFoo;
}

// Then I added a bit stuff to see how it behaves.
type BarFo struct {
// Note, I added a local field with the exact same name of its
// "inherited" type.
a int;
*MyFoo;
}

func (b *BarFo) Add(i int) {
b.a += i;
}

// tests
b := BarFo{a: 1, MyFoo: &MyFoo{2}};
b.Add(10);
b.Set(2); // Give me the wrong answer. Turns out, b.a is still 10,
and b.MyFoo.a becomes 2.

There are actually two things I've discovered:
1. Field conflict. Better not use the same name. Looks like when that
happens, the conflicted field wouldn't get promoted.
2. Promoted fields/methods are actually just pointers. b.Set(2)'s
receiver is not b, but b.MyFoo since I didn't write b's Set()
implementation, there is no runtime late binding. After understanding
the whole thing, fixing the code is trivial...

atomly

unread,
Dec 9, 2009, 1:58:54 AM12/9/09
to Jeff Schiller, Jessta, golang-nuts
On Wed, Dec 9, 2009 at 12:28 AM, Jeff Schiller <code...@gmail.com> wrote:
> Thanks - I think your advice is sound and will take it into
> consideration.  The only issues left for me to sort out is the
> fidelity to the DOM API and just having to deal with a lot of
> duplicated (albeit simple) functions.

You'll only have duplicated methods if you continue to do things in an
OO type way.

Instead of having:

func (parent Node) addChild(child Node) boolean {}

you have the following:

func addChild(parent Node, child Node) boolean {}

that should work, shouldn't it?

--
:: atomly ::

[ ato...@atomly.com : www.atomly.com : http://blog.atomly.com/ ...
[ atomiq records : new york city : +1.917.442.9450 ...
[ e-mail atomly-new...@atomly.com for atomly info and updates ...

Jeff Schiller

unread,
Dec 9, 2009, 2:14:49 AM12/9/09
to atomly, Jessta, golang-nuts
At some point, we have to be adding that child to a
vector/container/etc. This means that it has to be in a struct (not
an interface).

So far this is what I've envisioned:

type Node interface {
// public DOM API methods
ParentNode() Node;
AppendChild(Node);

// internal methods
_setParent(Node);
_insertChildAt(int, Node);
_numChildren() int;
}

type NodeImpl struct {
p Node;
c vector.Vector;
}
func (n *NodeImpl) ParentNode() Node { return p; }
func (n *NodeImpl) AppendChild(c Node) { appendChild(n,c);}
func (n *NodeImpl) _setParent(p Node) { n.p = p; }
func (n *NodeImpl) _insertChildAt(i int, c Node) { n.c.Insert(i,c); }
func (n *NodeImpl) _numChildren() int { return n.c.Len(); }

// package-level function that operates on interfaces only
func appendChild(p Node, c Node) {
if (c.ParentNode() != nil) {
removeChild(c.ParentNode(), c);
}
p._insertChild(p._numChildren(), c);
c._setParent(p);
}

Does that look sane?

Jeff

Russ Cox

unread,
Dec 9, 2009, 2:19:04 AM12/9/09
to Jeff Schiller, golang-nuts
On Tue, Dec 8, 2009 at 23:14, Jeff Schiller <code...@gmail.com> wrote:
> At some point, we have to be adding that child to a
> vector/container/etc.  This means that it has to be in a struct (not
> an interface).

I don't follow. There is no reason you can't have a
vector of interface values. (In fact, all vectors are
vectors of interface values.)

Russ

Ben Tilly

unread,
Dec 9, 2009, 2:23:13 AM12/9/09
to atomly, Jeff Schiller, Jessta, golang-nuts
On Tue, Dec 8, 2009 at 10:58 PM, atomly <ato...@atomly.com> wrote:
> On Wed, Dec 9, 2009 at 12:28 AM, Jeff Schiller <code...@gmail.com> wrote:
>> Thanks - I think your advice is sound and will take it into
>> consideration.  The only issues left for me to sort out is the
>> fidelity to the DOM API and just having to deal with a lot of
>> duplicated (albeit simple) functions.
>
> You'll only have duplicated methods if you continue to do things in an
> OO type way.
>
> Instead of having:
>
> func (parent Node) addChild(child Node) boolean {}
>
> you have the following:
>
> func addChild(parent Node, child Node) boolean {}
>
> that should work, shouldn't it?

If you switch from a method to a function, you should move the method
type into the function name. So

func (parent Node) addChild(child Node) boolean {}

becomes

func addChildNode(parent Node, child Node) boolean {}

I advocate that because method names are often fairly generic so that
they will make sense for multiple types. But you can't have 2
functions with the same name and different type signatures. Therefore
to avoid collisions you should put the type in the function name.

Cheers,
Ben

Jeff Schiller

unread,
Dec 9, 2009, 2:24:17 AM12/9/09
to r...@golang.org, golang-nuts
I'm saying that the vector itself has to be a field in a struct. What
operates on that field?

Answer: some function of the struct.

Ben Tilly

unread,
Dec 9, 2009, 2:33:16 AM12/9/09
to Jeff Schiller, r...@golang.org, golang-nuts
Request, please don't top post, it makes the conversation hard to
follow. I've fixed your top posting.

On Tue, Dec 8, 2009 at 11:24 PM, Jeff Schiller <code...@gmail.com> wrote:
> On Wed, Dec 9, 2009 at 1:19 AM, Russ Cox <r...@golang.org> wrote:
>> On Tue, Dec 8, 2009 at 23:14, Jeff Schiller <code...@gmail.com> wrote:
>>> At some point, we have to be adding that child to a
>>> vector/container/etc.  This means that it has to be in a struct (not
>>> an interface).
>>
>> I don't follow.  There is no reason you can't have a
>> vector of interface values.  (In fact, all vectors are
>> vectors of interface values.)
>
> I'm saying that the vector itself has to be a field in a struct. What
> operates on that field?
>
> Answer: some function of the struct.

If something (variable, element of struct, etc) is declared to be of
type interface then you can call any method named in the interface
definition on that thing. The actual method to call will be figured
out at runtime, but that it can be called is guaranteed by the fact
that nothing can go into that slot unless it satisfies the interface.

Cheers,
Ben

Jeff Schiller

unread,
Dec 9, 2009, 2:44:09 AM12/9/09
to Ben Tilly, r...@golang.org, golang-nuts
Ben,

I'm not sure why you're explaining this part to me. I already
understood it, so I'm clearly missing the relevance. :)

atomly suggested:
>
> Instead of having:
>
> func (parent Node) addChild(child Node) boolean {}
>
> you have the following:
>
> func addChild(parent Node, child Node) boolean {}
>
>that should work, shouldn't it?

I can definitely do this. I can make a function that acts on two
interface instances (Node). However, at some point this function must
add the child node to the parent in some way. The Node instances will
be backed up by struct instances that realize the Node interface but
internally store children in a vector or whatever. I'm just saying
that at some point, addChild() will have to call back to a method
defined on the Node interface but implemented in a struct that
realizes the Node interface.

See my "So far this is what I've envisioned" post.

Regards,
Jeff

P.S. I blame GMail for making it too easy to top-post :)

ziyu_huang

unread,
Dec 9, 2009, 3:11:46 AM12/9/09
to golang-nuts
Hi Jeff,

I feel you are still stuck on OO/Inheritance. I suggest write more Go
style code instead of struggling how to implements in C++/Java style .
I am not good at DOM and never write a DOM API, but I just try to re-
write it in Go style
=========== here is my example ======================
package main

import "container/vector"

type NodeIF interface {
ParentNode() NodeIF;
AppendChild(n NodeIF);
String() string;
}

type Node struct {
p NodeIF;
c vector.Vector;
}

func (n *Node) ParentNode() NodeIF { return n.p }
func (n *Node) AppendChild(child NodeIF) { n.c.Push(child) }
func (n *Node) String() string { return "Node"}
func (n *Document) String() string { return "Document"}
func (n *Text) String() string { return "Text"}
func (n *Entity) String() string { return "Entity"}
func (n *Node) ForEachChild(callback func(n NodeIF)) {
for i:=0; i < n.c.Len(); i++ {
callback(n.c.At(i).(NodeIF));
}
}

type Document struct {
Node;
}

type Text struct {
Node;
}

type Entity struct {
Node;
}

func main() {
var root Document;
root.AppendChild(new(Text));
root.AppendChild(new(Entity));
root.AppendChild(new(Text));
println("print all children");
root.ForEachChild(func(n NodeIF) {
println(n.String());
});
> > On Wed, Dec 9, 2009 at 12:28 AM, Jeff Schiller <codedr...@gmail.com> wrote:
> >> Thanks - I think your advice is sound and will take it into
> >> consideration.  The only issues left for me to sort out is the
> >> fidelity to the DOM API and just having to deal with a lot of
> >> duplicated (albeit simple) functions.
>
> > You'll only have duplicated methods if you continue to do things in an
> > OO type way.
>
> > Instead of having:
>
> > func (parent Node) addChild(child Node) boolean {}
>
> > you have the following:
>
> > func addChild(parent Node, child Node) boolean {}
>
> > that should work, shouldn't it?
>
> > --
> > :: atomly ::
>
> > [ ato...@atomly.com :www.atomly.com :http://blog.atomly.com/...
> > [ atomiq records : new york city : +1.917.442.9450 ...
> > [ e-mail atomly-news-subscr...@atomly.com for atomly info and updates ...

Ben Tilly

unread,
Dec 9, 2009, 3:12:37 AM12/9/09
to Jeff Schiller, r...@golang.org, golang-nuts
On Tue, Dec 8, 2009 at 11:44 PM, Jeff Schiller <code...@gmail.com> wrote:
>> On Tue, Dec 8, 2009 at 11:24 PM, Jeff Schiller <code...@gmail.com> wrote:
>>> On Wed, Dec 9, 2009 at 1:19 AM, Russ Cox <r...@golang.org> wrote:
>>>> On Tue, Dec 8, 2009 at 23:14, Jeff Schiller <code...@gmail.com> wrote:
>>>>> At some point, we have to be adding that child to a
>>>>> vector/container/etc.  This means that it has to be in a struct (not
>>>>> an interface).
>>>>
>>>> I don't follow.  There is no reason you can't have a
>>>> vector of interface values.  (In fact, all vectors are
>>>> vectors of interface values.)
>>>
>>> I'm saying that the vector itself has to be a field in a struct.  What
>>> operates on that field?
>>>
>>> Answer: some function of the struct.
>>
>> If something (variable, element of struct, etc) is declared to be of
>> type interface then you can call any method named in the interface
>> definition on that thing.  The actual method to call will be figured
>> out at runtime, but that it can be called is guaranteed by the fact
>> that nothing can go into that slot unless it satisfies the interface.
>
> Ben,
>
> I'm not sure why you're explaining this part to me.  I already
> understood it, so I'm clearly missing the relevance. :)

I first tried to figure out what you could be saying, but wound up as
confused as Russ was about your point. I then tried to guess what
misunderstanding could have lead to the statements that puzzled Russ,
and then I tried to address that. I obviously guessed wrong. I'll
try another guess.

> atomly suggested:
>>
>> Instead of having:
>>
>> func (parent Node) addChild(child Node) boolean {}
>>
>> you have the following:
>>
>> func addChild(parent Node, child Node) boolean {}
>>
>>that should work, shouldn't it?
>
> I can definitely do this. I can make a function that acts on two
> interface instances (Node).  However, at some point this function must
> add the child node to the parent in some way.  The Node instances will
> be backed up by struct instances that realize the Node interface but
> internally store children in a vector or whatever.  I'm just saying
> that at some point, addChild() will have to call back to a method
> defined on the Node interface but implemented in a struct that
> realizes the Node interface.

When that time comes you can cast the objects to the right type like so:

func (parent Foo) append (child Node) boolean {
childFoo, ok := child.(Foo);
if ok {
// Implement the appending knowing that both things
have type Foo.
}
return ok;
}

If this method is passed a child of type Foo then ok will be true. If
it is passed a child that is not of type Foo, then ok is false and you
can try something else.

Does this help bridge the gap between abstract interfaces and concrete
implementations of types that satisfy those interfaces?

> See my "So far this is what I've envisioned" post.

I did, but what I saw there didn't clarify what you've envisioned
*not* being able to do.

> Regards,
> Jeff
>
> P.S. I blame GMail for making it too easy to top-post :)

I blame Outlook for encouraging top posting by making it unnecessarily
hard to interweave message and reply in time honored fashion. Gmail
makes it easy to use email in the time honored way, and defaults to
the classic Usenet quoting style.

Cheers,
Ben

Jeff Schiller

unread,
Dec 9, 2009, 3:22:07 AM12/9/09
to Ben Tilly, r...@golang.org, golang-nuts
On Wed, Dec 9, 2009 at 2:12 AM, Ben Tilly <bti...@gmail.com> wrote:
>
>> See my "So far this is what I've envisioned" post.
>
> I did, but what I saw there didn't clarify what you've envisioned
> *not* being able to do.

Actually I didn't have any clarifications required at that point. I
had agreed with Jessta that the best approach would be package methods
that act on interfaces (and my post was about what I intended to go
write).

I was just explaining to atomly why a package method that acts on
interfaces was not enough on its own, in the end I'll wind up in
methods implementing interface methods :)

Sorry for the confusion!

Jeff

Jeff Schiller

unread,
Dec 9, 2009, 4:03:31 AM12/9/09
to golang-nuts
Ok, I finished refactoring godom tonight, it works (all tests pass),
though still implements a very very tiny subset at the moment.

I'm sorry for not being clear before. The 'duplicated methods' I was
talking about are the one-line AppendChild() methods in the three
structs _doc, _elem, _node:

http://code.google.com/p/godom/source/browse/document.go?spec=svndabdc4430551f0ef23ce7b0896c73539ccc8de47&r=dabdc4430551f0ef23ce7b0896c73539ccc8de47
http://code.google.com/p/godom/source/browse/element.go?spec=svndabdc4430551f0ef23ce7b0896c73539ccc8de47&r=dabdc4430551f0ef23ce7b0896c73539ccc8de47
http://code.google.com/p/godom/source/browse/node.go?spec=svndabdc4430551f0ef23ce7b0896c73539ccc8de47&r=dabdc4430551f0ef23ce7b0896c73539ccc8de47

Basically these methods have the exactly same code but are required to
ensure that the receiver is the specialized type (_doc, _elem) and not
the 'base' type (_node). If the method did not exist in _doc or
_elem, then the parents would not be set properly on the parent in the
package-level function that actually does the work:

http://code.google.com/p/godom/source/browse/dom.go?spec=svndabdc4430551f0ef23ce7b0896c73539ccc8de47&r=dabdc4430551f0ef23ce7b0896c73539ccc8de47#35

(we would end up setting the parent of the children to *_node instead
of their actual type)

That's how I understand it anyway, but I could be confused... :)

Jeff
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted
Message has been deleted

steven099

unread,
Dec 9, 2009, 2:46:35 PM12/9/09
to golang-nuts
On Dec 9, 4:03 am, Jeff Schiller <codedr...@gmail.com> wrote:
> Ok, I finished refactoring godom tonight, it works (all tests pass),
> though still implements a very very tiny subset at the moment.
>
> I'm sorry for not being clear before.  The 'duplicated methods' I was
> talking about are the one-line AppendChild() methods in the three
> structs _doc, _elem, _node:
>
> http://code.google.com/p/godom/source/browse/document.go?spec=svndabd...http://code.google.com/p/godom/source/browse/element.go?spec=svndabdc...http://code.google.com/p/godom/source/browse/node.go?spec=svndabdc443...
>
> Basically these methods have the exactly same code but are required to
> ensure that the receiver is the specialized type (_doc, _elem) and not
> the 'base' type (_node).  If the method did not exist in _doc or
> _elem, then the parents would not be set properly on the parent in the
> package-level function that actually does the work:
>
> http://code.google.com/p/godom/source/browse/dom.go?spec=svndabdc4430...
>
> (we would end up setting the parent of the children to *_node instead
> of their actual type)
>
> That's how I understand it anyway, but I could be confused... :)
>
> Jeff

Seems to work for now, though, if you started getting a lot of those
kinds of methods, it would get tedious. One thing you could do is just
to expose the AppendChild(Node, Node) method, though this would tie
you in to this implementation rather than letting you at some point
specialize the in individual appendNode methods. Then you have
AppendChild(Node, Node) just forwarding. blech!

Alternatively, you could make an interface partNode such that type
Node interface { partNode; AppendChild(Node); ... }, then have a
type nodeImpl struct { *partNode; } that implements AppendChild(Node).
This can work with any shared methods that you want acting on the
types themselves and not their embedded types.

Another thing you could do is add default implementations to _node
that act on a self Node (all your Nodes are pointers to structs). So,
if the method call ever falls through
that far, you get the default behaviour, but still acting on your
actual data type.

So there's all kinds of ways you can structure your code to avoid
method forwarding :)

steven099

unread,
Dec 9, 2009, 8:04:30 PM12/9/09
to golang-nuts
On Dec 9, 4:03 am, Jeff Schiller <codedr...@gmail.com> wrote:
> Ok, I finished refactoring godom tonight, it works (all tests pass),
> though still implements a very very tiny subset at the moment.
>
> I'm sorry for not being clear before.  The 'duplicated methods' I was
> talking about are the one-line AppendChild() methods in the three
> structs _doc, _elem, _node:
>
> http://code.google.com/p/godom/source/browse/document.go?spec=svndabd...http://code.google.com/p/godom/source/browse/element.go?spec=svndabdc...http://code.google.com/p/godom/source/browse/node.go?spec=svndabdc443...
>
> Basically these methods have the exactly same code but are required to
> ensure that the receiver is the specialized type (_doc, _elem) and not
> the 'base' type (_node).  If the method did not exist in _doc or
> _elem, then the parents would not be set properly on the parent in the
> package-level function that actually does the work:
>
> http://code.google.com/p/godom/source/browse/dom.go?spec=svndabdc4430...
>
> (we would end up setting the parent of the children to *_node instead
> of their actual type)
>
> That's how I understand it anyway, but I could be confused... :)
>
> Jeff

Question: do you intend for others to be able to make Nodes? The fact
that you're exporting the interface implies that you do, but the fact
that you are calling unexported methods on it means that you can't
since your package won't have access to their implementations of the
methods...
Message has been deleted

c9s

unread,
Apr 11, 2013, 10:13:07 AM4/11/13
to golan...@googlegroups.com, code...@gmail.com
I use this way to achieve the late binding:


Which is not so perfect, but a way to resolve this kind of problem.

atomly

unread,
Apr 11, 2013, 11:52:44 AM4/11/13
to c9s, golang-nuts, Jeff Schiller
Couldn't that much more shortly be:


[ atomiq records : new york city : +1.347.692.8661 ...
[ e-mail atomly-new...@atomly.com for atomly info and updates ...


On Thu, Apr 11, 2013 at 10:09 AM, c9s <corneli...@gmail.com> wrote:

I use this way to archive the late binding:


Which is not so perfect, but a way to archive this.

codedread於 2009年12月9日星期三UTC+8上午10時01分53秒寫道:

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Jeremy Wall

unread,
Apr 11, 2013, 12:05:54 PM4/11/13
to c9s, golang-nuts, code...@gmail.com
Yes I would disagree:

type Node interface {
}

type node struct {}

// private type node implements Node interface here.

type Element struct {
   node // embed a node so that Element satisfies the Node interface.
   // Anything extra we need
}

// Now Element can override any node methods it needs to and add any extra methods it needs.

Robert Johnstone

unread,
Apr 11, 2013, 4:41:05 PM4/11/13
to golan...@googlegroups.com, code...@gmail.com
I can't provide a full solution, but hopefully I can at least ease some of your pain.  You can do "inheritance" on interfaces (http://play.golang.org/p/fUQxxdOnUg).  There is no implementation, so it isn't really inheritance, but you can at least derive interfaces that extend the method set.  The effect is the same as one abstract base class inheriting from another.

For inheritance, the best approach that I can suggest is to use embedding.  You then override any methods as required.  I believe that this has been suggested elsewhere.  The major weakness of this approach is the base classes cannot delegate through virtual functions.  You'll need to change the code organization so that you provide separate policy implementations instead of using derived classes if that is required.  This advice is probably not very helpful, but I believe that it is the closest you will achieve. 

(I get the impression you have a strong C++ background.  If that is the case, I suggest you look at patterns used in C++ templates for inspiration.  As much as Go lovers love to hate C++, the patterns of collaboration can show surprising similarities with template metaprogramming.)

Kyle Lemons

unread,
May 8, 2013, 4:45:14 PM5/8/13
to tobia.c...@gmail.com, golang-nuts, code...@gmail.com
On Tue, May 7, 2013 at 3:23 PM, <tobia.c...@gmail.com> wrote:
Hi everybody. Let me add my 2 cents.

What Jeremy suggests is part of a more general design pattern I have come up with myself in the past few days, while bending my head around Go's type system ;-) That is, using public interface inheritance in parallel with private struct embedding:

type A interface {...}
type a struct {...}

type B interface {A; ...}
type b struct {a; ...}

I try to discourage people from creating their interfaces before they create their types, at least until you're more familiar with the language.  In Go, the model can be understood to be that clients define interfaces for what behaviors they need, libraries provide concrete types.  In reality, it is often more a mix of the two, but always creating interfaces for your type is probably not going to be what you want.
 
The main problem (or rather *feature*, as I will explain) that differentiates this approach from traditional class-based languages is, as Robert points out, that "the base classes cannot delegate through virtual functions."

If you have method a.PrintName() calling a.GetName(), both required by A and defined for a, and you only override b.GetName() for b, then a.PrintName() will still call a.GetName(), even on b objects. In other words, all Go methods are non-virtual.

Correct me if I'm wrong, but the only "virtual calls" (or late binding) available in Go are method calls done on interface variables. What I mean by "virtual call" is a function call for which the language will automatically use the most specific implementation of that function name it has available, depending on the dynamic type at hand.

Therefore, if you want to have a PrintName function that automatically calls the most specific GetName() available for that type, it follows that PrintName must operate on an interface variable. Therefore it cannot be a method, it must be a package function.

What I gather from this is that you can only have one level of late binding in Go (short of type casting and other manual bending of the rules) and this splits all Go code (that will ever operate on one particural type hierarchy) into two realms at war, with a one-way DMZ in between. You can travel from the West to the East, but once you're there, you stay there.

The East is the realm of methods, calling each other on the same type (aka. non-virtually) and overriding (or decorating, or delegating to) those inherited from embedded types. It's a strictly top-down, hierarchical society. The methods at the bottom (of inner, embedded types) cannot call those at the upper layers (defined on outer, embedding, "subclassing" types.)

On the other side of the wall you have the West, or the realm of package functions (or methods of a different receiver) that accept and operate on generic interface arguments. When they call methods on their arguments or variables, they will always see the latest (most specific) implementation of any given method name, but won't be able to access the type internals directly (short of type casting.) This is a thriving, dynamic, and interconnected world, albeit somewhat more expensive to live in.

The one-way DMZ between the two realms is the interface definition.

I've only been studying Go for a few days (so feel free to flame away) but as far as I can see, the most important aspect to keep in mind while designing one's type hierarchies is that once a given code path crosses the wall, from the realm of interfaces into that of method receivers, it's stuck there. It cannot go back to seeing the bigger picture. Not in a clean way, at least.

This is probably the most important difference between Go and object-oriented languages, where virtual (or late-binding) methods can go on an endless dance of calling each other, from top to bottom and from bottom to top of the type hierarchy (sideways too, in languages with multiple inheritance.)

But this is also a great way to *force* us to design our interfaces right and split them from the implementation details, with probably a great performance boost as a bonus.

It's a bit mind-bending, but I like the challenge so far.


-Tobia

This post is a work of fiction. Any resemblance to events or real world locations, current or historical, is purely coincidental. :-)

Nathan Youngman

unread,
May 8, 2013, 10:36:11 PM5/8/13
to golan...@googlegroups.com, code...@gmail.com
Hi Jeff,

I have a brief example of polymorphism with interfaces in this talk https://vimeo.com/65704694 and more on embedding and OO design in this article: http://nathany.com/good. Also see Rob Pike's article http://talks.golang.org/2012/splash.article#TOC_15. from his Go at Google talk: http://www.infoq.com/presentations/Go-Google. I hope this helps.

Nathan.


On Tuesday, 8 December 2009 19:01:53 UTC-7, codedread wrote:
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.

Peter Caven

unread,
May 10, 2013, 4:18:31 AM5/10/13
to golan...@googlegroups.com, code...@gmail.com
Thanks for pointers to your talk and slides!

I started with your fishes example and expanded it to use a channel of interface type.  
I acheived a level of polymorphism that I didn't expect! 

I discovered that it's possible to "downcast" from a base interface to another interface (implemented by a struct type) by using a type assertion.

This was something that I didn't expect at all and I now have a new appreciation for the power in Go.


-- Peter
Reply all
Reply to author
Forward
0 new messages