Interfaces are great
They're honestly brilliant. Interfaces allow for simple, elegant programming. Let's use Java to get a quick idea of how to use interfaces.
package mypackage;
public interface Edible {
public void eat();
}
Now, a Burger class to implement Edible.
package mypackage;
public class Burger implements Edible {
public void eat() {
System.out.println("Burgers, yum!");
}
}
This means that we can use our Burger class wherever we see Edible, such as in method arguments!
The problem with interfaces
But let's say that we are using a library, and that library has a class called Salad. Here's what Salad looks like:
package theirpackage;
public class Salad {
public void eat() {
System.out.println("I'm being healthy!")
}
}
So Salad has an eat() method, but because the other library doesn't even know about Edible interface exists, it obviously can't implement it. This problem has definitely been encountered before, so let's talk about how Go has fixed it.
How Go fixes interfaces
With how Go interfaces work, you don't need to declare an interface implementation. If you implement the proper methods, you implement the interface. This is very prominent in Go, and is a key feature. Let's use our previous example.
First, let's remake our Edible interface.
package mypackage
type Edible interface {
Eat()
}
Now, let's make a Burger struct that implements Edible.
package mypackage
type Burger struct {}
// How to define a method in Go
func (b Burger) Eat() {
fmt.Println("Burgers, yum!")
}
Notice that we never acknowledged Edible anywhere while we made the Burger struct. This is because any type that has an Eat() method associated with it is considered Edible. This means that even types from other libraries can be Edible, as long as they have an Eat() method!
This means that even this Salad struct from a different package is Edible.
package theirpackage
type Salad struct {}
func (s Salad) Eat() {
fmt.Println("I'm being healthy!")
}
This shows just how awesome Golang is. I highly recommend watching their video, A Tour of Go, which shows all of the brilliant features that Go has to offer.
EDIT: I really like the discussion on this post! One of the key things that I didn't mention here is that interfaces in Go should be used to describe simple patterns. Here is a clip from A Tour of Go that should hopefully describe how interfaces in Go should be used compared to how they're used in OOP.
Latest comments (46)
Not sure if the Go designers are just trolling or what... You're suggesting that the idea is that a class can implement an interface purely out of coincidence, and that's somehow useful in reality. But, if the interface was implemented accidentally, that means that the author of the implementing class didn't know what behavior the interface specifies for those methods (he/she haven't seen the API docs). So that's exactly the unfortunate case where you don't want to utilize implicit interfaces. If, however, the author of the class have implemented the interface on purpose, it's a big loss that this intent can't be expressed in the language (doing that in comments is obviously inferior; they aren't enforced or checked), as now the code is less self-documenting.
OK, there's a thin chance that both the method signatures and the specified behavior matches, purely out of blind luck, and not because the author deliberately implemented the interface. Given the certainly low chance of that happening, is it worth it to give up said self documenting capability? I mean that's quite a big sacrifice.
First of all - Go isn't object oriented. It doesn't have classes, it has structs. It doesn't have methods, it has functions that operate on structs. OOP interfaces describe what a class is, but Go interfaces describe how a struct behaves.
It's okay to accidentally implement an interface, there's no sacrifice. Sure, you might make a door struct that might have a
close()function, but in order to mess something up, you'd need to pass it into a method that clearly says it takes anio.Closeras an argument.Also, these exceptions are still caught at compile time. If you pass a struct that doesnt match a specific interface to a function that requires that interface, it will throw a compile-time error that specifies which functions are missing from the struct.
My point didn't go though. I wasn't talking about uncaught mistakes. My concern is about how self documenting the code will be, and for what was that sacrificed. Let me try again. If a type
Thas a set of functions associated that satisfy theIinterface, and also they behave as described in the documentation ofI, then most certainly the author ofT(or of the functions) has deliberately "implemented" theIinterface there. Right? I mean, just how often does such thing happen by chance? If almost never, then why do the Go designers want us to not state that intent (I'm implementingI) in the language? What's the actual, practical, every day use case for "implementing without stating it"? Surely it's not that Go developers often get extremely lucky and so can avoid adapters.Structural subtyping is a terrible approach. It means Go Interfaces can only abstract over a type's structure, but not its laws/semantics/algebra.
For example, not every type with a
combinefunction is a monoid (combinemust be associative). In Go, every type withcombineis automatically a monoid.It only conforms to the interface if it has the correct parameters as well. The examples listed don’t include type information, which was my oversight when trying to make it simple.
Also, interfaces in Go describe what a structure does rather than what it is, and they should be used as such. So something with a
combinemethod is usually aCombinerrather than something with a fancier name.Yes, that was precisely my point. They let you abstract over the signature of a function, but not over its laws. Therefore, Go's expressiveness is limited.
The word awesome is over used. Your main selling point is a developer doesnt need an in depth knowledge of the code base there working on. If developers are forced to work with an interface they will simply implement it. I would not want implementation of my interfaces spread throughout my code base. I think its a code smell. I want to know what my code implements.
Really the main point I was trying to make is that code from other libraries can be easily used in code you make. If you bring in a library which supplies an
HttpRequest, and you have an interface calledRequesterwhich has aRequest()method, as long asHttpRequesthas theRequest()method, it conforms to theRequesterinterface and can be used where you'd want it to be used.This is really cool. Would be awesome to see in more languages to be honest
Someone sent me a language on twitter that has a similar concept. They had both "traits" and "interfaces", where traits had to be explicitly implemented and interfaces would be more like Go.
That sounds like Scala. It has a type safe concept of structural typing (a lot safer than Go)
While structural typing is safer, the post pretty clearly explains the problems with it. The safety brought by structural typing isn't worth the benefits that it restricts.
That's a pretty broad statement. The best you can argue is a tradeoff for the language features that are right for the product/team/roadmap. I've work for years with static and dynamic languages. They both have strengths and weaknesses. That Go is duck typed is just not a selling point. Many languages do it. I would avoid comparing Go with statically typed, OOP, languages to be honest (unless you fancy a flame war). Go doesn't solve interface problems, it just has a different idea of what an interface is, and it's not that novel. Go should be considered when runtime performance is critical and a simple and effective concurrency paradigm is needed. Go is many things, but it will always have safety issues (maybe that is why the error handling seems to be, err, well).
Go is statically typed. If you were to pass a
Dogstruct (which can'tQuack()) into a function that takes aDuckinterface, you will hit a compile-time error.If I understand correctly Go isn't even strictly duck typed.
The type inference doesn't happen at runtime but at compile time, giving very clear compiler errors.
So I don't really see how this kind of typing can lead to runtime errors in Go's case
The only time it would result in a runtime error is when casting, as per every staticly typed language.
That's what I thought! So I really don't get all this duck typing trash talk some people are throwing around in regards to Go
Wow! Implicitly making a type conform an interface sounds like a really bad idea to me. I mean, what could possibly go wrong? Right?
I like the Swift approach way more where you can explicitly declare that an existing type conforms to an interface (called protocol in Swift) like this:
class Salad: Edible {}
No accidental conformance.
Or as the joke goes: if it walks like a duck and quacks like a duck it probably throws exceptions at runtime.
@Ruotger Deecke: Well said. The author of the article doesn't seem to realize the trouble Go programmers are gonna get in with this.
Let me make an example.
If I have an "Audio" object (parsed and instanced from a random file in a folder) and a "AnimatedGif" object (from the same folder), and both have a "Play" method, I can easily happen to pass the "Audio" object to the "AnimatedGifVisualizer" by error, because both classes have a "Play()" method.
This will work fine until the "Play()" method would be actually called, suddenly crashing everything. And you will NEVER found why. Replace this for any similar scenario using any other data types, locally or server side, and you get the same silent, untraceable crash. This implicit interface conformance thing is a debugging hell.
This is why Go will never compete with better designed langages like Swift.
Because while who designed Swift really thought about this when created Swift protocols, Go designers just throwed in features because those sounded nice at first sight, with no careful assessment of consequences.
If an error happens on your part, it isn't Go's fault for being designed badly, it's most likely yours. In order to crash the system in your example, you need to put your
Audiostruct in a place calledAnimatedGifVisualizer...In Go, methods have very clear meaning about what they do. If you see a method that's called
CloseThenExit(io.Closer)in an io library, would you really pass in yourDoorstruct which has aClose()method?Reading this, I’m thinking it could be called “The Problem with Duck Typing, and how Go Fixed it.” I don’t know how good code that was written without the knowledge of the existence of an interface will be at implementing it. But if you write code that expects an object that you can order, mix, and eat, then instead of enduring three “has such method” tests in every method that uses such an object (like you spend your time doing in if you do duck typing in Ruby), you can define an interface that turns those tests into a type declaration, and be done with it. I my opinion, that’s a fix for the ordeal that duck typing it in practice.
What happens if the interface changes? How would you even know that happen until runtime? If the interface is an internal one to your application no problem, just add tests to cover that code. But if you are dealing with a thrid party library and trying to conform to an internal interface, this could become a real pain point.
If the interface changes, you no longer implement the interface if your struct does not have the matching methods. This will most likely cause issues at compile-time, so you are able to catch it very early.
I think you would catch such changes at compile time?
Also, what about dependency management? I think managing dependencies is one of the main reasons for why interfaces exist in the first place (dependency inversion principle). Without explicit declaration of interfaces, how do you know upon which modules or libraries you depend? Do you have an easy way of figuring out on which interface(s) you depend, when you look at one struct/class/whatever-it-is in Go?
Interfaces should not be used to handle dependencies in Go. Go has features specifically for managing dependencies in the CLI with
go getand such. Go is built around open source, so it's able to use things like git to manage dependencies (import paths for third party libraries are actually the URL to get to their library from). There are also third party managers for people who don't like the way thatgo getworks, by putting dependencies in avendorfolder.There is a bit of responsibility on the library maker and user though. A library maker shouldn't change an interface without documenting it, just as the user shouldn't update a dependency without reading the changes. A change in an interface will still most likely result in compile-time errors, as Go does use a strong typing system.
Good post. Thanks.
I added these links on the twitter thread but i think that i would be useful to leave them here to!
The first comment is that the go interfaces remind me a lot on haskell's type classes which on quick search are compared a lot with classic interfaces
And upon reading on type classes i stumbled with a C++ proposed feature that the call concepts and that page gives a bit of insight into how's all this compiled statically.
Haskell type classes are syntactical. There is no runtime dynamic dispatch. Go interfaces are structural types, their implementations are always inferred, and their purpose is solely for runtime dynamic dispatch.
Interesting approach. Java people (myself included) would have a hard time with this one. It sounds like it would work well for standard/widely used operations (i.e. GET, POST, DELETE), but also the name collision could be an issue.
HOWEVER, to avoid name collision, one would just have to "know something exists" (so that you dont reuse a function name) in the same way you would in typing systems similar to java. So to me, it could be a worthwhile tradeoff.
Sounds like a really bad idea to me. What if a class has a method with the same name as an interface method, but with completely different semantics? If you want to use Salad as an Edible, just make an EdibleSaladAdapter, and you're done... Sure, that's more work; but at least it's safe.
If you see that a method takes a Closer ("Closable" in other languages), sure a struct for a door might have a Close() method, but is it really a good idea to pass it to a method like io.CloseAndQuit(Closer)?
In Go, it's usually quite clear what interfaces are intended to be used for, so while it's possible to pass something like a door or a cabinet into an io function, it's pretty clear that you'll get unintended consequences. It might not be as safe in a few select contexts, but you get the huge benefit of convenience that I've described above.
Haskell's approach is interesting as well, and not dissimilar from Go. In Haskell you can declare an interface (but really a typeclass), and then explicitly make a type "part" of that interface. It would be like declaring a class in Java, then declare interface, then you can define an implementation of the interface's method separate from the original class.
This has the benefit of not having to know which interfaces a type follows when you declare the type, as well as having to explicitly make a type to be a part of the interface.