What about base members?

18 views
Skip to first unread message

Jon

unread,
Nov 11, 2009, 11:36:20 PM11/11/09
to golang-nuts
Inheritance is not just about polymorphism. Inheritance is also about
cleanly handling similar-but-not identical, like when two classes
share everything except one or two functions. They can both descend
from an abstract base class that contains all the common
functionality. How are you supposed to do this in Go?

A composite struct, with functions that return interfaces, implemented
by a mix of common and custom structs (that implement the exported
interfaces)? That seems to invite divergence between the branches ....

Stephen Ma

unread,
Nov 12, 2009, 12:25:43 AM11/12/09
to golang-nuts
On Nov 12, 3:36 pm, Jon <shem...@gmail.com> wrote:
> Inheritance is not just about polymorphism. Inheritance is also about
> cleanly handling similar-but-not identical, like when two classes
> share everything except one or two functions. They can both descend
> from an abstract base class that contains all the common
> functionality. How are you supposed to do this in Go?

Go supports a form of "base class" by promoting subfields and methods
of anonymous struct fields. More specifically, if you have a "base"
struct type B, with method f(), and you define "derived" struct types
D1 and D2 with B as anonymous fields then D1 and D2, then the method f
() can be applied to variables of type D1 and D2. You can put all of
your common methods and fields in B.

Here's a concrete example:

package fu

type B struct {
count int;
}

func (b B) inc() {
b.count++;
}

type D1 struct {
B;
}

type D2 struct {
B;
}

func test() {
var d1 D1;
var d2 D2;

d1.inc();
d2.inc();
}

- S

Jason

unread,
Nov 12, 2009, 12:30:16 AM11/12/09
to golang-nuts
You might want to take a look at Day 2 of the 3-day training slides
regarding Anonymous Fields. It's pretty interesting, allowing you to
basically multiply-inherit from as many struct types as you like. And
instead of having weird munging rules, you have to be explicit (i.e.
resolve) conflicts between multiple competing anonymous type fields.

In a way, the new struct defines a scope that contains all of its
anonymous members' fields and methods. Sounds like inheritance to me!

The links for the 3 days of slides are in the first paragraph of the
Tutorial:

http://golang.org/doc/go_tutorial.html

-Jason

Jon Shemitz

unread,
Nov 12, 2009, 9:28:10 AM11/12/09
to Stephen Ma, golang-nuts
Excellent reply, thanks. This perhaps should be in the FAQ.

Truls

unread,
Nov 16, 2009, 4:18:59 PM11/16/09
to golang-nuts
Jason wrote:

> In a way, the new struct defines a scope that contains all of its
> anonymous members' fields and methods. Sounds like inheritance to me!

Yes, anonymous fields let derived types inherit behavior, but this is
through delegation and not actual inheritance. The problem with this
is that once you enter the code for the base type, you are stuck! The
object the method was called on does not get a chance to customize the
behavior. The following program prints 13, and not 42 as in real OO;

package main
import "fmt"

type B struct {}
type D struct { B }

func (self B) test() {
fmt.Printf("%d\n", self.answer());
}

func (self B) answer() int {
return 13;
}

func (self D) answer() int {
return 42;
}

func main() {
d := D{};
d.test();
}

-Truls

Peter Froehlich

unread,
Nov 16, 2009, 4:26:55 PM11/16/09
to Truls, golang-nuts
Hi all,

On Mon, Nov 16, 2009 at 4:18 PM, Truls <truls....@gmail.com> wrote:
> Yes, anonymous fields let derived types inherit behavior, but this is
> through delegation and not actual inheritance. The problem with this
> is that once you enter the code for the base type, you are stuck! The
> object the method was called on does not get a chance to customize the
> behavior.

Sorry, at the risk of becoming the "resident crazy terminology
nitpicker" here: What you describe and what Go does is *not*
delegation, it's *forwarding*. In delegation "self" inside a method
that was delegated to is the delegating object (the original
receiver). In forwarding, "self" inside a method that was forwarded to
is the receiving object itself (the "embedded" object in Go lingo).
Disclaimer: I *think* that's true for Go. Any Guru?

Cheers,
Peter
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab

Ben Tilly

unread,
Nov 16, 2009, 4:34:41 PM11/16/09
to Truls, golang-nuts
Not so fast. If you code the equivalent example in C++ you will get
13 as well. That is because when the function test is compiled the
type and methods known so the compiler compiles in the direct function
call.

To get the behavior you expect in C++ you'd have to say that the
method is virtual. This forces a dynamic run-time lookup. You should
be able to get a similar dynamic run-time lookup in Go with the
reflect package.

Cheers,
Ben

Truls

unread,
Nov 16, 2009, 5:15:17 PM11/16/09
to golang-nuts
Peter Froehlich wrote:

> What you describe and what Go does is *not* delegation, it's
> *forwarding*. In delegation "self" inside a method that was delegated
> to is the delegating object (the original receiver). In forwarding, "self"
> inside a method that was forwarded to is the receiving object itself
> (the "embedded" object in Go lingo).

Thanks for clearing that up.


Ben Tilly wrote:

> To get the behavior you expect in C++ you'd have to say that the
> method is virtual.  This forces a dynamic run-time lookup.  You should
> be able to get a similar dynamic run-time lookup in Go with the
> reflect package.

Well, there is a huge difference here - in the C++ case you are
calling the method on the original receiver whether you use the
implementation for the base class or the derived class, while in the
Go case the call has been forwarded to a different (embedded)
instance, so there is no way, reflection or not, to call any methods
on the original receiver.

Changing my test() method to the following makes it print "B" even
though I call it on an instance of D.

func (self B) test() {
t := reflect.Typeof(self);
fmt.Printf("%s\n", t.Name());
}

-Truls

Ben Tilly

unread,
Nov 16, 2009, 5:51:02 PM11/16/09
to Truls, golang-nuts
On Mon, Nov 16, 2009 at 2:15 PM, Truls <truls....@gmail.com> wrote:
> Ben Tilly wrote:
>
>> To get the behavior you expect in C++ you'd have to say that the
>> method is virtual.  This forces a dynamic run-time lookup.  You should
>> be able to get a similar dynamic run-time lookup in Go with the
>> reflect package.
>
> Well, there is a huge difference here - in the C++ case you are
> calling the method on the original receiver whether you use the
> implementation for the base class or the derived class, while in the
> Go case the call has been forwarded to a different (embedded)
> instance, so there is no way, reflection or not, to call any methods
> on the original receiver.
>
> Changing my test() method to the following makes it print "B" even
> though I call it on an instance of D.
>
> func (self B) test() {
>    t := reflect.Typeof(self);
>    fmt.Printf("%s\n", t.Name());
> }

Really? That's annoying. Understandable, but annoying.

What happens if you define an interface CanAnswer and modify the test
method to be a method on that interface rather than a method on B?

Cheers,
Ben

Peter Froehlich

unread,
Nov 16, 2009, 6:05:52 PM11/16/09
to Ben Tilly, Truls, golang-nuts
Hi all,
For the record, I think this behavior is preferable to "normal"
inheritance, most importantly because it rules out the "good old"
fragile base class problem. Once you're "up" in your "base class"
there's no way to go "back down" again by accident. Therefore base
class code is "self-contained" and can't be disturbed by subclasses. I
should use different terminology since no "implementation inheritance"
is actually going on, but I hope you get the picture. A big win for
modularity.

Forwarding by itself is strictly less "powerful" than "normal"
inheritance, but it doesn't preclude you from writing excellent code.
It's just that when you want the crazy behavior normal "implementation
inheritance" gives you, you have to pass the original receiver around
as an explicit parameter. If you want some more background:

http://www.cs.jhu.edu/~phf/pub/ecoop-inheritance-2002.pdf
Reply all
Reply to author
Forward
0 new messages