It's not just "people who use it poorly". The point of an interface is to abstract over some details while guaranteeing others. If I am unaware of an interface, I don't know to avoid the names used in that interface, and I don't know to abide by the invariants assumed in that interface. That seems like it will bite people who've done nothing wrong. If I am providing a library, I can't possibly be aware of every interface anyone might define in code that uses it. I've no clue how frequently this will occur, in practice.
Haskell's typeclasses work around this by letting you define "how a type implements an interface" at either the definition of the interface or the definition of the type. (Or, strictly, anywhere else both are in scope - but that gets messy for a few reasons, so it's discouraged and GHC warns about "orphan instances" unless you tell it not to.)
If I am unaware of an interface, I don't know to avoid the names used in that interface, and I don't know to abide by the invariants assumed in that interface. That seems like it will bite people who've done nothing wrong. If I am providing a library, I can't possibly be aware of every interface anyone might define in code that uses it. I've no clue how frequently this will occur, in practice.
That's not a problem with Go's module system. If someone is using your library, and wants to use your interface in a particular package, that's fine. If they want to use another library, with another interface of the same name in another package, that's also fine.
If they want to use both libraries in the same client package, they'll have to locally rename one or both of the imports.
It's entirely possible that I just don't know enough about go's interfaces. Wasn't it explicitly stated up-thread that you don't need to name the interfaces you support? If that's the case, I don't see how you get around the possibility of a type seeming to support (because of what's defined for it) an interface that it doesn't (because those functions actually do other things - obviously or subtly).
Here's a (very contrived) example of what I think you mean.
Here's a container interface. It has a sort() method, which sorts the elements of the container.
Here's a different container interface. It's got a method that tells you what kind of concrete container you have, so you can understand how expensive actions like insert and sort are going to be. But the author was from England, and so called that method "sort".
You can see how that's going to (fail to) work out when you define a "sortable" interface...
Traditional (but even more contrived) examples include "draw" and "fire" doing unexpected things when applied to a gun as opposed to an image or an employee.
ok, ok. I think I'm understanding. Let me propose a middle ground.
First, you really have to split this up into two distinct cases.
The most powerful/useful is when you create an interface in your package for a structure in a different package. It's been years since I've done .NET or Java, but I remember frequently cursing a library developer (often someone at Microsoft or Sun) for exposing a concrete type instead of an interface. In this case, I don't see any downside. Creating a:
type Render interface {
Draw()
}
And then using my (or someone else's) Gun structure, is no different than using the Gun structure directly and expecting it to render itself to the screen rather than come out of its holster. The interface buys you the level of indirection, but whether you use the interface or the concrete type, it's up to you to know WTF Draw() does.
This first approach is well explained at [1]
In the second case, where a library expects an interface, I agree there's a risk:
// 3rd party package
package fastRender
type Drawable interface {
Draw()
}
func Render(d Drawable) {
...
}
// my code:
fastRender.Render(new(Gun))
I've lost some degree or type checking. Since my Gun's Draw doesn't explicitly implement fastRender.Drawable, maybe I'm trying to fit a square peg into a round hole. There's nothing the compiler can do to help either.
It sounds a bit like an academic concern to me. Is this something that's actually bitten people in the ass? How is it different than any method that takes primitives? I can pass a []byte of a bitmap to a Music player's Play(data []byte) method...
Note that I'm not arguing for Java. Haskell typeclasses covers both of these use cases (you can make a new type an instance of an existing typeclass, or existing types instances of a new typeclass, without touching any exsisting code) but requires someone to look at it and say "This actually implements the contract that interface assumes." They can be wrong about that, of course, but I think making it explicit helps.
There's also a subtle third case - I import two libraries. One of them defines Drawable, the other has something that looks like it implements Drawable. But it's just happened upon the same signature, and makes different assumptions. Maybe it even says it implements Drawable - but a different Drawable defined in a third library.
As I've said all along, I don't know how often these things are hit in practice. I think so far go development has been comparatively centralized, and so it might be seen less often than it would if it were used everywhere, but that might still be rare enough to not be a problem.
OK, now I understand. It may be possible to muck things up if you've got two different types (from different packages) that have the exact same signatures for all the functions defined in the interfaces.
It doesn't seem like a high probability that this would happen on accident, though it might be possible to contrive an example.
Certainly the larger an interface is, the less likely that someone supports all of it by accident. Of course, that's both good and bad - if the accidental support would have been correct then we're missing out, but I think most of the time we shouldn't count on that.
One could possibly force the issue by including an unused function with a deliberately unique name (supports_interface_foo, or maybe a UUID or both) as part of the interface. Can functions added later fill parts of interfaces? If so, this approach would make the situation roughly that of Haskell typeclasses. If not, then this approach makes the situation roughly that of Java interfaces. In either case, it might be appropriate to some pickier interfaces.
Thinking about it in the abstract, it seems to me that the probability goes up as interfaces get smaller/narrower (which apparently is a style that go encourages) and the codebase gets larger (which is go's target environment).
True. And yet... for a good method name in an interface, you want it to be as general as the interface could be. You want sort(), not sortVector(), because sorting is much more general than sorting a vector - you could sort a tree, if it can be ordered.
You could include the name of the interface as part of the method name, though. But the downside of that approach is, if another interface wants to re-use the same method, things get awkward...
Haskell's typeclasses work around this by letting you define "how a type implements an interface" at either the definition of the interface or the definition of the type. (Or, strictly, anywhere else both are in scope - but that gets messy for a few reasons, so it's discouraged and GHC warns about "orphan instances" unless you tell it not to.)