- I was wondering if there is also a possible way to implement (or
mimic) an "is a" relationship between objects (user defined types) in
Go ? in order to achieve the "specialization" effect which is obtained
in C++ or Python via the mechanism of inheritance ?
Also note that you can embed any named type including an interface
(very useful!). You can also embed interfaces in interfaces.
The relationship is between /named types/ which is important because
it's more powerful:
type X int
//methods on X
type Y struct {
X
} //has methods of Y
type Z interface { Z() }
type W struct {
Z
} //you can assign anything with a Z method to the anonymous field in W
> This feature allows for specialization in Go (in the sense of :
> inheritance + overloading).
>
>
> A priori, this sounds like a very powerful feature.
>
> In C++, before the introduction of templates, generics were obtainable
> (at least within a tree of classes deriving from a common ancestor) by
> defining a set of methods on a common ancestor (virtual) class and by
> redefining said methods for the children classes, when needed
> (overloading).
>
> Would this modus operandi not provide generics in Go (within a set of
> structures -embedding a "common ancestor"- ) ?
>
> Serge.
>
>
I think I see what you're saying but that's not really generics so much
-rob
Struct embedding doesn't do what OO inheritance does
(viz, method overriding). So they may not (just) want
struct embedding -- they may want (or find useful) first-class
functions or interfaces.
Chris
--
Chris "allusive" Dollin
Hi Andrew,
Since Go provides inheritance/specialization (as you have correctly
pointed at) then why does container/stringvector.go actually *copy*
(some of) the methods of container/vector.go instead of simply
inheriting them by embedding (and redefining them when appropriate) ?
Serge
> When studying C++, Java and Python it struck me that one very strong
> point that all thee languages have in common is their build-in ability
> to map the (inheritance-related) tree structures of classes which
> represent real-world objects which by nature possess hierarchical
> relationships, such as widgets from a GUI tool kit, employees in an
> organization, and more generally : modelling real-world objects.
I keep wishing I could find the Smalltalk retrospective article in
which it was pointed out that it is a bit odd to have processes
derived from lists, which was done because they wished to have
processes arranged in a linked list. The point being made in the paper
that it was hardly an intuitive relationship. Yes, we can keep finding
cases where inheritance makes sense; that doesn't establish that it
will always or even in most cases make sense. It just means it does at
times.
ron
. The contra: The absence of inheritance (e.g. as known from C++ or
Python), which leads to a lot of code duplication (as we have seen
from the example of container.vector.go and container/
stringvector.go).
Perhaps something eludes me, but it seems to me that the absence of
inheritance in Go (in the usual sense of the term, when dealing with
"classes" as in C++, Python and Java), leads to a lot of code
duplication, which in turn will translate into a lot of maintenance.
> Say, for instance, you modify the container/vector.go module at some
> point, then
... the changes are propagated to the int/string/whatever vector
files as part of the build.
> (Because of the mandatory duplication of code,
Go has functions. Go has /first-class/ functions, which means
you can store [pointers to] them in data structures and
imitate method over-riding if you have to think that way.
Which you don't.
> When studying C++, Java and Python it struck me that one very strong
> point that all thee languages have in common is their build-in ability
> to map the (inheritance-related) tree structures of classes which
> represent real-world objects which by nature possess hierarchical
> relationships,
No, they don't "by nature" have hierarchical relationships; we impose
those relationships in our modelling. Different modelling imposes
different constraints.
There's an (implicit) is-a graph (NOT a hierarchy) among Go's
interfaces which IMAO is where it belongs. Trying to conflate the
descriptive is-a's with inheritance is-subclass-of (aka extends)
doesn't seem to work very well.
This is I think another case of puddings, the proof of which is
to be gained by trying out coding tactics allowed by the language
and seeing how Go's approach works on a larger scale than
that of class & method declarations.
http://golang.org/doc/go_spec.html#Interface_types
should be enough to get started. A variable (so, also a
function argument) of interface type T can accept a value
of any type U that implements at least the methods of T.
If U is also an interface type, you can think of U being a
subinterface of T.
It's not a hierarchy because different superinterfaces of U
might omit different methods. But there is a unique top
element, viz interface{}.
Yes, one that could be solved by allowing paramaterization of types,
but I do not see how it could be solved by inheritance.
As for your last question, I don't really follow it. There are lots of
articles on the internet about composition vs. inheritance that could
be much more informative than I could be.
nobody is saying that this particular issue is handled by the language,
which is why generics are still being considered.
on the other hand, the vector types are unusual in that the underlying
data structure is visible as well as the methods on it (which is why
it matters that StringVector actually works on []string rather than
[]interface{}).
for other kinds of generic data structure, using interface{} and
defining methods to transform to and from the required data
type can work, with a relatively small runtime cost.
> So basically, there are two ways to handle the case of closely related
> data structure in Go (as in the example of the vectors):
>
> . 1 : Duplicate the code and slightly modify it to fit every single
> particular case (as in vector, intvector and stringvector).
Not /every/ single particular case. Enough to be useful.
> (tedious, but efficient)
Tedious the first time; then you write a program in Your Preferred
Language to do the job.
> or
>
> . 2 : Use reflection to deal with the actual type of the variable(s)
> (at runtime).
>
> (more elegant,
Hmm, hardly "elegant" IMAO.
> less error-prone (no duplication),
Playing with reflection is non-trivial.
> less maintenance-
> intensive (for the same reason), but possibly
I would have said "almost certainly", but I defer to the Go
implementors.
> slower at run time)
Or: pick a good choice of iterfaces (eg the sort interface, which
doesn't do either of your suggestions).
i'm not sure that "reflection" is the right term for what i was referring to.
in my view reflection involves writing code to inspect the type
itself (with the reflect package) not simply converting between
dynamic and static types.
it's worth making the distinction, because converting between dynamic
and static types is straightforward, quite efficient and often used, whereas
using reflection is harder, not nearly as efficient and is used
rarely.
good luck with the python port!
I'd hardly say "less error-prone", since it (in general) means that
many errors become runtime errors rather than compile-time errors.
Unless you have sufficiently simple code that it can be expressed as a
set of interface methods. But it would depend on whether this is
write-once-use-many-times sort of code or the inverse. Certainly
StringVector falls in the category of code that is seldom written but
often used, so having a type-safe API is definitely invaluable.
--
David Roundy
> I must confess that the whole thread started from a misunderstanding
> on my part: I have been studying Go for about a year now and although
> I understand the concept of interface, as such, I thought that perhaps
> Go *also* provided a way to implement some kind of inheritance (but
> that its modus operandi eluded me).
Embedding provides "some kind of inheritance", where "some kind of"
excludes method over-riding in the Java style; perhaps that's what
provoked your misunderstanding?
That is, if type T has methods M1, M2 ..., then given
type U struct { T; other Something }
U has the methods of T and any other methods that get defined on U.
And you can redefine methods from T.
HOWEVER if M1 of T calls M2 of T, and U has a redefinition of M2,
calling M1 of U calls M1 of T, NOT of U. That's what I mean above
by "excludes method over-riding". (Methods of) T don't get to see
redefinitions made in types that embed T.
(If one wants an effect similar to that, you can deploy Go's
existing machinery to do something like it; but you have to
pay more up-front in T and elsewhere for doing so.)
But here's the thing: this isn't actually a "xenotransplantation" as
you put it :-). It uses the very common go idiom of composing
behaviour by combining discrete layers (see Writer and Reader). It
takes a bunch of related end behaviours acting on the same object and
groups them together (also not new, see ReadWriter). The only thing is
that it is self referential, which allows you to combine the various
elements in parallel, rather than in series.
People often come to Go with a very strong tendency to follow some
form of inheritance model. They either get frustrated by people saying
"no you can't do that here" and leave, or they attempt to emulate it
directly using embedding for inheritance, and interfaces for vtables.
This makes for very messy, inflexible, and all around *bad* code.
Sometimes (though rarely) inheritance *is* the optimal way to express
the relationship between a set of types, and people either try the
afore mentioned horror code, or give up and use another language.
Often, if you think about it hard enough, you can come up with a
clever alternate representation that is specific to your use. However,
this isn't always forthcoming.
While I'm not claiming that this is the be all and end all of Go
design (far from it!), it does allow you to start modelling what you
have in your head when it doesn't fit Go's standard abstractions. And
what happens when you realize that you don't need the inheritance
crutch? You have already written easily composible types, useful
interfaces, and a library of proceedural functions which should work
just swell, with a minimum of extra code wasted on beating it into the
now abandoned inheritance abstraction.
I probably didn't do it justice, either by my explanation of it (sorry
it was abstract, I usually have to do many revisions to clarify
things), or by presenting it as an inheritance strategy rather than
what it really is: an very general purpose scheme for parallel
composition. However, I was a bit excited about how much simpler it is
than most attempts at inheritance-like schemes in Go and felt I should
draw attention to how it can be used for this purpose. Of course, if
there's a specific glaring reason why this is *bad* (apart from
enabling people to model inheritance), then feel free to point it out.
I haven't come up with one yet.
It is very simple actually. If you look at the io package, for
example, there is an interface called Reader, which contains a Read
method, and a Seeker with a Seek method. Then there is the ReadSeeker:
type ReadSeeker interface {
Reader
Seeker
}
This is composition, but it serves to illustrate an is-a relationship:
a ReadSeeker is also a Reader and a Seeker. If you have a type that
implements ReadSeeker, it will also implement Reader and Seeker.
--Benny.