Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: spec: allow embedding overlapping interfaces #6977

Open
adonovan opened this issue Dec 17, 2013 · 68 comments

Comments

Projects
None yet
@adonovan
Copy link

commented Dec 17, 2013

If you view an interface as a set of constraints on the implementing type, then
combining two interfaces (that are not mutually incompatible) such as:

  type I interface { f(); String() string }
  type J interface { g(); String() string } 

has a natural interpretation that is equivalent to an interface containing the union of
such constraints.  e.g. these should be equivalent:

  type IJ interface { I; J }
  type IJ interface { f(); g(); String() string }

but in fact the first is an error: "duplicate method: String".  This is
somewhat surprising.  Is there any reason not to permit this?  The set-union behaviour
is easy to understand, describe and implement, and it seems useful in practise when you
have overlapping interfaces describing different aspects of a type.

(I chose String() string since I've seen many users add this constraint to their
interfaces.  It could be any method though.)
@robpike

This comment has been minimized.

Copy link
Contributor

commented Dec 18, 2013

Comment 1:

Which String method gets called when I do
var x IJ // using first definition
fmt.Println(x.String())
The resolution of the ambiguity is why the second version of IJ works.
@adonovan

This comment has been minimized.

Copy link
Author

commented Jan 2, 2014

Comment 2:

But does that matter?  You could choose one arbitrarily and the effect would be the same.
@griesemer

This comment has been minimized.

Copy link
Contributor

commented Jan 8, 2014

Comment 3:

Labels changed: added release-none, languagechange.

Owner changed to @griesemer.

@leo-liu

This comment has been minimized.

Copy link

commented Jan 24, 2014

Comment 4:

There's no ambiguity at all. IJ is a interface but not a struct, so no real method is
duplicated.
@griesemer

This comment has been minimized.

Copy link
Contributor

commented Feb 27, 2014

Comment 5:

Status changed to LongTerm.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Apr 21, 2014

Comment 6:

Labels changed: added repo-main.

@gopherbot

This comment has been minimized.

Copy link

commented May 21, 2014

Comment 7 by SRabbelier:

Note that even if you factor out the common method into its own interface it doesn't
work:
type C interface { String() string }
type CI interface { C; f()  }
type CJ interface { C; g() } 
type CIJ interface { CI; CJ }
http://play.golang.org/p/r5yOykMn-a
prog.go:8: duplicate method String
@griesemer

This comment has been minimized.

Copy link
Contributor

commented May 21, 2014

Comment 8:

Re: #7: you didn't factor out the common method in CIJ - both CI and CJ have the String
method and hence the same issue.
Factoring out a common method set will work.
@leventeliu

This comment has been minimized.

Copy link

commented Jun 30, 2015

Say struct SI implements I, struct SJ implements J, and struct SIJ implements IJ,

OI := struct SI{} 
OJ := struct SJ{}
foo := struct SIJ{
    I: OI,
    J: OJ,
}

In this case, GO doesn't know which String() is the right method to invoke.
But if you explictly implement the common method(s) of the interfaces, it works.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Jun 30, 2015

@geterns You're example is confusing and syntactically incorrect (did you mean SI := struct { I }, etc.?). Either way, the proposal is about overlapping interfaces and the resulting interface - structs are not related to this except that a struct may implement an interface. How that interface came to be is unrelated at that point.

@leventeliu

This comment has been minimized.

Copy link

commented Jul 1, 2015

@griesemer Yes, I'm sorry about the mistask. My example is about combining interfaces with duplicate method(s) in a struct - by explictly implementing the common method(s) of interfaces for the struct, it works. However, combining interfaces with duplicate method(s) to get a new interface is still not working :(
Thanks for your reminding, they're not the same case.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Jul 1, 2015

@geterns This is a long-term issue and a language change (although backward-compatible). It's unimportant (though not hard to implement) and thus likely won't be addressed anytime soon. Regarding your other comments about combining interfaces in a struct: embedding in structs is different from embedding in interfaces - conflicts are only reported if access is ambiguous in the struct case.

@jdef

This comment has been minimized.

Copy link

commented Feb 29, 2016

I'd love to see this fixed. Interfaces is Go are pretty awesome and this is a fly in the ointment. I wonder what the original justification was for generating errors under these conditions? As long as the overlapping method signatures are identical I can't imagine why you'd ever want the compiler to flag this as an error.

@josharian

This comment has been minimized.

Copy link
Contributor

commented May 12, 2016

It's unimportant (though not hard to implement) and thus likely won't be addressed anytime soon.

FWIW, I just filed a dup (#15666) on behalf of a friend for whom this was causing considerable frustration.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented May 13, 2016

Perhaps we should try to address this for 1.8. It seems pretty straightforward and incontroversial. Anybody having good counter arguments why this might be a mistake?

@hasty

This comment has been minimized.

Copy link

commented May 13, 2016

Just to explain how this comes up in real life: I tend to abstract away the data layer from the business layer so a) I can easy make mocks for tests, and b) I can have flexibility in changing data providers, sharding data, etc. Typical interface might be:

package user

type Database interface {
    GetAccount(accountID uint64) (model.Account, error)
}

So then I have some other packages that want to be able to fetch accounts under some circumstances, so they say they require their Database to have all of user's Database methods:

package hardware

type Database interface {
    user.Database
    SaveDevice(accountID uint64, device model.Device) error
}
package wallet

type Database interface {
    user.Database
    ReadWallet(accountID uint64) (model.Wallet, error)
}

Then, I have some package that needs both of those packages, and its Database interface looks like:

package shopping

type Database interface {
    wallet.Database
    device.Database
    Buy(accountID uint64, deviceID uint64) error
}

And then, kablooey: Duplicate method GetAccount(accountID uint64) (model.Account, error)

@mdempsky mdempsky modified the milestones: Go1.8Maybe, Unplanned May 14, 2016

@awishformore

This comment has been minimized.

Copy link

commented Aug 8, 2016

Would be neat if this was fixed, since it seems straight forward and non controversial. I run into it regularly and have to resort to copy-pasting method signatures.

@robpike

This comment has been minimized.

Copy link
Contributor

commented Aug 9, 2016

I think it's OK but I find it all a bit confusing. The clarity of "no duplicates" is comforting.

@splace

This comment has been minimized.

Copy link

commented Aug 19, 2016

surely if two interfaces include the same sub-set of methods, that’s telling you there is a lower level 'thing' that should have its own interface, having this an error means smaller (flexible) interfaces, I’d have thought this was spot-on for Go, doesn't the problem come from inheritance thinking?

@cznic

This comment has been minimized.

Copy link
Contributor

commented Jul 11, 2017

Method sets are sets. This fact however does not imply that a method set must support set operations. Or all set operations. Or any particular set of set operations. Or that a method set must define those operations in the exactly same way as any abstract mathematical sets composed of any abstract members.

Some set operations are impossible to have for method sets or they have no obvious well defined/agreed meaning. There's nothing intrinsically wrong in defining the union operation of a method set to be applicable only to method sets having empty intersection. It's a design choice. I don't judge it as good or bad, I want to point out that the decision is legitimate. In the same sense as disallowing to assign an integer variable to a floating point one. Some languages allow that and one can similarly argue that, after all, they are both real numbers, one is in the superset of the other so why do we even have such restriction for assignment in some languages like Go?

@faiface

This comment has been minimized.

Copy link

commented Jul 11, 2017

@cznic Of course, you're right, it's not mandatory. However, I still argue that it's a bad choice. Not supporting proper union when there's literally nothing preventing it is, IMHO, unnecessarily limiting.

@faiface

This comment has been minimized.

Copy link

commented Jul 11, 2017

@cznic Oh and btw, the analogy with integers and floats is wrong ;). int32 can express values not expressible by float32 and int64 can express values not expressible by float64, so they're not in a superset/subset relationship.

@metaleap

This comment has been minimized.

Copy link

commented Oct 6, 2017

As we know, code-generation has become a popular pastime and widespread paradigm in Gopherland.. (partially because it is such an excellent "close-to-the-metal" / systems language), whether for Generics or infinite other uses. As such, currently writing a Go code generator transpiling from a let's say "highly expressive ML-style type system", I'm adding my loudest-possible vote to this proposal!

I don't think anyone is calling for or needs to allow "duplicate methods". Rather when embedding multiple interfaces that each embed the same interface, for the compiler to handle this intelligently. If duplicate methods result from different embedded interfaces, fine complain --- but if they originate from the same, as in my case (and the S.O. poster's linked below), it really isn't an "error" in a way. I'll boilerplate my way through this situation now, detecting multiple embeddings down the line manually and then rewriting them as they occur ---pesky business!---, but going forward there's little reason not to ease the pains for developers facing such perfectly realistic use-cases as this one. As you scale up from that toy example to the real world, it's easy to see that the top-voted answer there isn't a desirable approach. (Especially for us code-generators of course ;)

Just some anecdata for the "what would be the use?" questioners in the crowd 😀

@dsnet

This comment has been minimized.

Copy link
Member

commented Oct 6, 2017

Why does this issue have the "Needs Fix" label? It does not seem like this is an accepted proposal.

\cc @rsc, @ianlancetaylor

@griesemer griesemer removed the NeedsFix label Oct 6, 2017

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Oct 6, 2017

@dsnet Removed "Needs Fix". Before the proposal process was formally in place, we discussed this subject internally and came to the conclusion that there's no harm to "fix" this (permit it). Later we decided that we should hold on with any (even backward-compatible) language changes for now and save this for Go 2, and while at it also let it go through the proper proposal and (external) feedback process.

@splace

This comment has been minimized.

Copy link

commented Oct 6, 2017

I'm confused about the confusion (see my previous comments.), is it something to do with the way Java 'interfaces' work? having to explicitly implement them?

i would like to propose a way to help drive home the difference, and render this issue mute;

remove embedded interfaces.

that is, force writing them out. embedding interfaces is, apart from short hand, really only an aid to readability for big interfaces, (bad practice anyway), and this could adequately be handled using commenting.

auto removing from existing code seems non-hard. (this would be a shame aesthetically, but if it clears up anything that's worth it.)

@metaleap

This comment has been minimized.

Copy link

commented Oct 6, 2017

remove embedded interfaces, that is, force writing them out

Great way to ensure ever more Go code gets emitted by generators rather than hand-written.. 😀 what we have right now works well for developers that leverage the "type-class" (aka "interface") paradigm heavily: when then needing to change the embedded "interface" (method set) over time, they can do so just once (and the final implementers), rather than also all the embedders manually copying over signatures..

Why remove what already works quite brilliantly, except for this minor oversight?

It is immediately apparent that: we already have a "duplicate method detected" mechanism, and instead of "comparing the name and complaining", it can rather just as easily (one would hope!) compare the signatures before either complaining or quietly & helpfully discarding that clearly non-conflicting "redundant repeat mention".

@splace

This comment has been minimized.

Copy link

commented Oct 6, 2017

Why remove what already works brilliantly, except for a minor oversight?

BTW i'm happy with the way it is.

my proposal was really just to get someone thinking; "how can it be 'required' to have more flexibility in a feature, that isn't actually required itself?"

for me the fundamental benefit of go interfaces, not needing to be explicitly implemented, means you can/should make them the smallest possible sub-set, Java (and Java thinking in Go) tends to lead to thinking you need to compose interfaces , (but this is really not needed in Go.) so leads to big inflexible interfaces. With well designed minimal interfaces, i believe, this issue goes away, the extra flexibility, this issue wants, just allows more of the wrong thing. the fact that people hit this issue should be a bad sign for them.

i guess auto-generating Go from Java will just give you this, and then require a lot of work to de-construct the interfaces into, the now possible, simpler (more flexible/powerful) interfaces.

idea: is it possible to automate this simplification? seems all the information needed has been imported from the Java.

@metaleap

This comment has been minimized.

Copy link

commented Oct 6, 2017

Who mentioned Java to begin with? Not me 😀

@alandonovan

This comment has been minimized.

Copy link
Contributor

commented Dec 28, 2018

Russ (feb 13) asks for real-world examples. Here's the one that keeps biting me:

Starlark is a Python dialect implemented in Go. The base starlark.Value interface defines a bunch of methods that every value must implement, like its String, Type, and Truth, but additional interfaces define optional facets of a type. For example, a value x that satisfies the Iterable interface may be used in a for y in x statement, a value that satisfies HasFields may be used in a x.field expression, and a value that satisfies Mapping may be used in a variadic call f(**x). Often a situation arises in which one needs to test whether a value satisfies two interfaces, such as an iterable mapping, but the Iterable and Mapping interfaces both embed the Value interface, and thus contain overlapping sets of methods.

package starlark
type Value interface {
    String() string
    Type() string
    Truth() bool
    ...
}
type Mapping interface {
    Value
    Get(key Value) Value
}
type Iterable interface{
     Value
     Iterate() Iterator
}
package mypkg
type IterableMapping interface {
    starlark.Mapping
    starlark.Iterable // error: duplicate method String (et al)
}
@svenkanna

This comment has been minimized.

Copy link

commented Jan 4, 2019

I encountered the problem of embedded overlapping interfaces several times. I am surprised that an issue which is such a fundamental and straight forward and appears to be a bug rather than an enhancement has been delayed for so many years!!! BTW I am not talking about the issue of interface treating as a method set. I am more concerned about the issue that if an interface is embedded more than once in an interfaces, it should be acceptable even semantically as well. Here is an example from my side:

type IOrder interface{
}

type IConfirmedOrder interface{
IOrder
ConfirmedBy() IPerson
}
type IPurchaseOrder interface{
IOrder
GetVendor() IVendor
}

type IConfirmedPurchaseOrder interface{
IConfirmedOrder
IPurchaseOrder
}

If I am missing something that should be done in go's way of doing things, please help me with the right approach but If someone is going to suggest me that I should take out IOrder from either IConfirmedOrder and IPurchaseOrder, I am not sure if that is a solution and is an ugly workaround because if I have to pass IConfirmedPurchaseOrder, it should be possible with a single interface. If you see the merit, I request the community to resolve this issue (again, specifically the overlapping embedded interfaces part and not the general issue) at the earliest.

@dolmen

This comment has been minimized.

Copy link

commented Jan 18, 2019

Here is a concrete example using overlapping interfaces from the stdlib: fmt.Stringer and flag.Value.

var (
	_ interface {
		fmt.Stringer // .String() string
	} = flag.Value(nil)

	_ interface {
		flag.Value // .String() string and others
	} = flag.Value(nil)

	_ interface {
		fmt.Stringer
		flag.Value  // "duplicate method String"
	} = flag.Value(nil)
)

When defining a type I like to declare with such construct all the well-known interfaces implemented by the type as this is a form of documentation enforced by the compiler (and that I hope one day would be used by godoc). Too often I came back to that fmt.stringer/flag.Value conflict.

@npc-g

This comment has been minimized.

Copy link

commented Feb 21, 2019

Since people are giving real-world examples, here's mine.

I have a number of different server structs, each backed by a database interface. The interfaces share some methods in common, but also have methods only used by that server and not the others. In production, these are all implemented by the same struct, but I'd rather not have the servers know about that or about the methods used by the others.

There's an initialization package which brings up the various servers, and which takes in a configuration struct including the database implementation to use for the servers. This initializer doesn't expose the fact that there's multiple different servers being brought up and I'd like to keep it that way.

So what I'd like to do is something like

package initsrv
import (
  "srv1"
  "srv2"
  "srv3"
)

type DB interface {
  srv1.DB
  srv2.DB
  srv3.DB
}

type Config struct {
  DB DB
  ...
}

func Initialize(config *Config) { 
  s1 := srv1.New(config.DB)
  s2 := srv2.New(config.DB)
  s3 := srv3.New(config.DB)
  ...
}

But can't, because of this duplicate method limitation.

The other alternatives are:
a) Just hardcode the production type in the config.
b) Have Config take in both a srv1.DB, srv2.DB, & srv3.DB. I'd like to be able to split & merge the various servers at will without having to expose the changes to callers so this is sub-optimal. Also, the servers should always talk to the same DB instance in production and I'd like to enforce that here.
c) Copy/paste all the DB methods from srv1, srv2, & srv3 into initsrv.DB
d) Have some common interface with all the DB methods that's shared between all the packages.

I'd be interested in hearing other people's thoughts but it doesn't seem like any of those solutions is as clean as just being able to merge the interfaces.

@prakashpandey

This comment has been minimized.

Copy link

commented May 3, 2019

I have a real life example which requires this feature

I have different packages lets say package A, package B, package storage.
All these packages have interfaces like.

type StorageProvider interface {
     StoreUser(user *User)
     StoreFile(file *File)
}

package storage implement the interfaces of all packages which needs storage.

The problem is that two packages can have interfaces having the exact same function name and signature and this feature can solve it. Although currently, I have done work around to solve this.

@gopherbot

This comment has been minimized.

Copy link

commented Jun 12, 2019

Change https://golang.org/cl/181817 mentions this issue: design: add 6977-overlapping-interfaces.md

gopherbot pushed a commit to golang/proposal that referenced this issue Jun 12, 2019

design: add 6977-overlapping-interfaces.md
Updates golang/go#6977.

Change-Id: Ide58cf4a1e4b45f16f00d0bbe2f36f2077056e11
Reviewed-on: https://go-review.googlesource.com/c/proposal/+/181817
Reviewed-by: Ian Lance Taylor <iant@golang.org>
@griesemer

This comment has been minimized.

Copy link
Contributor

commented Jun 12, 2019

I have just submitted a detailed design doc for this proposal.

@bcmills

This comment has been minimized.

Copy link
Member

commented Jun 20, 2019

My one concern with this proposal is that it somewhat complicates documentation and IDE integration tools. Consider this situation:

package example

interface Door {
	// Close shuts the door.
	Close() error
}

interface Conversation {
	// Close ends the conversation.
	Close() error
}

interface Doorslam {
	Door
	Conversation
}

func buildASnowman() error {
	var d Doorslam = […]
	return d.Close()  // ← cursor is here
}

If I place my cursor on the call to d.Close() at the end and press my IDE's “show documentation for identifier at cursor” hotkey, what should it display? I think the proposal should at least provide some guidance here.

CC @ianthehat @stamblerre

@mdempsky

This comment has been minimized.

Copy link
Member

commented Jun 22, 2019

@bcmills Arguably the IDE could/should show both. Though also arguably picking one arbitrarily should be valid too, as the assumption is they're compatible.

Either way, agreed that at least giving recommendations seems appropriate.

@griesemer I think that does highlight that the impact it will have on go/types, which is probably worth clarifying since it's a user-visible change. In particular, assuming:

type A1 interface { A() }
type A2 interface { A() }
type A3 interface { A1; A2 }
  1. What does go/types.Interface.NumMethods return for A3? I assume 1, though I can imagine someone arguing for 2, so I figure it's worth confirming.

  2. Assuming NumMethods returns 1, which A() declaration does Method(0).Pos() refer to? Do we guarantee one method in particular (e.g., the first one discovered through depth-first search?), or can go/types arbitrarily pick one?

  3. Also assuming 1, do we provide any way to easily query for the dropped *Func values? Since we allow access to the embedded types directly, I don't think this is necessary. Users can readily compute the full set of *Func values themselves, and we can extend the go/types.Interface API later if appropriate.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Jun 25, 2019

@mdempsky Regarding your questions:

  1. A3 has one method, so NumMethods is the total number of methods in A3 which is defined as the union of the embedded and local methods. That number is 1 in this case.

  2. Method(0).Pos() should probably pick one method deterministically, if only for reproducibility, say of position information.

  3. I'd also argue that at least for now nothing else needs to be done here.

@adg

This comment has been minimized.

Copy link
Contributor

commented Jun 28, 2019

I'm not necessarily opposed to this change, but some feedback on the proposal: it does not contain any examples of real code that would benefit from this change. The Person/Employee example is contrived and doesn't feel (to me) like something anyone would actually write, and the io interface example is trivially solved with just a line of code.

What motivated the action on this issue?

@gocs

This comment has been minimized.

Copy link

commented Jun 28, 2019

I call this the diamond problem.

type A1 interface { A(); B() }
type A2 interface { A() }
type A3 interface { A1; A2 }

func foo(a3 A3) {
        a3.A() // error
        a3.A1.A() // ok
        a3.B() // ok
        a3.A1.B() // ok
}
@dolmen

This comment has been minimized.

Copy link

commented Jul 2, 2019

@adg See my comment above: #6977 (comment)

@alandonovan

This comment has been minimized.

Copy link
Contributor

commented Jul 2, 2019

@adg see also mine from Dec 28: #6977 (comment)

@adg

This comment has been minimized.

Copy link
Contributor

commented Jul 3, 2019

@dolmen @alandonovan yep I have read your comments. I'm suggesting that the proposal document should include such examples, rather than the hypothetical examples that are there right now.

@griesemer

This comment has been minimized.

Copy link
Contributor

commented Jul 3, 2019

@adg I'll update the doc.

@seebs

This comment has been minimized.

Copy link
Contributor

commented Jul 11, 2019

I just actually ran into this in a real system. Simplified a bit:

https://play.golang.org/p/ZBmO4Xpfiby

I sort of worked around this by making a WriteOnlyFoo interface, so Foo is a combination of ReadOnlyFoo and WriteOnlyFoo, but gosh that feels messy.

In this case, though, there's genuinely no overlap of methods coming from different interfaces, I just want to be able to compose things. In fact I'd be covered by a simpler version which allows duplication only at the whole-interface level -- not "another method which has the same name", but "two interfaces each embed the same underlying interface, and then another thing embeds both of them".

So the parallel example might be something like:

type ReadWriteCloser {
    ReadCloser
    WriteCloser
}

And if ReadCloser and WriteCloser were both spelled as [x]er; Closer, that would still work.

EDIT: But re-reading the proposal, I'm in agreement that it's probably better to just allow the overlap anyway, as long as signatures are identical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.