Hacker News new | ask | show | jobs
by Svip 4241 days ago
I think the point here is implicit inheritance. There are many interfaces in Go, but you don't need to specify which interface you are implementing. So you can easily implement io.Reader and io.Writer among many others, without extending your type declaration for lines on end.

This means that many standard types in Go implements either io.Reader and/or io.Writer. That's neat. The implicit interface implementation is definitely a language feature, and the libraries are making good use of it.

(That is not to say, you could not do the same in other languages, but you would have to specify the interface they implement rather than just adding a method with a specific layout.[0])

[0] I am not remembering the right term right now.

2 comments

I suspect you will come to hate this feature. Just because an class happens to have a method called close() does not mean it works the same way as another class that also has a close method. An interface is much deeper than the prototypes of its methods, and pretending you can pattern match an API to whatever names a code author happened to pick is likely to lead to pain eventually ...
The very point of an interface is to decouple the caller from all the implementation specifically because it will work differently between implementation. If you expect two classes to implement it the same way, you an abstract base, not an interface.

Close is a particularly good example. One need only look at C#'s IDisposable to see that it does, in fact, work well. A mock might noop it, another class might close an FD, and yet another might make an RPC call.

I agree that interfaces tend to have fairly narrow family tree. And, by this narrowness, there's little ambiguity about what T GetById(id int) means. As the tree expands, which happens with implicit interfaces, ambiguity is more likely. Nevertheless, there's a fairly large common vocabulary that we'd all largely agree on. Closer, Reader, Writer, Logger, etc. Even in more complex ones, I see little risk of confusion, say, http.ResponseWriter. And, something that I've noticed from Go (which I never did in C# or Java), is the tendency to favor very small interfaces, which ends up being pretty awesome.

That aside, consider that implicit interfaces allow the consumer to define the interface. For example, you create a library that has a concrete struct called MyStruct with a method called DoStuff(). You define no interface because you don't need one.

I, a consumer of your library, need an interface because in some cases I'm using your MyStruct to DoStuff and in other cases, I'm using my own implementation. So I create an interface, define DoStuff(), and BAM!, your structure now implements my interface. I don't have to change your code.

Sure, the workaround is to wrap your structure in my own which implements the interface. But how, in this case, is the implicit interface not a huge win?

Maybe, as you say, it'll screw over people who use it poorly. For everyone else, I see no drawbacks.

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...

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.

I'll have to think about that.

Structural types, as opposed to nominal types?
Basically. But Go obviously also have nominal types.