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: Go 2: implement an interface based on function signature if method name is ambiguous #37280

Closed
beoran opened this issue Feb 18, 2020 · 5 comments
Labels
Milestone

Comments

@beoran
Copy link

beoran commented Feb 18, 2020

Background

I found the following criticism of Go language here: https://www.quora.com/What-reasons-are-there-to-not-use-Go-programming-language?share=1

Several points are mentioned, but the one that caught my eye is the following:

Types always implement interfaces implicitly; you just give your type methods that happen to have the same names, arguments, and return values as the interface in order to implement it.
Also, you can't overload functions or methods.
Neither of these two are particularly bad on their own. Indeed, either one on its own poses at worst a minor inconvenience. However, the consequence of having both in the same language creates the very real danger of being unable to use useful or possibly necessary libraries (e.g. your Go code needs to interact with some other service your company uses, and the only way to do that is with a Go library that team wrote).
The sneaky thing about implicitly implemented interfaces is that the interface's methods' names effectively transcend all namespaces. And that's particularly problematic due to Go's love of short, generic method names like Read() and New() and Create(). This is a global namespace name collision bug -- you know, the kind the rest of the software industry has been rid of for several decades.
For example, let's say I have two third-party packages, foo and bar. I want to make a type that implements both foo.Foo and bar.Bar.
foo.Foo is an interface that requires a method Copy() Foo,
and bar.Bar is an interface that requires a method Copy() (Bar, error).
Upon seeing this, I know I'm screwed. I can't overload a method called Copy() on my type, so I can never satisfy both interfaces. I simply can't continue, and for reasons that are entirely out of my hands. The best I can do is define a pair of types, one that implements foo.Foo and one that implements bar.Bar, and interveave their operation in some hideous, hacky way.

I thought this could perhaps be fixed by putting two structs that implement the two interfaces together in one struct, however doesn't quite work out nicely. See the following example:

package main

type Fooer interface {
	Foo() int
}

type Fooer2 interface {
 	Foo() string
}

type DoFoo1 struct {
}

type DoFoo2 struct {
}


func (DoFoo1) Foo() int {
	return 400
}

func (DoFoo2) Foo() string {
	return "Foo"
}

type DoFoo struct {
	DoFoo1
	DoFoo2
}

var _ Fooer = DoFoo1{}
var _ Fooer2 = DoFoo2{}
var _ Fooer = DoFoo{}
var _ Fooer2 = DoFoo{}

func main() {
}

https://play.golang.org/p/Q6Vwpb019-6

The compiler complains:

./prog.go:33:5: DoFoo.Foo is ambiguous
./prog.go:33:5: cannot use DoFoo literal (type DoFoo) as type Fooer in assignment:
	DoFoo does not implement Fooer (missing Foo method)
./prog.go:34:5: DoFoo.Foo is ambiguous
./prog.go:34:5: cannot use DoFoo literal (type DoFoo) as type Fooer2 in assignment:
	DoFoo does not implement Fooer2 (missing Foo method)

Proposal

In this case, there is no actual ambiguity, in because the signature of the two Foo functions is different. The Foo from DoDoo1 implements Fooer, and the Foo from DoFoo2 implements Fooer2. The code above should work. The compiler should automatically select the correct function based on the type signature of the methods to resolve the ambiguous name. Only if none of the candidates has required type signature, or if there are several with the required signature, then the compiler should raise an error. This would then also address the main criticism of interfaces I cited above.

This is not just a theoretical issue, there are indeed cases where it's hard to implement two different interfaces, normally from different packages, with one type, because there are name clashes.

Proposal template

  • Would you consider yourself a novice, intermediate, or experienced Go programmer?
    I am experienced, I have been using Go for 10 years since before v1.0.
  • What other languages do you have experience with?
    Batch files, Basic, C, C++, D, Java, Javascript, Lua, Objective-C, Oberon, Pascal, PHP, Python, R, Ruby, Shell scripts, Visual Basic, ...
  • Would this change make Go easier or harder to learn, and why?
    It would not make Go easier to learn, but also not harder. It is something a novice might reasonably consider that it should work.
  • Has this idea, or one like it, been proposed before?
    AFAIK, no.
  • Who does this proposal help, and why?
    This proposal would help people who like the person criticising go would like to satisfy two different interfaces which have methods that have the same names but different signatures. .
  • Is this change backward compatible?
    Yes, currently this is a compilation error. This proposal is to loosen an existing restriction.
  • Show example code before and after the change.

Before:
The code above can be made to work by selecting a member of the DoFoo struct to satisfy one of the interfaces.

var df = DoFoo{}
var _ Fooer = df.DoFoo1
var _ Fooer2 = df.DoFoo2

After:
The compiler automatically select the sub-struct that correctly implements the interface.

var df = DoFoo{}
var _ Fooer = df // gets assigned to df.DoFoo1 automatically
var _ Fooer2 = df // gets assigned to df.DoFoo2 automatically

This would also work for functions with interface parameters.

  • What is the cost of this proposal? (Every language change has a cost).
    The new compiler feature has to be implemented, documented and tested.
    • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
      The gopls tool may or may not have to be updated to know about this new feature.
    • What is the compile time cost?
      Low, the compiler has at do a few extra field look ups, but it should already have that data available.
    • What is the run time cost?
      None.
  • Can you describe a possible implementation?
    At compile time the compiler selects the sub-struct that satisfies the interface automatically. I think this should be relatively easy to implement.
  • How would the language spec change?
    "Interface types" needs to be amended to specify this new feature of the language.
  • Orthogonality: how does this change interact or overlap with existing features?
    It extends the current interface feature in a small but useful way.
  • Is the goal of this change a performance improvement?
    No.
  • Does this affect error handling?
    Not directly, apart from the fact that error is an interface.
  • Is this about generics?
    No.
@gopherbot gopherbot added this to the Proposal milestone Feb 18, 2020
@pierrec
Copy link

pierrec commented Feb 18, 2020

I have bumped into this when using 3rd party marshaling libraries such as msgp which defines generic names for its interface methods.

@ianlancetaylor ianlancetaylor changed the title Proposal: Implement an interface based on function signature if method name is ambiguous. proposal: Go 2: implement an interface based on function signature if method name is ambiguous Feb 18, 2020
@ianlancetaylor ianlancetaylor added v2 A language change or incompatible library change LanguageChange labels Feb 18, 2020
@ianlancetaylor
Copy link
Contributor

For a suggestion like this, please identify real code that this would help. This issue has been recognized as a theoretical problem in Go since the beginning. It has never been changed because the problem is rarely seen in actual code. It's very unlikely that we would change the language in this way without specific examples where it would make a difference.

See also this FAQ entry: https://golang.org/doc/faq#overloading. The rule of matching methods by name is very simple and easy to understand. Matching methods by signature is much more subtle.

@beoran
Copy link
Author

beoran commented Feb 19, 2020

I mainly made this issue because I read said criticism, where someone apparently did run into this problem. I personally have not encountered this yet, but I thought that maybe for some Google projects you had already encountered this problem. I wanted to see if other people had really encountered the problem, or if it is, as you say, largely academic. So far, one person has replied that that they did run into this problem when using msgp. I will wait for more feedback on this issue, and if no one shows up with more examples of this issue, I will close it. You may label it "hold" or so if that makes more sense.

For the record I would like to state that after more thought, I found a workaround involving some pointers to the top level object:

package main

import "fmt"

type Fooer interface {
	Foo() int
}

type Fooer2 interface {
 	Foo() string
}

type DoFoo1 struct {
   *DoFoo
}

type DoFoo2 struct {
   *DoFoo
}


func (d DoFoo1) Foo() int {
	return d.FooerFoo()
}

func (d DoFoo2) Foo() string {
	return d.Fooer2Foo()
}

func (DoFoo) FooerFoo() int {
	return 400
}

func (DoFoo) Fooer2Foo() string {
	return "Foo"
}


type DoFoo struct {
	DoFoo1
	DoFoo2
}

var _ Fooer = DoFoo1{}
var _ Fooer2 = DoFoo2{}

func main() {
	df := DoFoo{}
	df.DoFoo1 = DoFoo1{&df}
	df.DoFoo2 = DoFoo2{&df}
	var f1 Fooer = df.DoFoo1
	var f2 Fooer2 = df.DoFoo2
	fmt.Printf("%d %s\n", f.Foo(), f2.Foo())
}

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

Like this we can simulate "overloading" the different functions as is appropriate.

@ianlancetaylor
Copy link
Contributor

As mentioned above, the current language rule is very simple and easy to understand: methods are matched by name, and nothing else.

Without examples of real code that would be significantly helped, we aren't going to make this change. Therefore, this is a likely decline. Leaving open for four weeks for final comments.

@beoran
Copy link
Author

beoran commented Feb 25, 2020

Seeing the lack of interest in this issue, and that a plausable workaround is available, I will withdraw this proposal immediately to leave the Go team more time to work on more pressing issues. Thanks for your consideration.

@beoran beoran closed this as completed Feb 25, 2020
@golang golang locked and limited conversation to collaborators Feb 24, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants