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.)
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
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.
@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.
@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.
@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.
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.
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.
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?
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)
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.
I think it's OK but I find it all a bit confusing. The clarity of "no duplicates" is comforting.
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?
@splace I think there are plenty of situations where the interfaces are not under your (the implementers) control and there's no way around but to manually create the "union" interface because the embedding is currently not allowed. See also the example above by @hasty .
More fundamentally, an interface is a method _set_, and adding the same thing twice to a set is usually permitted. This is in contrast to embedding in structs, which are really lists/groups of fields.
@griesemer i think there aren't any situations where interfaces aren’t under your control, you never need to import them because any struct with the right methods is fine (this is not inheritance.) and they can ALWAYS be refactored to not get this error.
looking at that example, interfaces are being used where structs are required.
not allowing duplicate methods just means forcing good factorisation, you can obviously allow it, but Go has the tendency to push back quite hard against bad ideas.
@splace I don't think this is a bad idea :-)
Using structs for my data access layer prevents me from stubbing out a DB for testing. Interfaces are the right tool for the job.
And extracting a sub-interface is exactly what this issue is preventing. In my example, user.Database _is_ the common sub-interface.
Didn't get to this for 1.8. Maybe 1.9.
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?
I am not strongly opposed to this feature, but since no one has attempted to answer this question, let me try.
An interface is not just a set of method signatures; it's a set of method signatures _and their expected semantics_. For example:
type A interface {
// Returns an error if the database could not be contacted, or (InvalidID, nil)
// if the record could not be found.
LookupID(name string) (int, error)
}
type B interface {
// Returns ErrNotFound if the record could not be found.
LookupID(name string) (int, error)
Foo()
}
type C interface {
A
B
Method()
}
var c C
What does c.LookupID
return when the record cannot be found? This is a silly example, but illustrates the problem. I had a hard time coming up with a better example because I don't see why the feature is needed. As @splace says, it seems better to define minimal interfaces that do not overlap, than to define large interfaces that are unioned. It's worth pointing out that @hasty's example doesn't suffer from this problem because it uses a "diamond" embedding pattern, where method overlap comes from the same interface being embedded twice. As a compromise, perhaps the feature could be restricted to "diamond" embeddings (although this adds complexity to the type system).
I don't think it should represent semantics.
Comments like these are, in my opinion, bad practice.
and their expected semantics
Interfaces don't explicitly store this information though.
There may exist two interfaces A and B that share a common method set; but one or more of those methods intend to do different things. If those two interfaces are then embedded into a third interface intended to represent the union of the two original interfaces, it is not clear what the intended behavior is.
I see the advantage of allowing this practice, is it mathematically pleasing and even practical in some situations discussed above. However, I agree with @robpike that it's confusing, to me the fusion of two interfaces sharing a method set comes with the burden of asking what happens in the situation below.
type(
IX interface{ X() }
JX interface{ X() }
// later, someone makes a change to IX
//IX interface{ X() error }
//JX interface{ X() }
IJX interface{IX; JX} // duplicate method X (as per current behavior)
)
The commented-out IX and JX have an incompatible method set, and this would break IJX even if interfaces behaved as sets. IX and JX now depend on each other as long as IJX exists, and their shared method signatures can not be changed. That seems too _C++ inheritancey_ for my taste.
@tombergan I think the problem with your example is that as far as the Go programming language's type system is concerned, every type that implements B
already also implements A
, even though A.LookupID
and B.LookupID
are semantically incompatible.
In practice, it's the Go programmer's responsibility to avoid scenarios like this where values are permissible by the type system, but logically incorrect. And in the same way, I think it's the programmer's responsibility to make sure that unioning overlapping type interfaces semantically makes sense.
In practice, it's the Go programmer's responsibility to avoid scenarios like this where values are permissible by the type system, but logically incorrect. And in the same way, I think it's the programmer's responsibility to make sure that unioning overlapping type interfaces semantically makes sense.
This seems like a good argument to not automatically merge overlapping interfaces. Only the programmer knows if two interfaces are semantically compatible, so only the programmer should be responsible for writing the merge function. Otherwise, consider:
package p
type A interface {
Foo()
}
package q
type B interface {
Bar()
}
package main
import ("p", "q")
type C interface {
p.A
q.B
}
Then someone changes p
:
package p
type A interface {
+ Bar() // incompatible with B.Bar()
Foo()
}
Now a change to an imported package has silently made the definition of C
ambiguous.
Only the programmer knows if two interfaces are semantically compatible, so only the programmer should be responsible for writing the merge function.
In your example, who wrote this?
type C interface {
A
B
Method()
}
I assumed the programmer. :)
The programmer didn't decide that C
was still valid after package p
was changed. The type system decided that.
If the programmer had instead written:
type C interface {
Foo()
Bar()
}
it would still be valid (as far as the Go type system is concerned) after package p changed.
But nothing actually changed, though. Bar()
is still implemented in the same way.
Maybe it doesn't do what package p
expected Bar()
to do, but this could have happened whether an interface is defined or not, if the implementation of C
just happened to have a Bar()
.
If the programmer had instead written:
type C interface { Foo() Bar() }
it would still be valid after package p changed.
Yes, exactly, so we don't need this new language feature then? :-) Except perhaps for "diamond" embeddings where there is no ambiguity.
But nothing actually changed, though. Bar() is still implemented in the same way. Maybe it doesn't do what package p expected Bar() to do
If we expand the example a bit:
import ("p", "q")
type C interface {
p.A
q.B
}
func DoStuff(c C)
How does a caller of DoStuff
know what c.Bar()
should do if that's not clear from the interface definition?
but this could have happened whether an interface is defined or not, if the implementation of C just happened to have a Bar().
Fair point. I still think that the proposed feature does nothing but add ambiguity, except for diamond embeddings as in your example (where there is no ambiguity).
Yes, exactly, so we don't need this new language feature then? :-)
We have lots of open "might be nice" issues, @tombergan. This one had been idle since August until your post three days ago. :-)
Is this one very important? I don't know.
It's easy to build up hypotheticals on each side. The hypothetical arguments raised so far might just as well be used against implicit interface satisfaction in general (and have been). The classical example of this in the old C++ debates is having abstract classes Graphical and Cowboy both with method Draw() and then someone creates a Graphical Cowboy by subclassing both and it unexpectedly shoots instead of rendering itself on the screen. In practice, in Go, there are other types involved and clear names chosen, so that this tends not to come up in practice. If we haven't had problems with people passing a Cowboy where a Graphical is expected, I don't expect problems with an interface { Graphical; Cowboy }
either. Hypotheticals are easy to raise but equally easy to discount.
Real-world examples would be more compelling. So far on this thread there is one real-world example of a case that would benefit and no real-world examples of problems.
I'd like to add a bit of color to @hasty's real world example above. He and I were having lunch and I asked him how things were going at his company, Go-wise. He said great overall but (1) compilation could be faster :) and (2) working around this particular problem contorted his package layout and added significant cognitive overhead.
Still only one data point, but the request (originally #15666, closed as dup) came from significant pain, not a "might be nice".
@rsc, fair point about real world examples. Note that @hasty's real-world example is a diamond embedding. That is the only real-world example so far. If the proposal is limited to diamond embeddings, I have no issues.
I see this proposal differently than implicit interface satisfaction. Go's type system lets package P define interfaces that are implemented by types from other packages imported by P. It would be impossible to write such programs if Go did not use implicit interface satisfaction. In contrast, the current proposal does not allow writing new programs -- it's already possible to type out the union of two interfaces when there is a naming conflict. The current proposal only makes some programs very slightly shorter. That is part of the argument against this proposal. The rest of the argument is: (a) it appears to have very limited use, and (b) it introduces possible ambiguity. The exception is diamond embeddings, where (b) is not an issue.
It's hard to find real-world counter-examples for language features that don't exist yet. How about a different language? C# apparently considers this problem sufficiently important that classes are allowed to specify which interface each method implements. C# is a bit of a kitchen sink, so it's hard to know whether the feature was added due to real or merely perceived concerns, and I don't program in C# regularly so I cannot comment anecdotally.
In any case, I only chimed in because this feature appeared to be accepted for Go 1.9 even though no one had raised what seemed like the obvious counter-argument. I've argued against this proposal more strongly than I originally intended. I'm happy to drop the arguments if there is still widespread agreement that this proposal is a good idea.
If this proposal is implemented, could we also improve the traceability of duplicate method error messages in the compiler?
The program below, which to my understanding is invalid even under the proposal, does not emit a useful-enough message to debug an interface composed of a set of interfaces where one method's signature has been inexplicably changed:
tmp/sandbox563595814/main.go:5: duplicate method A
package main
type IAAB interface{
A()
IAB
}
type IAB interface{
A() string
B()
}
func main() {}
The new message should clarify that the method has a signature mismatch rather than being a duplicate method, since the proposal implies methods may be duplicated.
It would be interesting to see the union of many interfaces sharing a common method, where one of those subinterfaces (perhaps defined in another package) changes the signature of that common method. How does the maintainer of the union detect where the incompatibility occurs?
package main
type Dup interface{
X()
X() int
X() float64
}
duplicate method X
is concise and valid. How would the same clarity be achieved for a set of interfaces? When a concrete type has a signature mismatch the error (pasted below) is very clear. https://play.golang.org/p/2nlnD8TYOL
tmp/sandbox698729363/main.go:14: cannot use ab literal (type ab) as type IAB in assignment:
ab does not implement IAB (wrong type for A method)
have A(complex128)
want A() string
All of the real-world troubles I've had with this issue have been limited to diamond embedding, so I'd be happy if just that were allowed. As has been noted, it's completely unambiguous.
I still suspect that the broader approach of "interfaces are a set of methods, so treat them as a mathematical set" would be fine in practice, but perhaps it should be tabled.
This bypassed the proposal process (because it is very old) but we should probably save it for the eventual broader consideration of features during the Go 2 process.
I have one more example where fixing this would help. Consider we want to have ReadSeekCloser
, so naturally, we define it like this:
type ReadSeekCloser interface {
io.ReadSeeker
io.ReadCloser
}
But, this doesn't work, because of "duplicate method".
I find this unnecessary. Not only does it force me to duplicate method definition, it's also inconsistent. Considering that an interface is a set of methods, it's natural to expect, that C now contains all types that implement the union of the methods of A and B.
type C interface {
A
B
}
However, this doesn't always work, because when A and B share some methods, this results in the "duplicate method" error. However, I guess no one has a problem with the union of sets which share elements, right? Well, interfaces are sets of methods, so I consider this inconsistent.
Edit: Also, as many other pointed out, there's no ambiguity here. No type can implement two methods of the same name and each interface variable only ever contains one concrete type.
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?
@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.
@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.
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 😀
Why does this issue have the "Needs Fix" label? It does not seem like this is an accepted proposal.
\cc @rsc, @ianlancetaylor
@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.
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.)
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".
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.
Who mentioned Java to begin with? Not me 😀
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)
}
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.
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.
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.
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.
Change https://golang.org/cl/181817 mentions this issue: design: add 6977-overlapping-interfaces.md
I have just submitted a detailed design doc for this proposal.
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
@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 }
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.
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?
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.
@mdempsky Regarding your questions:
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.
Method(0).Pos()
should probably pick one method deterministically, if only for reproducibility, say of position information.
I'd also argue that at least for now nothing else needs to be done here.
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?
@adg See my comment above: https://github.com/golang/go/issues/6977#issuecomment-455492997
@adg see also mine from Dec 28: https://github.com/golang/go/issues/6977#issuecomment-450440293
@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.
@adg I'll update the doc.
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.
@griesemer My reading of your proposal is that
type i interface { m() }
type j interface { i; i }
is valid. Is that consistent with your understanding / intention?
FWIW, cmd/compile, go/types, and gccgo all currently allow:
type e interface {}
type f interface { e; e }
Change https://golang.org/cl/187518 mentions this issue: cmd/compile: refactor expandiface
Change https://golang.org/cl/187519 mentions this issue: cmd/compile: allow embedding overlapping interfaces
@mdempsky Yes, that is at least the current thinking.
I will update the design doc, eventually.
@adg Per your comment, started adding more examples (e.g., https://golang.org/cl/188197).
Change https://golang.org/cl/188197 mentions this issue: design/6977-overlapping-interfaces.md: add another example
Change https://golang.org/cl/187978 mentions this issue: spec: add an example of a trivially invalid interface
Change https://golang.org/cl/190258 mentions this issue: go/types: allow embedding overlapping interfaces
Change https://golang.org/cl/190378 mentions this issue: spec: allow embedding overlapping interfaces
Change https://golang.org/cl/191257 mentions this issue: go/types: allow embedding overlapping interfaces
Reading through this entire thread, it seems to me that the proposed solution is simple enough, but the problems it seeks to solve are somewhat contrived. (Which is to say, some of the examples which have been presented in favor of this proposal seem to me personally to result from earlier engineering decisions in need of repair, rather than from the lack of the proposed language feature/behavior.)
The idea of being able to compose interfaces without worrying about overlapping methods, providing the signatures match, does seem to be a good one.
Below I'll respond to some of the examples that have been given.
First, replying to @tombergan 's comment (not a real example) https://github.com/golang/go/issues/6977#issuecomment-279081456
...consider:
package p type A interface { Foo() } package q type B interface { Bar() } package main import ("p", "q") type C interface { p.A q.B }
Then someone changes
p
:package p type A interface { + Bar() // incompatible with B.Bar() Foo() }
Now a change to an imported package has silently made the definition of
C
ambiguous.
I wish to point out that adding an additional method to an exported interface is by definition backward incompatible, so this could not possibly happen "silently" no matter how overlapping interfaces were handled. Package p
must have changed major versions in order to add a new method to an exported interface.
Actually, I can't imagine a single real-world scenario that would ever call for the expansion of an exported interface. Imagine if fmt.Stringer
had a method added. It wouldn't matter what that method were. Even if it were DoNothing()
with no return value, it would break all code that used fmt.Stringer
. Adding Bar()
to p.A
above is comparable. Surely the very point of defining interface types in the first place is to avoid the need for such breakage; you can instead define additional methods on concrete types without modifying the interface types fulfilled by existing code. If you have ever encountered a real-world case where an exported interface type had to be expanded after it was already in use by other packages/developers, I would be very interested to see it.
(Besides which, any concrete type fulfilling main.C
could only have one Bar()
method defined on it regardless of the contents of p
and q
, so there would never be any ambiguity anyway. But I think someone else pointed that out already.)
Regarding @npc-g 's database use case here: https://github.com/golang/go/issues/6977#issuecomment-466134479
If there are multiple database implementations each with methods unique to those databases, and you want to deal with a single interface which only contains the methods in common between the database, then why not use an interface type that only contains the common methods (and define it centrally)?
If instead you have an interface type in each separate database package (as you illustrate), then by definition you must have all of those methods to fulfill any particular DB interface type, and if you were to define a single interface as a UNION of all those interface types (as your code snippet would appear to illustrate), then you would have to implement ALL the methods from ALL the packages' DB interface types before you would have any single concrete type which would fulfill the union interface (initsrv.DB
in your example). So, I don't see how that could possibly work, even with the proposed feature, because none of the DB types from the other packages would actually fulfill initsrv.DB
.
You seem to want an intersection between method sets, rather than a union, which is not what is discussed in this proposal at all. Please let me know if I have misunderstood your example.
Regarding @svenkanna 's example here: https://github.com/golang/go/issues/6977#issuecomment-451374772
Despite the appearance, this example is not actually an illustrative real-world use case, both because of the peculiar interface type names (which do not clearly communicate their purpose—not illustrative) and especially because the overlapping part of the method sets, interface type IOrder
, is shown as the empty interface (therefore there would be no conflict from any method name).
I can't imagine a single real-world scenario that would ever call for the expansion of an exported interface.
I think you're imagining _library_ packages, meant for import by numerous, unknown clients. Most packages in the wild have very few clients, and are confined to a single project.
Despite the appearance, this example is not actually an illustrative real-world use case, both because of the peculiar interface type names (which do not clearly communicate their purpose—not illustrative) and especially because the overlapping part of the method sets, interface type
IOrder
, is shown as the empty interface (therefore there would be no conflict from any method name).
The example is related to order management domain. IOrder is not an empty interface in the real use case. You can imagine IOrder definition something like the below:
type IOrderItem interface {
Product() IProduct
Quantity() float64
Rate() floatt64
}
type IOrder interface{
ID() int64
Items() []IOrderItem
}
Change https://golang.org/cl/200221 mentions this issue: design/6977-overlapping-interfaces.md: various updates
This is a bit late, but @adonovan the example you have given (or at least the explanation for the need of overlapping interfaces) is not very strong: @adg has pointed out in a code review that one could easily solve the problem you're trying to address with a sequence of type assertions.
Anyway, I have removed that example again from the design doc in favor of this one which seems a bit stronger (see https://golang.org/cl/200221).
On Mon, Oct 14, 2019 at 6:19 AM Ad van der Veer notifications@github.com
wrote:
Would this allow for a type switch statement in which a case combines two
overlapping interfaces to present the union interface in the case body? e.g:type A interface{
Foo() string
}type B interface{
Foo() string
Bar() string
}func main() {
var v interface{}switch vt := v.(type) {
case A,B:
// A and B overlap on Foo(), make it available on 'vt'
println(vt.Foo())
}}
No, this is an independent (or at least a separate) concern. But you could
define a type AB interface { A; B } and then use AB instead of A, B in the
switch case and then it would work.
>
>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/6977?email_source=notifications&email_token=ACBCIT6RAKVGPICWIO3RW63QORWXJA5CNFSM4BJ37CV2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBEUEMQ#issuecomment-541671986,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ACBCIT3LP7ZXUNCKUGZGD73QORWXJANCNFSM4BJ37CVQ
.
@griesemer looking at L157 here: https://go-review.googlesource.com/c/proposal/+/200221/3/design/6977-overlapping-interfaces.md#157
I think this should say package device
based on device.Database
being aggregated in shopping.Database
@skyscooby Indeed. Looks like device
and hardware
are used instead of just one of them in the text. Will fix. Thank you.
As a reminder, we introduced a new process for these Go 2-related language changes in our blog post https://blog.golang.org/go2-here-we-come. The Go 1.14 development cycle is now over and it’s time for the final decision.
The feedback here has been almost entirely positive. For example, see the emoji on https://github.com/golang/go/issues/6977#issuecomment-501137744.
@adg asked for stronger examples, which have been added to the design doc.
Given the uniformly positive feedback and how small a change this is, it seems like this is a likely accept for Go 1.14. Leaving open for a week for final comments.
If accepted, we'll just need to close the issue - the implementation is landed already per the language change process.
- rsc for proposal review
But does that matter? You could choose one arbitrarily and the effect would be the same.
Type-equivalence does not always mean semantic equivalence. There are plenty of interfaces with additional behavior requirements that the compiler is not aware of. In a perfect world, all behavior would be specified in the type system, but this is not the case.
In a pretty good world, developers would register their interfaces through a common query system and immediately deprecate colliding interfaces for distinct interfaces. Maybe something like Hoogle for Go interfaces?
Meanwhile, a mechanism for disambiguation would be nice.
Leaving open for a week for final comments.
Something I thought of: what's the expected behavior when go 1.13
module A depends on go 1.14
module B, and B makes use of overlapping interfaces or changes its exported interfaces in a way that cause A to make use of them? Two specific cases:
Is it okay for B to export a defined interface type that uses overlapping interfaces, and for A to consume that type? (I assume yes, though I think this currently doesn't work.)
Initial state: module B exports type X interface{}; type Y interface{}
and module A has type I interface { B.X; B.Y }
. If B upgrades to go 1.14
and adds M()
to both B.X
and B.Y
, should module A build even if it stays on go 1.13
? (I'm leaning towards no, but not sure.)
- Is it okay for B to export a defined interface type that uses overlapping interfaces, and for A to consume that type?
It should be, yes: since the method sets are identical and the go
version for B
permits overlapping interfaces, it should not be a breaking change for the maintainer of B
to replace
interface FooBarrer {
Foo()
Bar()
}
interface BarBagger {
Bar()
Bag()
}
interface FooBarBagger {
Foo()
Bar()
Bag()
}
with
interface FooBarrer {
Foo()
Bar()
}
interface BarBagger {
Bar()
Bag()
}
interface FooBarBagger {
FooBarrer
BarBagger
}
- […] If B upgrades to go 1.14 and adds M() to both B.X and B.Y, should module A build even if it stays on go 1.13? […]
No. The package combining the two interfaces is A
, and the go
version for A
does not permit that package to embed overlapping interfaces.
The maintainer of B
has made a breaking change by adding a method to an exported interface, so the fact that this breaks A
should not come as a surprise.
Specifically, the change to either interface would also break A
if they had previously defined:
type I interface {
B.X
B.Y
M() someOtherType
}
What about:
package a // go 1.14
type a1 { interface A() }
type a2 { interface A() }
type A = interface { a1; a2 }
package b // go 1.13
import "./a"
var _ a.A
That specific example won't work, because the go 1.13
directive is in the go.mod
file and relative imports aren't allowed in module mode. 😛
But putting that aside, the var _ a.A
should be allowed, because the package that actually combines the two interfaces is a
, not b
.
That specific example won't work, because the go 1.13 directive is in the go.mod file and relative imports aren't allowed in module mode. stuck_out_tongue
Ack. I don't understand Go modules well enough to know how to concisely write a test case that uses them directly, so I resorted to pseudocode.
But putting that aside, the var _ a.A should be allowed, because the package that actually combines the two interfaces is a, not b.
This is my inclination as well.
I've submitted b7d097a. As of master, cmd/compile applies these rules for overlapping interfaces:
-lang=go1.14
or newer.No objections raised, so accepting.
Already implemented, so closing.
Change https://golang.org/cl/214240 mentions this issue: compiler: permit duplicate methods from embedded interfaces
Change https://golang.org/cl/216997 mentions this issue: doc/go1.14: document overlapping interfaces change (update release notes)
Change https://golang.org/cl/217134 mentions this issue: go/types: unexport Checker.LookupFieldOrMethod
Most helpful comment
I have just submitted a detailed design doc for this proposal.