Intersections of interface types

1,191 views
Skip to first unread message

Matthias Zenger

unread,
Feb 19, 2011, 8:15:32 AM2/19/11
to golan...@googlegroups.com
I was wondering if someone here knows why interface types can only be composed if they are disjoint? Here is an example:

  type Reader interface {
    Get() interface{}
    Active() bool
  }
  
  type Writer interface {
    Put(interface{})
    Active() bool
  }

It is straightforward to develop an implementation that has a Get(), a Put(interface{}) as well as an Active() method. So, why can't I intersect both types by including them in a ReaderWriter interface type like this:

  type ReaderWriter interface {
    Reader
    Writer
  }

This limitation is really annoying as it limits reuse of interface types that have more than one method significantly. Is there any reason why the language doesn't allow me to do this?

Thanks,
  Matthias


bflm

unread,
Feb 19, 2011, 8:35:34 AM2/19/11
to golang-nuts
The reason is IMO the obvious ambiguity of an method that materializes
when composing an interface inheriting two methods of the same
signature and name. It could be probably solved by qualifying the
ambiguous method by e.g. the name of the parent entity it comes from.
Then one would have an language orthogonality exception in that the
pkgname.Exported pattern is no more universal to the reader in the
sense of a reference to Exported as a top level exported declaration
found in pkgname. Such exceptions are - I guess - unwelcome by the
language designers.

unread,
Feb 19, 2011, 9:04:03 AM2/19/11
to golang-nuts
There is no ambiguity. The 'Reader', 'Writer' and 'ReadWriter' are
just interfaces. A Go interface is a *set* of method *signatures*.

The reason why the language does not allow such compositions has to
lie somewhere else.

bflm

unread,
Feb 19, 2011, 9:21:33 AM2/19/11
to golang-nuts
On Feb 19, 3:04 pm, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:
> There is no ambiguity.

There is. Let's take this invalid Go program:
====
package main

type Reader interface {
Get() interface{}
Active() bool
}

type Writer interface {
Put(interface{})
Active() bool
}

type ReaderWriter interface {
Reader
Writer
}

type R int

func (r R) Get() interface{} {
return nil
}

func (r R) Active() bool {
return false
}

type W int

func (w W) Put(interface{}) {
}

func (w W) Active() bool {
return true
}

type RW struct {
R
W
}

func main() {
var rw ReaderWriter = RW{0, 0}
println(rw.Active()) // should print "true" or "false" ???
}
====
make says:
tmp.go:15: duplicate method Active
tmp.go:43: RW.Active is ambiguous
tmp.go:43: cannot use struct literal (type RW) as type ReaderWriter in
assignment:
RW does not implement ReaderWriter (missing Active method)
tmp.go:44: ambiguous DOT reference ReaderWriter.Active

If the program would be compilable, what it should, in your opinion,
print out?

Matthias Zenger

unread,
Feb 19, 2011, 9:22:19 AM2/19/11
to bflm, golang-nuts
>   type Reader interface {
>     Get() interface{}
>     Active() bool
>   }
>
>   type Writer interface {
>     Put(interface{})
>     Active() bool
>   }
>
> It is straightforward to develop an implementation that has a Get(), a
> Put(interface{}) as well as an Active() method. So, why can't I intersect
> both types by including them in a ReaderWriter interface type like this:
>
>   type ReaderWriter interface {
>     Reader
>     Writer
>   }
>
> This limitation is really annoying as it limits reuse of interface types
> that have more than one method significantly. Is there any reason why the
> language doesn't allow me to do this?
 
The reason is IMO the obvious ambiguity of an method that materializes
when composing an interface inheriting two methods of the same
signature and name. It could be probably solved by qualifying the
ambiguous method by e.g. the name of the parent entity it comes from.
Then one would have an language orthogonality exception in that the
pkgname.Exported pattern is no more universal to the reader in the
sense of a reference to Exported as a top level exported declaration
found in pkgname. Such exceptions are - I guess - unwelcome by the
language designers.

I'm not sure I understand. In Go, interfaces and implementations are
orthogonal. According to the Go language spec, interfaces are just
sets of methods. Thus, in theory, when building the union of two
message sets, ambiguities should only arise if a method with the same
name has two incompatible/different signatures as it won't be possible
to implement this type.

I just had another look at the language description:
Unfortunately, there is no specification of what a well-formed interface
declaration is. I suspect that the issue is an unfortunate implication of
this statement: "An interface may contain an interface type name T
in place of a method specification. The effect is equivalent to
enumerating the methods of T explicitly in the interface."

== Matthias

yy

unread,
Feb 19, 2011, 10:06:34 AM2/19/11
to bflm, golang-nuts
2011/2/19 bflm <befeleme...@gmail.com>:

>> This limitation is really annoying as it limits reuse of interface types
>> that have more than one method significantly. Is there any reason why the
>> language doesn't allow me to do this?
> The reason is IMO the obvious ambiguity of an method that materializes
> when composing an interface inheriting two methods of the same
> signature and name.

The problem is not ambiguity. The problem is duplication. The error
given by the compiler is very clear: "duplicate method Active".

This makes sense, because the specification says: "The effect [of
embedding an interface type T] is equivalent to enumerating the
methods of T explicitly in the interface.". So, it is equivalent to:

type ReaderWriter interface {
Get() interface{}
Active() bool
Put(interface{})
Active() bool
}

which, indeed, produces the same error.

You are asking for a language change, but I'm not sure how that change
would look like.

What if you add a new interface to your example?

type Activer interface {
Active() bool
}

type ReaderWriter interface {
Reader
Writer

Activer
}

Where is the limit?

Interfaces would not be so powerful if they were not so simple. Maybe
limiting the use of complex interfaces is not such a bad thing.

--
- yiyus || JGL .

bflm

unread,
Feb 19, 2011, 10:20:51 AM2/19/11
to golang-nuts
On Feb 19, 4:06 pm, yy <yiyu....@gmail.com> wrote:
> The problem is not ambiguity.

The problem is IMO only the ambiguity. Duplication in this case is one
of the ways how to create the ambiguity. I'm not sure if that
implication can be reversed :-)

> The problem is duplication. The error
> given by the compiler is very clear: "duplicate method Active".

Sure. The compiler knows, that the duplication is provenly an error as
it creates ambiguity.

> This makes sense, because the specification says: "The effect [of
> embedding an interface type T] is equivalent to enumerating the
> methods of T explicitly in the interface.". So, it is equivalent to:
>
>   type ReaderWriter interface {
>     Get() interface{}
>     Active() bool
>     Put(interface{})
>     Active() bool
>   }
>
> which, indeed, produces the same error.
>
> You are asking for a language change, but I'm not sure how that change
> would look like.

I'm not asking for a language change. I think the current state of
this subject in the specs and implementation is reasonable and
correct. The OP is questioning it, not me.

> What if you add a new interface to your example?
>
> type Activer interface {
>   Active() bool
>
> }
>
> type ReaderWriter interface {
>   Reader
>   Writer
>   Activer
>
> }

This is OK as long as you remove the Active method from the OP post
definitions of Reader and Writer (that's how I suppose you mean it).
It then removes the ambiguity and so it's semantics are clear => no
need for the compiler to complain.

unread,
Feb 19, 2011, 10:39:04 AM2/19/11
to golang-nuts
On Feb 19, 2:21 pm, bflm <befelemepesev...@gmail.com> wrote:
> make says:
> tmp.go:15: duplicate method Active
> tmp.go:43: RW.Active is ambiguous
> tmp.go:43: cannot use struct literal (type RW) as type ReaderWriter in
> assignment:
>         RW does not implement ReaderWriter (missing Active method)
> tmp.go:44: ambiguous DOT reference ReaderWriter.Active
>
> If the program would be compilable, what it should, in your opinion,
> print out?

The original question was about *interfaces*, not about struct
embedding and not about ambiguous method resolutions. The errors at
lines 43 and 44 do not have any relation to the subject of the
original question.

The dispute here is only about the error at line 15. As far as that is
concerned, the answer is: it is an arbitrary choice made by the Go
language designers.

bflm

unread,
Feb 19, 2011, 11:03:01 AM2/19/11
to golang-nuts
On Feb 19, 4:39 pm, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:
> On Feb 19, 2:21 pm, bflm <befelemepesev...@gmail.com> wrote:
>
> > make says:
> > tmp.go:15: duplicate method Active
> > tmp.go:43: RW.Active is ambiguous
> > tmp.go:43: cannot use struct literal (type RW) as type ReaderWriter in
> > assignment:
> >         RW does not implement ReaderWriter (missing Active method)
> > tmp.go:44: ambiguous DOT reference ReaderWriter.Active
>
> > If the program would be compilable, what it should, in your opinion,
> > print out?
>
> The original question was about *interfaces*, not about struct
> embedding and not about ambiguous method resolutions. The errors at
> lines 43 and 44 do not have any relation to the subject of the
> original question.

They do. The RW struct (in the invalid example providen) is just
saytisfying the ReaderWriter *interface*. It shows the unsolvable
ambiguity of the *interface*. Note that the example RW struct per se
is perfectly valid Go. Only when assigning the instance of RW to an
*interface* the problem demonstrates.

> The dispute here is only about the error at line 15. As far as that is
> concerned, the answer is: it is an arbitrary choice made by the Go
> language designers.

Even if I would want to agree, there's no way to do it. Keeping the
ambiguity compilable leads to unwanted non determinism.

Here is another invalid program which shows the same problem from a
bit different POV:
====
package main

type I interface {
Active() bool
Get() interface{}
Put(interface{})
}

type R int

func (r R) Get() interface{} {
return nil
}

func (r R) Active() bool {
return false
}

type W int

func (w W) Put(interface{}) {
}

func (w W) Active() bool {
return true
}

type RW struct {
R
W
}

func main() {
var rw I = RW{0, 0}
println(rw.Active()) // should print "true" or "false" ???
}
====
make says:

$ make
6g -o _go_.6 tmp.go
tmp.go:34: RW.Active is ambiguous
tmp.go:34: cannot use struct literal (type RW) as type I in
assignment:
RW does not implement I (missing Active method)
make: *** [_go_.6] Error 1
$

Note: line 34 is this one: "var rw I = RW{0, 0}", not the definition
of the RW struct.

As you can see, there's no more mentioning of any duplication, so the
interface ambiguity problem show hopefully now more clearly as being
the show stopper.

What should the program print out if it would be compilable?

unread,
Feb 19, 2011, 11:05:50 AM2/19/11
to golang-nuts
On Feb 19, 4:03 pm, bflm <befelemepesev...@gmail.com> wrote:
> On Feb 19, 4:39 pm, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:
>
>
>
>
>
>
>
>
>
> > On Feb 19, 2:21 pm, bflm <befelemepesev...@gmail.com> wrote:
>
> > > make says:
> > > tmp.go:15: duplicate method Active
> > > tmp.go:43: RW.Active is ambiguous
> > > tmp.go:43: cannot use struct literal (type RW) as type ReaderWriter in
> > > assignment:
> > >         RW does not implement ReaderWriter (missing Active method)
> > > tmp.go:44: ambiguous DOT reference ReaderWriter.Active
>
> > > If the program would be compilable, what it should, in your opinion,
> > > print out?
>
> > The original question was about *interfaces*, not about struct
> > embedding and not about ambiguous method resolutions. The errors at
> > lines 43 and 44 do not have any relation to the subject of the
> > original question.
>
> They do. The RW struct (in the invalid example providen) is just
> saytisfying the ReaderWriter *interface*.

The question was about *defining* an interface, not about satisfying
it.

bflm

unread,
Feb 19, 2011, 11:21:37 AM2/19/11
to golang-nuts
On Feb 19, 5:05 pm, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:

---
> The question was about *defining* an interface, not about satisfying
> it.
---

Yes. The answer/examples shows why the *definition* of an interface
can lead to ambiguity when it is satisfied by something. BTW,
interface *definition* without something satisfying it in a program is
a redundant *definition* by *definition* :-)

More seriously now on. The examples "exploiting" the problem by
satisfying the interface answers the OP question, i.e. why the
interfaces can't be composed in the way he would probably like to. The
method duplication in the OP interface definitions introduces the
ambiguity problem discussed here.

So the quoted above complaint about "Q: interface definition vs A:
satisfying it" doesn't make sense to me.

Gustavo Niemeyer

unread,
Feb 19, 2011, 11:54:27 AM2/19/11
to bflm, golang-nuts
> More seriously now on. The examples "exploiting" the problem by
> satisfying the interface answers the OP question, i.e. why the

You haven't shown any problem so far which would answer the OP
question, and I don't think you'll be able to. There's no ambiguity.

The correct answer to his question is most probably "it's simple
to implement the way it is now.".

--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/blog
http://niemeyer.net/twitter

unread,
Feb 19, 2011, 11:57:36 AM2/19/11
to golang-nuts
On Feb 19, 4:21 pm, bflm <befelemepesev...@gmail.com> wrote:
> On Feb 19, 5:05 pm, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:
>
> ---> The question was about *defining* an interface, not about satisfying
> > it.
>
> ---
>
> Yes. The answer/examples shows why the *definition* of an interface
> can lead to ambiguity when it is satisfied by something. BTW,
> interface *definition* without something satisfying it in a program is
> a redundant *definition* by *definition* :-)
>
> More seriously now on. The examples "exploiting" the problem by
> satisfying the interface answers the OP question, i.e. why the
> interfaces can't be composed in the way he would probably like to. The
> method duplication in the OP interface definitions introduces the
> ambiguity problem discussed here.

When the definition of an interface contains multiple occurrences of
the same method (same name and signature), the language designer has
the following two choices:

1. Report an error, duplications are disallowed

2. Ignore the duplicates, duplications are allowed but have no
semantic effect

In my opinion, the original question was the following: Why does Go
implement (1) instead of (2) ?

My answer was: it is an arbitrary choice made by the Go language
designers.

bflm

unread,
Feb 19, 2011, 12:14:01 PM2/19/11
to golang-nuts
On Feb 19, 5:54 pm, Gustavo Niemeyer <gust...@niemeyer.net> wrote:
> You haven't shown any problem so far which would answer the OP
> question,

I haven't?

> and I don't think you'll be able to.

Really? Bad news for me :-)

> There's no ambiguity.

$ make
6g -o _go_.6 tmp.go
tmp.go:34: RW.Active is ambiguous
tmp.go:34: cannot use struct literal (type RW) as type I in
assignment:
RW does not implement I (missing Active method)
make: *** [_go_.6] Error 1
$

No ambiguity?

> The correct answer to his question is most probably "it's simple
> to implement the way it is now.".

That's IMO not a correct answer and I think it's a quite misleading
one. Correct answer would have to say e.g. "It's forbidden/an error
because there's no way to implement it unambiguously without e.g.
introducing another levels of qualification for a singular entity
which an interface method is."

Gustavo Niemeyer

unread,
Feb 19, 2011, 12:22:59 PM2/19/11
to bflm, golang-nuts
>>  and I don't think you'll be able to.
>
> Really? Bad news for me :-)

No, that's good news for you. You're about to understand how Go
interfaces work. :-)

> No ambiguity?

No, no ambiguity. You can obviously do this today:

type A interface { String() string }
type B interface { String() string }
type C interface { String() strign }

All the OP is asking is why he can't do:

type C interface { A; B }

with exactly the same semantics as the previous version.

Stop for a moment and read what everyone in this thread is trying to tell you.

roger peppe

unread,
Feb 19, 2011, 12:23:45 PM2/19/11
to bflm, golang-nuts
On 19 February 2011 17:14, bflm <befeleme...@gmail.com> wrote:
> That's IMO not a correct answer and I think it's a quite misleading
> one. Correct answer would have to say e.g. "It's forbidden/an error
> because there's no way to implement it unambiguously without e.g.
> introducing another levels of qualification for a singular entity
> which an interface method is."

i think niemeyer is right.
there's no ambiguity in the original. an interface holds a *set*
of methods, and there's no reason that the result of the original
post's ReaderWriter interface couldn't be the set union of the
Reader and Writer interfaces (with an error if Active had a different
type in each). that would be a way to implement it unambiguously.

however the current definition is simple and sufficient for
most purposes. interface embedding is only a convenience
anyway - you can always explicitly include the methods
you want if necessary.

Gustavo Niemeyer

unread,
Feb 19, 2011, 12:25:48 PM2/19/11
to Matthias Zenger, golan...@googlegroups.com
Hi Matthias,

> This limitation is really annoying as it limits reuse of interface types
> that have more than one method significantly. Is there any reason why the
> language doesn't allow me to do this?

While this sounds somewhat like a reasonable request, I think there's
a detail there which makes it not so desirable.

The fact that you have a common interface between two methods means
there's another implicit interface which is not being declared, but
which exists. What would people do if they wanted to execute the
Active() method in either type, for instance?

The solution is likely to have a third interface:

type Activable interface {
Active() bool
}

and share embed this interface instead.

bflm

unread,
Feb 19, 2011, 12:42:40 PM2/19/11
to golang-nuts
On Feb 19, 6:22 pm, Gustavo Niemeyer <gust...@niemeyer.net> wrote:
> No, that's good news for you.  You're about to understand how Go
> interfaces work. :-)

Sorry, too late. The compiler is already passing the tests.

> > No ambiguity?
>
> No, no ambiguity.  You can obviously do this today:
>
>   type A interface { String() string }
>   type B interface { String() string }
>   type C interface { String() strign }
>
> All the OP is asking is why he can't do:
>
>   type C interface { A; B }
>
> with exactly the same semantics as the previous version.

I don't know why the question is explained to me if I've already
understood and answered it. Yes, I can be wrong any number of times in
a day. In such a case I will confess it as usual. Still my answer is
the same - when satisfying the C interface you can run into ambiguity
(c.f. previous examples) and that's why it's a compiler error.

--
> Stop for a moment and read what everyone in this thread is trying to tell you.
--

Please feel free to continue in explaining your thoughts. There's no
point in asking you to stop it regardless of any agreement or
disagreement :-)

Ryanne Dolan

unread,
Feb 19, 2011, 1:01:24 PM2/19/11
to bflm, golang-nuts
If I call Interface.Foo() and Interface defines two Foos, there seems to be an ambiguity.  Which Foo am I trying to call thru Interface?

But interfaces are just sets of functions... it's the underlying struct that can't have two Foos.

Obviously I can't call Struct.Foo() if there are two Foos defined for Struct.  That would definitely be ambiguous, and the compiler rightly doesn't allow this:

func (s *Struct) Foo() {something();}
func (s *Struct) Foo() {somethingelse();}

But that's not the question.

Say we have two interfaces A and B:

type A interface { foo() }
type B interface { foo() }

And a struct:

type S struct { }
func (s *S) Foo();

Clearly:
- S satisfies A
- S satisfies B

The question is, why can't S satisfy C?

type C interface {
  A
  B
}

I fail to see any ambiguity at all.  There is only one Foo, regardless of whether I call any of these:

S.Foo()
A.Foo()
B.Foo()
C.Foo()
C.A.Foo()
C.B.Foo()

Moreover, it is impossible to find a case where Interface.Function() is ambiguous, since Interfaces can only refer to an underlying type which necessarily has a single definition for every Function.

Thanks.
Ryanne

--
www.ryannedolan.info

Steven

unread,
Feb 19, 2011, 1:01:42 PM2/19/11
to bflm, golang-nuts
You're talking about struct embedding. This thread is about interface composition. The RW struct doesn't satisfy I, point blank. See the lines:
tmp.go:34: cannot use struct literal (type RW) as type I in
assignment:
       RW does not implement I (missing Active method)


The other line about ambiguity is explaining why RW doesn't have an Active method, so that you can fix it.

Having duplicated methods in an interface doesn't open up the possibility of them being implemented by two different methods on one type. If the method is duplicated on the type, then the type doesn't have that method at all. You would have to define a sane Active method on RW itself. The duplicated methods in the interface would just be merged as long as they matched perfectly, and it would be the same as you're I interface.

Try calling Active on RW. The ambiguity has nothing to do with the interface. Try adding an Active method to RW. No ambiguity. Again, nothing to do with the interface. We're not talking about embedded method resolution. We're talking about composing interfaces, which is something entirely different.

JONNALAGADDA Srinivas

unread,
Feb 19, 2011, 1:50:14 PM2/19/11
to golan...@googlegroups.com
On Saturday, February 19, 2011 11:31:42 PM UTC+5:30, Steven wrote:
We're not talking about embedded method resolution. We're talking about composing interfaces, which is something entirely different.

        Does the Specification maintain that distinction currently?  As per Sections http://golang.org/doc/go_spec.html#Method_sets, http://golang.org/doc/go_spec.html#Struct_types and http://golang.org/doc/go_spec.html#Interface_types, currently there seems to be a sort of symmetry between how promotion of methods of embedded structs and those of embedded interfaces results in the `method set' of the corresponding outer entity.
        The common rule of `method names must be unique in a method set' can still hold.  However, the process of constructing a method set itself probably needs to be broken into two - one each for interfaces and structs - for Matthias' request to be allowed in the language.

        Or, am I reading the Specification incorrectly?  Thanks.

                                                            Greetings,
                                                                    JS

Steven

unread,
Feb 19, 2011, 2:12:58 PM2/19/11
to golan...@googlegroups.com, JONNALAGADDA Srinivas
No. It is true that the method names must be unique in a method set. The way method names are resolved is what is being discussed. In structs, the method is resolved to the one at the smallest depth. If this does not produce a unique result (multiple methods with the same name at the same depth), then the resolution is ambiguous, and the struct doesn't have the method. In interfaces, the method set is flattened (there is no concept of depth, unlike with structs), and then any duplication results in an error (ie it isn't resolved). What's being suggested is that duplicate methods with identical signatures in an interface would be merged, and thus the method set can be resolved to have only one method with that name, so there is no error. This would work because all we care about is the signature; there aren't multiple implementations to choose between.

A concern might be that two methods with identical signatures might have entirely different meanings:

type Cowboy interface { Draw() }
type Canvas interface { Draw() }

type MyInterface interface {
    Cowboy
    Canvas
}

Of course, it isn't likely that these two methods would have identical signatures anyway, and if they did, you probably wouldn't compose something that is both a cowboy and a canvas; you are likely composing related things in the same domain, so the names would have unique meaning.

Ultimately, it should be up to the programmer, not the compiler, to know whether a specific composition makes sense, ie, whether two identical methods have identical meaning.

Another issue might be documentation. Currently, composed interfaces aren't flattened in godoc, so its a non-issue. However, if some tool tried to flatten the method set for the sake of documentation, it might have trouble choosing which doc comment to use. One option would just be to mark each comment as originating with a specific embedded interface. Another would be, if the methods were ultimately originating from the same source, ie:

type Sheriff interface { Cowboy; Arrest() }
type Bandit interface { Cowboy; Raid() }

type BanditSheriff interface { Bandit; Sheriff }

then there isn't an issue either.

David Roundy

unread,
Feb 21, 2011, 3:29:48 PM2/21/11
to Steven, golan...@googlegroups.com, JONNALAGADDA Srinivas
Thanks for the clear explanation (which I cut)!

On Sat, Feb 19, 2011 at 11:12 AM, Steven <stev...@gmail.com> wrote:
> A concern might be that two methods with identical signatures might have
> entirely different meanings:
> type Cowboy interface { Draw() }
> type Canvas interface { Draw() }
> type MyInterface interface {
>     Cowboy
>     Canvas
> }
> Of course, it isn't likely that these two methods would have identical

> signatures anyway[...]

And in fact, given code like this (identical signatures with different
meanings), while the current language prevents you from declaring that
something is both a Cowboy and a Canvas, it still allows Cowboys to be
implicitly converted into Canvasses. Which emphasizes that when
you've got identical method signatures with differing meanings, you're
already in a very dangerous position.

[...]


> choosing which doc comment to use. One option would just be to mark each
> comment as originating with a specific embedded interface. Another would be,
> if the methods were ultimately originating from the same source, ie:
> type Sheriff interface { Cowboy; Arrest() }
> type Bandit interface { Cowboy; Raid() }
> type BanditSheriff interface { Bandit; Sheriff }
> then there isn't an issue either.

I'll just point out that this (as I see it) is the common case that
really cries out for this to be legal. It's frustrating that you
can't compose together two interfaces that each involve the same
interface: these are conceptually the *most* composable, in the sense
that they are already known to be the "same sort of thing". Okay, it
doesn't make sense that someone would be both bandit and sheriff... at
least by the rule of law... although it does happen in real life.
--
David Roundy

Eleanor McHugh

unread,
Feb 22, 2011, 6:07:10 AM2/22/11
to golang-nuts

This isn't strictly speaking true. An interface defines a query regarding the behaviour of an object (i.e. is the compiled form of a duck-typing message send query which reflects the occasional coupling between messages in a given context) and as such ignores the degree in which several objects returned by that query differ. This is the flipside of a struct or other type where the focus is on full structural equivalence. Two points will always be structurally equivalent regardless of whether they exist at different locations in a plane, however two riders may have no structural elements in common at all.

So whilst it's certainly the case that any object fulfilling an interface can be guaranteed to respond to a given message this is not the same as these objects all being of the same type, rather there is a type isomorphism which is being captured by the interface but which could be considered a meta-type as there are problem domains in which an object might well move from one meta-type to another during its lifetime without necessarily changing its essential structural type.

The latter incidentally are the edge cases which duck-typed languages excel at. Solving similar problems in statically typed languages generally involves an incestuous relationship with a given runtime which is a nightmare to maintain (although a lot of fun to figure out).

A change to the specification of interfaces which allowed duplication of methods across several different interfaces might seem like a good idea on the surface, but in reality it's creating a wrapper that embodies *more than one type isomorphism* and whilst this might in some cases be desirable, in the general case it will lead to more complicated interfaces which become difficult to reason about as the number of compositions increases. Putting the smarts for that into the compiler may not even be possible even if it were desirable.

And anyway, duck-typing is at its most productive when it's applied with a light touch. At least I've found that the case during my years of Ruby coding.

Currently I find one of the best way to reason about interfaces (and duck-typing in general) is to lift the analogous case of measurement in quantum mechanics. There is a finite degree of information which any measurement will return, and that information is tightly coupled to both a particular property and the context in which the measurement is made. The more you weaken that restriction the less benefit you gain, both in terms of cost of performing the measurement (a reasonable consideration in a systems language) and in terms of the design flexibility which duck-typing provides.

As you rightly mention regarding your Cowboy and Canvas example, the muddle arises not from the underlying abstractions but from the semantics which have been used to describe them. Each interface in this case implements a different message, and that message should actually be parametric as each describes an action being performed on the object by an outside agency, or an action which the object is performing.

type Cowboy interface { Draw(w Weapon) }
type Canvas interface { Draw(l Line) }


type MyInterface interface {
Cowboy
Canvas
}

This would be a perfectly valid conceptual description (though invalid Go code) for a Cowboy who happened to also be a possessor of Body Art, but then again perhaps it would make more sense in that case for Canvas to be an embedded structural type, probably as a field called Skin, and for the method to actually be called DrawUpon to disambiguate it from DrawWith (for a pen or pencil) and Draw (for a firearm). That could still lead to ambiguity with a LoanAgreement where DrawUpon means something different, but that's the point of bounding all of these things by context.

Your other example also contains some very messy abstractions and should really be:

type Cowboy interface { Draw(w Weapon) }
type Sheriff interface { Arrest(m Miscreant) }
type Bandit interface { Raid(p Place) }

Where we would only ever be interested in whether our subject is a member of one of these duck-typed groups when attempting to perform the appropriate action. It is enough to know a Sheriff can arrest a miscreant to know whether or not it is type safe for the Sheriff to attempt to.

Of course it might be nice to know if the subject happened to belong to all three groupings at which point we can use a function to find out:

func IsCowboyBanditSheriff(i interface{}) bool {
if _, ok := i.(Cowboy); !ok { return false }
if _, ok := i.(Bandit); !ok { return false }
if _, ok := i.(Sheriff); !ok { return false }
return true
}

However such queries are generally of limited value in real-world systems which use duck-typing, as demonstrated by how rarely in message-passing languages you see code that relies on an explicit chain of responds_to? (or equivalent) method calls. It's much cheaper and less verbose to send the message/make the assertion at the time it's required and only worry about the overlaps for purely accounting purposes. Likewise where a message isn't supported, the runtime code location is nearly always the place that should be being dealt with. It may be an error, or it may just be a reason to exclude a given object from being processed at a given time.

What I'm essentially saying is that interfaces are not a tool for type composition, but rather for type decomposition. By designing them well you minimise the dependencies between structural types whilst maximising their reuse. The normal rules of low-coupling/high-cohesion apply here just as they do to methods on structural types, only now you have a new way in which to express those concepts which leverages type inference to create runtime flexibility.

In situations where you need a Cowboy you want a type that reflects that, and nothing more than that. However often we confuse the concept Cowboy (a struct possessing properties) with those of Rider, GunFighter and a whole plethora of other aspects of Cowboyhood defined by the actions they perform. Whilst all of these things are nouns the are really aspects describing a set of verbs which we consider unique (at least in context) to the objects which implement them.

If there is one addition I would like to see to the language spec, it's an analogue to interfaces which allows a type to be described in terms of a subset of the structural properties it possesses rather than messages it responds to. In the Cowboy example, the probability is that a lot of the code I want to write related to tattooing and body art is really about the concept of Skin. I can achieve something similar through interfaces using methods for getters and setters, but it conflates two essentially disjunct concepts. Being able to know that a given type actually possesses Skin and that that Skin is of a type (concrete, interface or property class) that my code knows how to deal with in a certain place at runtime would add a whole new layer of richness to domain modelling.

> Okay, it doesn't make sense that someone would be both bandit and sheriff... at
> least by the rule of law... although it does happen in real life.


Which is very much the point. When you're duck-typing you're not abiding by the rule of law, but by the rule of thumb. And rules of thumb should by definition do as little to be prescriptive as is required to solve a particular problem.

So when designing interfaces we should apply Occam's razor to hone them to the smallest definition possible and when the compiler kicks up a duplicate method error we should look for new abstractions which better capture the underlying functional isomorphism of the type-space we're describing.

Over the last year I've had to relearn a static mindset to make good use of Go's structural typing mechanisms, an effort that at times has been very frustrating but ultimately worthwhile, I would highly commend the same effort being applied in the other direction by anyone coming from a static typing background if you really want to understand interfaces. Do some duck-typing in a language like Ruby or Smalltalk and see exactly how building complex message filters really affects the readability, maintainability, reusability and performance of dynamic code.

As the filter complexity increases what's really happening is that we're reimplementing static typing in a dynamic context, and hence without the rigour and safety which a compiler would introduce to the process. This results in very brittle code. Whereas embracing the strengths of dynamic typing leads to much more robust systems where that dynamism is desirable. After a few months of really trying to get your head into the duck-typing methodology you may also find a few of your entrenched beliefs about type relationships questioned in ways which will improve your Go code.


Ellie

Eleanor McHugh
Games With Brains
http://feyeleanor.tel/
----
raise ArgumentError unless @reality.responds_to? :reason

Eleanor McHugh

unread,
Feb 22, 2011, 7:02:43 AM2/22/11
to golang-nuts
On 22 Feb 2011, at 11:07, Eleanor McHugh wrote:
<snip>

Just to clarify, I was using the editorial "we" and "you" rather than flaming anyone or being terse. I'm laid up with the flu and apparently my people skills are suffering as a consequence!

roger peppe

unread,
Feb 22, 2011, 8:03:53 AM2/22/11
to David Roundy, Steven, golan...@googlegroups.com, JONNALAGADDA Srinivas
On 21 February 2011 20:29, David Roundy <rou...@physics.oregonstate.edu> wrote:
> I'll just point out that this (as I see it) is the common case that
> really cries out for this to be legal.  It's frustrating that you
> can't compose together two interfaces that each involve the same
> interface:  these are conceptually the *most* composable, in the sense
> that they are already known to be the "same sort of thing".  Okay, it
> doesn't make sense that someone would be both bandit and sheriff... at
> least by the rule of law... although it does happen in real life.

it's for that reason, i think that you won't find many (any?) interfaces in
the Go source tree of the form:

type T interface {
IncludedType
AdditionalFunctions()
}

instead, it's good practice to define the additional functions in their
own interface and create another to combine them.

type A interface {
AdditionalFunctions()
}

type T interface {
IncludedType
A
}

the io package, for example, uses this approach.

Peter Froehlich

unread,
Feb 23, 2011, 11:24:40 PM2/23/11
to golan...@googlegroups.com
Sorry, couldn't resist after reading this thread.

http://gaming.jhu.edu/~phf/pub/jmlc-2000.pdf
http://gaming.jhu.edu/~phf/pub/scp-composition-2004.pdf

The first paper is really enough for the current debate. Enjoy.
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab

Paulo Pinto

unread,
Feb 24, 2011, 1:45:40 AM2/24/11
to golang-nuts
Very interesting articles. Thanks.

On Feb 24, 5:24 am, Peter Froehlich <peter.hans.froehl...@gmail.com>
wrote:
> Sorry, couldn't resist after reading this thread.
>
> http://gaming.jhu.edu/~phf/pub/jmlc-2000.pdfhttp://gaming.jhu.edu/~phf/pub/scp-composition-2004.pdf
Reply all
Reply to author
Forward
0 new messages