9 ms·
I'm not sure I understand why implicit interfaces are better than explicitly declared interfaces. (This is an honest question.) I do iOS stuff occasionally, and
by austinz 13y ago
I'm not sure I understand why implicit interfaces are better than explicitly declared interfaces. (This is an honest question.) I do iOS stuff occasionally, and in that world one of the main purposes of interfaces (in Objective-C, protocols) is to explicitly declare that some entity adheres to a contract - it promises to implement some pre-agreed-upon functionality that other entities can use. What do implicit interfaces buy you? Better static type checking? Less typing?
/* Also, from the blog:
> Nowhere in this code did we ever declare a relationship between MyType and Answer.
"Answer" isn't in the example. Was this something from an earlier draft? */
- spydum 13y agoIt seemed to me clearly a autocorrect from Awesome to Answer.
- latch 13y agoWith implicit interfaces you're not tied to a specific interface, but rather the method(s). If you have a type that implements Close() error, your type can now be used anywhere (include other people's code) which accepts an io.Closer. Add a MarshalText() (text []byte, err error) function and it can now be used anywhere that expects a encoding.TextMarshaler. Furthermore, your type can be used in someone's else code which has defined its own MarshalTextCloser interface. I believe this is what the OP means by emergent. You wrote a type that has two methods, within your own package called "goku.Sayan", and it accidentally satisfies the interface "picard.MarshalTextCloser" which you'll never know about. "Sayan" was never built to satisfy _any_ interface. You just needed a Close and MarshalText function, but none the less, "Sayan" satisfies 3 distinct interface (which could be represented by an unlimited number of names) I realise accidental sounds dangerous, but the only real risk is that the methods don't do what you think they should. But I don't see how that's different from explicit interfaces; in both cases the intent is only implied by the name. Overall, as the OP says, it's quite powerful when dealing with simple 1 or 2 function interfaces. I think most Go developers see this naturally emerge in their own code: they favor small interfaces specifically for this type of re-use.
- deleted 13y ago[deleted]
- pcwalton 13y agoI think the chances that people independently developing two separate libraries will accidentally give their methods exactly the same name, with the same types of their arguments in the same order and with the same return value, allowing this "emergent behavior" are very low in practice. Rather, Close() and MarshalText() are really people implementing explicit, well-known interfaces defined in the standard library. MarshalText() is a good example of this—I would have named it something like Serialize() if not for the well-known, named interface I explicitly wanted to conform to. The primary benefit that structural interfaces give you, IMO, is less typing. Which has plenty of value, don't get me wrong; it's awesome that Go has successfully reduced the amount of syntactic overhead needed to implement interfaces. But I think the benefits of it shouldn't be overstated.
- Retric 13y agoA better example of Emergent behavior might be something that implements A and B it also incitements C if C is a subset of A and B. Or if your refactoring and decide to split an interface so what was X is now Y and Z. Consider adding unkillable NPC's to a game. Rather than having special code to handle MOB's that you can't kill you remove HP's from the MOB interface and add a killable interface and now the type system helps you refactor your code by complaining when you try to harm something without HP's. At the same time you don't have to change any existing objects.
- pcwalton 13y agoMaybe, but I'm not so sure about that either. Consider the two possibilities: 1. A and B were defined in separate libraries. In this case, the arguments about the likelihood of two types happening to define two methods with the same name and signature apply equally well to the likelihood of the two interfaces having a common subset at all. 2. A and B were defined in the same library. Here, I suspect most library authors in a language with explicit interfaces would notice this and factor out the common methods in A and B into a separate interface that A and B derive from. You might argue that the library writer could forget to do this, but I think it's not much more probable than the scenario in which A and B have no structural subset because the common functionality has a different names, or has a different return type, or has the arguments in a different order.
- pron 13y agoPersonally I prefer Java's explicit interfaces to Go's implicit ones, but implicit interfaces have the advantage of allowing you define "partial interfaces" without changing the class hierarchy. For example, in Java, if an interface A declares methods foo() and bar(), and you think foo alone is useful, you can't define an interface B that declares only foo() and have all implementations of A satisfy it. The downside of implicit interfaces is that they preclude safe refactoring.
- im3w1l 13y agoWould be nice sometimes to be able to declare that interfaces are "extended by" or "implemented by".
- pron 13y agoYeah, well, frankly I don't think either approach makes much of a difference. They both work. If anything, the reasons I prefer Java (the language; there are other reasons to like, or dislike, the JVM), are exceptions, final field, and concurrent data structures. The last two are much more substantial than interfaces or exceptions, which make for a pretty cosmetic difference (I find both languages to be quite similar).
- azth 13y agoJava's `final` while useful for primitives, it is not really that useful for reference types, which constitute the majority of types. C++'s `const` is a much better solution. Better yet, both Rust and D have superior immutability constructs.
- danieldk 13y agoJava's `final` while useful for primitives, it is not really that useful for reference types, which constitute the majority of types. They are if you use them in conjunction with immutable classes, such as Guava's immutable collections. Unfortunately, immutability is only a promise and not typed. But in practice, having a member that is final and of an immutable class works well.
- rtpg 13y agoOne advantage I can think of is that it lets you program in a duck-typing style without having to really think about it, but still get static checks.
- midas007 13y agoIt's essentially duck-typing iOS Objective-C protocols, but closer to how RubyMotion basically makes them optional.
- skybrian 13y agoThe advantage is that it allows interfaces to evolve without having to muck around with setting up shared dependencies. In a language like Java, if library A and library B want to share an interface, you have to create library C for the interface and modify A and B to depend on it, and if you want to add a method you have to modify all three at the same time. Furthermore, changing an interface isn't backward compatible so the correct way is to create a new interface every time you make a change. In Go, they can both declare the same interface or even skip formally declaring it and just agree to implement the same methods. Furthermore, if one of them decides to implement a new method, the other can just copy the method signature and they stay compatible. This makes it a lot easier to coordinate API changes.
- quotemstr 13y ago> Furthermore, changing an interface isn't backward compatible so the correct way is to create a new interface every time you make a change. That's a feature, not a bug. Say we're using implicit interfaces and have library A that provides an interface Foo and an application B that uses A and passes a Foo to some callback system. Now A gets updated and adds a method to Foo, but existing B binaries still call A with an implementation of the old version of Foo, and things fall apart. Java's model (and COM's and MS-RPC's) model here is better because it enforces good interface hygiene: incompatible types get incompatible names. Then you can choose whether to support the old name as well as the new name, but you're at least consciously making that choice. D's model worries me because it feels like it's easier to accidentally break things.
- skybrian 13y agoYou're right that adding a method to an interface is still an incompatible change. If A declares an interface and B uses it as a function parameter, this means that A can change B's API by updating the interface. One solution is for B to redeclare the same interface (even if it's the same at first). Then when A adds a method, B's interface is unchanged but it's a compatible subset, so nothing breaks. Also, the specific form of breakage you mention can't happen in Go because Go has no binary shared libraries; all binaries are statically linked and libraries are distributed as source. If there's a compatibility break then a Go developer somewhere will get a compile error. With implicit interfaces you're very likely to be able to fix the problem yourself, without having to coordinate across separate organizations. The explicit interfaces in Java and especially COM (with its GUID's) were designed to make binary compatibility between shared libraries easy to preserve, so the designers made different tradeoffs.
- kyrra 13y agoOne fun minor point about implicit packages. Since Go strictly forbids circular dependencies between modules, you can declare an interface in module A that module B inherits without B ever knowing about that interface. Sure you could do some refactoring and create a shared third module, but this is a neat trick.
- aboodman 13y agoThe general answer is that it puts swapability of implementation in the hands of the caller, not the callee. Let's say I have a function A that depends on concrete type B, and that I only use a subset of B's functionality. Now say I decide, hey, I'd actually really like to be able to swap a different implementation of B at runtime. With traditional interfaces, I would have to either: a) define a subset of B's functionality as interface I and change B to implement I, or b) define a subset of B's functionality as interface I and define a wrapper that implements I in terms of B. If I choose (a) and many other callers end up doing the same thing to B, then B implements a mess of little micro-interfaces that it really should have no reason to care about. If I choose (b) then the implementation of A is way more complex than it seems like it ought to be. === One place this comes up all the time is testing: Object A relies on a subset of the interface exposed by service S, and service S is hard to create in test environment. With Go interfaces, A just defines an interface for the subset of S that it needs, and the test code for A implements that interface. S doesn't need to know anything about it.
- eridius 13y agoYou have to define a subset of B's functionality as interface I even with implicit interfaces. The extra step of formally declaring that B conforms to I is not an onerous burden. And in a properly-designed language, you don't even have to modify B, you can declare the conformance locally where you define I.
- aboodman 13y agoI've never worked in a language that allows you to declare some type you don't control adheres to an interface. Just out of curiosity and in case anyone else reading has the same question, can you list some? Coming from Java and C++, this is part of the value I see in Go's implicit interfaces.
- pcwalton 13y agoScala and Rust. Haskell, if you consider typeclasses interfaces.