Hacker News new | ask | show | jobs
by rqk9j 2460 days ago
You might be defining interfaces on the wrong end. Interfaces should be defined where they are used, so searching for implementations becomes a non-issue anyway.

https://github.com/golang/go/wiki/CodeReviewComments#interfa...

2 comments

The problem of finding implementations of an interface is harder if they live in separate packages, isn't it? Why would it be a non-issue?

I'm not saying that we should define them together, the article makes sense in its recommendation. I'm just saying that the GP also seems correct to me, there is a problem in finding interface implementations in Go that would be lessened by explicit implementation (though I hope go pls will fix this on the tooling side).

I’ve never seen this problem, when did it come up?

As OP says usually the consumer defines an interface, so they aren’t ‘used’ in lots of places, and are rarely changed independent of the code that uses them (which could easily use its own interface instead if required). Typically the implementer changes (which doesn’t matter as long as it still conforms), not the interface.

The exception would be interfaces provided in the std lib, like io.Reader, which are widely used elsewhere but those won’t change at this point.

The way I see it, you normally have 3 places where an interface is 'used':

1. there is code written to depend on the interface

2. there is code written to satisfy the interface

3. there is code written to pass some implementation to a function using the interface

The recommendation, which I agree with, is to define the interface in area 1, which is always a specific package. Areas 2 and 3 can be spread all over the code, in different places. io.Reader is a good example - there is a single io package, and most code which does something with an io.Reader is there. Implementations exist in many places, some in the standard library, some in your own code base. Then, all over your code base, you may have code which takes some specific implementation of an io.Reader and calls some function in io and passes that specific Reader to it. This shows that even if you respect the recommendation in the code, you can still end up with many places which 'use' an interface in some sense.

Now, to give a more specific use case, say I have defined an interface which takes a slice, but I expect that the slice is used in a read-only manner. I want to audit all existing implementations to ensure that they respect these semantics, and I can only do that by hand since I can't express this in Go's type system.

Another simpler example is that I know what function I want to call, and what interface it takes, and now I want to find out what implementations exist for that interface, to see if I can re-use one of them or if I need to create a new implementation.

I'd contend that code in places 2 and 3 shouldn't care about the interface except insofar as they violate it (for which they'll get a compile error and be able to fix the problem).

The only place that really cares about the interface is place 1 - where it is used, where it defines a contract for the types passed to a function which they must conform to.

Re your examples, if you want a slice to be used in a read-only manner, pass in a copy of the slice, it's the only way to be sure in future too - those use-sites might be modified later anyway so one check doesn't really help. Interfaces are not intended to limit this sort of use, nor are they a good mechanism to do so. Re the existing implementation, I can't imagine losing track of types such that they'd be a good fit for a given problem, but I wouldn't know about them, interfaces are usually small so it's just a question of one or two functions...

While it's of academic interest to see which types conform to which interface (and people have written little tools to do this), I don't find it's really a limitation in real-world go code, it just doesn't really come up, and I like that interfaces are explicitly one-way, you don't declare them on the implementation side for good reasons.

In my own real world code, this problem comes up daily or more - I have a concrete class, but is it through an interface for mocking purposes. When I am trying to follow the code which uses the interface, I want to follow what galena to the values passed into the interface, through the real (or sometimes mock) implementation. Hopefully gopls will some day be able to do this, but it is a constant annoyance at the moment that I have to break my flow and search for the implementation class by hand.

I am honestly considering just getting rid of the interface and finding some other hackish way to mock the code, just because of the improvement in ease of following the code.

But again, it's easier to solve your problem if you state it differently. Why do you want to know who implements an interface?

In a statically compiled binary, the compiler will complain at every conversion if the interface is not satisfied.

The compiler will only complain if the intended semantics are fully expressed in the type system, which would be difficult to do in Haskell or Idris, and is completely impossible in Go. If I maintain both the consumer and the producer of the interface, it is my job to find all implementations of that interface and ensure that they respect the intended semantics.
>Interfaces should be defined where they are used

Generic interfaces (e.g. Reader) could be used in 20000 different places, packages, repos, etc...