The first example is not emergent behavior. And it has nothing at all to do with the implicit nature of interfaces. It is in fact a pretty straightforward consequence of the fact that functions are first-class types. Because they're types, you can newtype them, and you can implement interfaces on the newtype. This should work in any language that has interfaces and first-class functions.
For reference, here's the exact same thing in Rust:
// Define trait Foo, this is the equivalent of an interface
pub trait Foo {
fn foo(&self, x: int) -> int;
}
// Define a newtype called FooFunc
pub struct FooFunc(fn(x:int) -> int);
// Explicitly implement the trait on FooFunc
impl Foo for FooFunc {
fn foo(&self, x: int) -> int {
let &FooFunc(f) = self; // unwrap the newtype
f(x)
}
}
// This is a method that uses a Foo, analogous to http.Handle()
pub fn doFoo<T: Foo>(f: T) {
println!("foo(42) = {}", f.foo(42));
}
// Here's our function that we want to wrap
fn MyFoo(x: int) -> int {
x+1
}
fn main() {
// Call the function, just like http.Handle using http.HandlerFunc
doFoo(FooFunc(MyFoo));
}
And just like Go, this ends up being free at runtime.
Yeah, it looks like their solution is just creating a function object whose interface-required method calls the object itself (or at least that's my OOP-centric interpretation). You could do something like that in Javascript (minus the interface, obviously):
var f = function(){ console.log('called'); };
f.callMe = function(){ this(); };
I wonder if this is a useful pattern in JS. Can't recall ever seeing it in practice.
It's useful in go, because it means you can trivially turn a bare function into a type that fulfills an interface, which makes your function more flexible - it can easily take either a type with a method or just a method.
Which many would prefer because it's implicit documentation. Never knowing what interfaces a go type implements sometimes bugged me. Functions, defined in interfaces often look different in style and paradigm so sometimes they stand out and made me wonder why they would implement such functionallity in this way, not knowing that the definition comes from a hidden interface. It also takes away the ability to quickly look up all die functionality because I would have to know the functions names and what interface they are defined in. Imagine a interface changes and you would have to find all the types you have to change.
This is exactly the problem you face in generic programming in C++ (and would have been fixed with concepts). You don't get to typecheck against the possible interfaces some object implements, and you have to be very good at parsing compiler diagnostics - assuming your compiler reports good enough diagnostics to let you figure out the problem.
I would gladly trade for the verboseness of making these relationships explicit if it let me typecheck and refactor with ease.
It is common to say "Foo implements the Fooer interface" in the doc on the function. Also, looking up what interfaces a type implements is 1.) impossible (depends on what other interfaces exist in the universe), 2.) pointless - you shouldn't care what interfaces it implements, you should look for functions that you want to use with your type.
You're also missing one of the best parts of implicit interfaces - you can use other people's types in your own functions with interfaces that didn't even exist when they wrote their type. To do that in languages with explicit interfaces, you have to write ugly wrapper classes around the other person's type for zero benefit.
Haskell typeclasses are an interesting way to achieve the same effect explicitly - if your module implements/returns/requires some type, then I can make your type "be" a member of my typeclass (pretty much equivalent of your class implementing my interface) without touching your module or creating a new type; the explicit declaration can be made afterwards to join together two modules that didn't know that the other existed but have types that are similar enough.
Only if the type already defines the methods for that interface. In e.g. Rust you can define whatever interface (trait) you want for external types. No need for wrappers.
Ahh, that's cool, I didn't realize Rust could do that.
Though it does still require you to write the thing that tells the compiler their type implements your interface, right? Not a huge deal, but it's still essentially like writing a wrapper (though perhaps not as huge?)
Also, it sounds like you can't implement a third party trait on a third party type, so you can't use bill's cool log writer in joe's cool log roller.
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? */
With 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.
I 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.
Cowboys draw their guns; artists draw their pictures; an artist cowboy draws pictures and guns. It happens more than you think, and name hygiene is a big deal in programming languages.
The problem goes away with less ambiguous naming conventions (e.g. DrawGun() and DrawPicture()), but overloading is so gosh darn convenient, and thinking of unambiguous names is quite difficult. C# is really the only mainstream language that gets this right with explicit interface implementations.
Yes, that's a drawback to implicit interfaces I had forgotten: it fails to solve the cowboy-shape problem (and worse, there's no way to fix it in the future without introducing a whole new kind of interface: the language seems kind of stuck with this issue).
Agreed about C# multiple interface handling. I'd really like to see something like that added to Go at some point.
In fact it's probably not that far away from where Go is now. Go already has something very similar for implicit composition / mixins. Now, if you do something like this:
type Foo struct {
Bar
}
Then Foo automatically has the methods of Bar, e.g. Foo.bar() instead of Foo.Bar.bar() although the former is really just shorthand for the latter.
If you then have:
type Foo struct {
Bar
Baz
}
Then assuming that Baz also has the method bar() then you now need to be explicit about it and call Foo.Bar.bar() or Foo.Baz.bar() because the compiler doesn't know which one Foo.bar() means.
Of course, this is slightly different to interface disambiguation, but there's no reason why you couldn't have two function definitions with the same name and some added interface qualifier - the functions would then only be callable if the object is cast to an interface. In Go, casting to an interface is a bit like boxing, as it creates a separate vtable for that object's methods to match the interface ABI.
Hence, you could have say:
a := Foo(foo)
b := Bar(foo)
Now a has a vtable entry for Foo::foo() -> a.foo() and b has a vtable entry for Bar::foo() -> b.foo().
On the other hand, there would be no foo.foo() method unless one were declared separately without any interface qualifier.
I think this is more of a problem in languages that also encourage having deep class hierarchies with virtual dispatch as their main mechanism of polymorphism. It's in those languages that you tend to see argument-less functions like Draw() because the entire concept of what it is for that object to be drawn as well as where has been baked in to one large composite object.
In languages that place more emphasis on type deduction and composition it's much more likely you're calling draw to tell it what to draw on, and the aggregate type information once you introduce an argument or two is actually quite rich.
Which leaves you with things like Close(), which, let's be honest, we don't need 30 interfaces that all just have Close in them just because the base language didn't happen to include one. The concept is simple and relatively unambiguous and almost always has to do with some kind of disposal of resource.
That's just how natural language is; languages based on objects will suffer since that can't rely on full natural language power even if based on naturalistic concepts. Any duck typed system simply doesn't care much about name meaning at all.
This is not an issue in any kind of real code written by a human being. When would you ever pass a cowboy object into a screen painting function?
You'd never do this:
cb := cowboy.New()
screen.Paint(cb)
Anyone who wrote that code would have to be insane, and anyone reviewing the code would tell the person they were insane.
Also, would you not test your code? Like, at least run it and make sure it doesn't do crazy stuff? Maybe write some unit tests?
There can always be edge cases where functions don't work precisely as you expect, but that happens without interfaces, too. Pass an array into a sorting function and it turns out to sort by string length not alphabetically.... the programmer bears some small responsibility for actually understanding what the functions that he calls actually do.
I agree that it's generally not such a big problem. It may be useful to draw a cowboy on the screen, but it's more likely that confusion could arise with methods that do something related, not something entirely different.
For instance, in JDBC there is a PooledConnection.close() method and a Connection.close() method. Both Connection and PooledConnection are interfaces. They are semantically related but it's not a polymorphic relationship. PooledConnection does not extend Connection.
PooledConnection.close() must always close the actual physical connection to the database because PooledConnection is used by the connection pool itself. The Connection interface is used by the application and hence Connection.close() may close the connection or return it to a connection pool.
JDBC drivers usually come with implementations of both interfaces where the Connection implementation wraps an instance of a PooledConnection implementation. Arguably, being able to formally declare which interface a particular close method belongs to is beneficial in cases like this.
I think this is really a problem that exists beyond interfaces. For example, even if you had these two concrete types, and both had close methods, how would you know when to call one close versus another and what they do? This kind of confusion is a perpetual problem... and I don't think Go interfaces make it any worse, really.
I would know because the concrete types formally reference an interface, and the documentation of the interface would tell me more about the intended semantics.
You're right that simply having this formal reference doesn't solve all problems that could possibly arise. But there's one form of confusion that is much less likely to arise.
As is so often the case, more flexibility comes with more opportunity for screw-ups.
I can see you point in general about usually this not being a problem and I agree with you (I come from Python here so duck-typing is the name of the game if you wish). However I was trying to think where this might break (playing devil's advocate), I can think of specialized domains with lots of code referring to same terminology.
Ok like we have Close(). One could have Commit(), or MtxDeterminant() or Launch() things like that, where just a lots of people using same terminology in a domain are just bound to create collisions (on individual names!). Now high likely it is that a combination of those would be hit, not very sure on that.
A 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.
Maybe, 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.
Consider cases where A is defined in a library that B depends on.
You might have Name and Location where Location is (latitude longitude altitude). Now you want to map things, while existing objects Trucks with Name and Location are easy you want also map stuff with a name and address. You can get a latitude and longitude from an address, but not altitude and adding meaningless altitude is IMO a bug waiting to happen.
I know what you mean; it doesn't [thankfully] happen for complex interfaces, but I maintain that it's useful (OP possibly overstates) as a composition of smaller interfaces. It's completely possible to write a type which satisfies http.ResponseWriter, both technically and in spirit, unintentionally while still being useful.
I've used it around a Stats interface which required a single method: Statistics() map[string]string and have been able to hook up otherwise independent code into a common logger. Speaking of loggers, the fact that the built-in log package doesn't expose an interface has always been an annoyance to me specifically because I can't build my own code and know that it'll satisfy a log.Logger interface in other projects.
> I 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
> But I don't see how that's different from explicit interfaces; in both cases the intent is only implied by the name.
Go's approach is theoretically more error prone. It relies on names matching in a way that can happen by coincidence. The important part of explicit interfaces here is referencing a common declaration site, that could e.g. give informal requirements in the form of comments. I doubt Go's approach is particularly error prone in practice, though.
Personally, I think it would be better if interfaces were explicit, but the list of interfaces on a type was open, allowing you to add interfaces over any type anywhere, and not just in the place where the type is defined.
type foo = ...
and
impl hashable(foo) = ...
impl hashable(int) = ... // yep, adding to a builtin type
This is the way that Myrddin currently handles it.
This is similar to how Rust works. You can implement new traits (which are what Rust calls interfaces) on pre-existing types. The rule is that either the trait or the type must be defined in the current crate (e.g. in the current library).
This means that my library can define a new trait and implement it on, say, int. Or I can define a new type and implement a pre-existing trait on it. But it prevents me from implementing pre-existing traits on pre-existing types, which is important for the compiler to be able to answer the question "does type A implement trait B" without having the answer change when a new library is linked.
I do not have that restriction; Traits are instead scoped by module, so a trait implementation in one module is not necessarily available in another, unless it is exported explicitly. Now, on the other hand, I doubt Rust has the same kind of bugginess I have :)
Structural typing (sometimes referred to as "duck typing") has a few problems that make me unconvinced about the benefits.
First of all, I agree that the accidental argument is not very convincing (two developers writing a method Launch(), one that launches a football and another that launches nuclear missiles).
However, we lose two important things with implicit interfaces:
- Code readability. It becomes much harder for a human to interpret what types are actually implemented.
- More importantly, it severely limits the tooling available since the compiler has a lot less knowledge about the types you are dealing with. Automatic refactorings are all but impossible with structural typing.
Overall, I really don't see the harm in saying explicitly "My type is called Account and it implements Serializable and Entity" as opposed to me having to guess by reading all its methods and also having to remember which methods are necessary to be an Entity or a Serializable.
Duck typing and structural typing are not the same thing. In structural typing you are still making assertions about the type, in duck typing you are deliberately avoiding them. The opposite of structural typing is nominal typing, where in types are named and explicitly associated with the objects they represent.
Particularly, this is extremely wrong when talking about structural typing:
> - More importantly, it severely limits the tooling available since the compiler has a lot less knowledge about the types you are dealing with. Automatic refactorings are all but impossible with structural typing.
The compiler has as much information in structural typing as it does in nominal typing. The type of an object is determined at compile time either way, it's just that in structural typing Account implements Serializable and Entity because a subset of its methods match the definitions of Serializable and Entity, not because you said it does.
Personally 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.
Yeah, 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).
Java'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.
Yep. The same applies with Scala too (with `val` vs `var` declarations). I still don't like the separation of type declarations with the actual implementation; they should be the same, which is what Rust and even C++ got right.
Sure, but at that point you're not really writing Java any more. If you decide to use Checkers many of Java's plus points (in particular the huge library ecosystem and all the development tools) no longer apply.
Well, all reference types are eventually made of primitives -- and arrays. The problem with Java's final is that there are no final arrays. This is planned to be resolved in Java 9.
The 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.
> 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.
You'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.
The 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.
You 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.
I'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.
Rust is the language I'm thinking of right now. If you declare a trait (what Rust calls interfaces), you can also implement that trait on any type.
The basic rule is you can implement a trait on a type if you declared either the trait or the type yourself. You just can't implement someone else's trait on someone else's type.
But Obj-C also allows for the same thing. You can declare @protocol (interface) conformance in a class category, and you can declare your own category on someone else's type.
One 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.
> That's it: a tiny adaptor that makes a bare function satisfy an interface. How is that possible?
No, why is it necessary?
In Java 8:
public interface Handler {
void serveHTTP(ResponseWriter w, Request r);
}
public class MyApplication {
public static void myHandler(ResponseWriter w, Request r) {
w.write("An even easier webserver?");
}
public static void main(String... args) {
Http.handle("/", MyApplication::myHandler);
}
}
No adaptor needed!
Or even:
public class MyApplication {
public static void main(String... args) {
Http.handle("/", (w, r) -> w.write("An even easier webserver?"));
}
}
And please bear in mind that this is Java, the slowest-moving and least sophisticated of contemporary languages! Why does Go require so much more boilerplate than Java?
It's just a design decision of the http library, that its router by default accepts an interface and not a function - so internally you need this little adapter to make the function comply with that interface. I also loved this little trick when I noticed it.
But if the HTTP library would directly accept a function, you could then just pass a function without that trick. Go doesn't mandate you use interfaces for callbacks (like Java did until 1.8), you can also define an API that accepts functions directly, provided they have a certain signature.
Also, the standard library hides this little trick from you, and just exposes the HandleFunc method, that lets you register plain functions as handlers. as in this example: http://golang.org/pkg/net/http/#ListenAndServe
If I understand the Java 8 example correctly, the Http.handle accepts an interface as well (the Handler interface with a single method). Java compiler is smart enough to automatically convert functions into objects satisfying interfaces with exactly one method, while Go compiler isn't.
Usually you don't want the compiler to be "smart" in this way, because it creates other headaches. For example, is a temporary object created each time you do this? What impact will that have on GC? If I compare an object of this interface with a "fake interface object" generated by the compiler, will it always be the same for the same callback? What is the call stack going to look like when I use jps? Seeing another <<anonymous>> line in there wouldn't give me the warm fuzzies when debugging.
I was not a fan of auto-unboxing for ints, for the same reasons.
There are a lot of good features in Java 8, but I'm not a fan of this particular one. Even if it saves a line of typing.
BTW I don't think the compiler is "smart enough" - but the Http API has 2 (probably more) overloaded methods for each type you may want to pass to it. Go doesn't have method overloading by design. If that's something that is a must have for anyone, Go is simply not for them.
Yeah, well, knowing the Go philosophy, it makes sense not to have the compiler solve ambiguities like that, especially since a function can have its own methods.
Edit: To clarify I mean if you had a Java X app deployed how hard is it to upgrade to Java 8? One thing I like, maybe naively, about Go is static binaries.
It's pretty trivial. 1. Upgrade JVM on server (i.e. change 1 line of puppet config) 2. Build with Java 8 (i.e. change target option in your build file) 3. Deploy.
I mean sure, you have to install the new JVM, but if you don't have a system in place for making changes to your servers then you've got bigger problems than which language you're using. And honestly I think the JVM with its classpath approach solves the library problem better than most platforms; upgrading the JVM binary occasionally is no great hardship, and aside from that everything is just jars, dynamic but not getting in the way of each other unless you want them to. What happens when security holes are found in Go libraries, do you have to recompile anything that depends on them?
Personally, no, not yet. My current employer has been dragging its feet over Java upgrades, so we're only just transitioning onto Java 7.
However, most of the work involved in doing that is paving the road so that version upgrades are faster. I would anticipate that moving from 7 to 8 will be much faster than moving from 6 to 7.
The second one just looks like a hack. You've defined a useless function to make an interface not implementable. It would look cleaner with a keyword in the interface definition.
First of all: I agree. There are many things in Go that feel like a hack. For example, it is common (and idiomatic) to use a map with empty values when you want to use a set (unordered and unique values), because the map's keys are a set. I personally dislike this, and some aspects of Go are like this.
However this blog post is about how good core language design leads to unplanned patterns that are useful, like HandlerFunc and the private interface hack.
One person's "unplanned patterns that are useful" is another's "this is a potentially dangerous hack that could lead to all manner of headaches in any non-trivial program with even just a few hundred lines of code, let alone thousands or millions."
The sweet spot for Go is in the hundreds to a couple of thousand lines of code. Unix philosophy and all that. Leave the monolithic MLOC monstrosities to Java.
People write MLOC monstrosities in Java because they can. You get some boring financial topic and some sub-par programmers and they'll write as much garbage as the language can possibly sustain.
These things are a testament to how safe and simple Java is as a language.
Not having massive crappy code bases is a negative sign in terms of how reliable and easy to understand a language is.
None of the things you mentioned are "potentially dangerous hacks." Using maps as sets is common in many languages and there's nothing wrong with it. Adding a guard method to an interface is pretty clear and it works well, as well.
Yes, it's best to use the smallest tool (orthogonal language and libraries) for the job because it's less to test and maintain, and more likely to be correct.
The function doesn't have to be useless. He uses a small and simple example to illustrate it. However the more common example of this is an interface with public piece your consumers can interact with and a private piece that only you can interact with.
> However the more common example of this is an interface with public piece your consumers can interact with and a private piece that only you can interact with.
Also known as...a class. Honestly, this is why classes exist: they are able to support both public and private interfaces, while interfaces and type classes are only able to support public interfaces. The closest we get to this essential aspect of OO programming in the H & M world is via existential types.
I dont know, these feel to me like very simple features ("functions are objects", "functions take a final-type") .. nothing really "wow". The wow-factor is, I guess, that Go can actually do it.. which seems to me a little masochistic. ("Wow my underpowered lang. isnt so bad!").
Welcome to the world of languages with type systems that don't suck. It's going to be a fun ride, and if you think this is cool, you're going to be really excited by the other languages we've got in here.
It continually amazes me that people are willing to put up with Java.
If you are putting Go in the category of languages with a type system that doesn't suck, you probably need to study more languages.
Go has a few nice things going for it but its type system was clearly designed by people who stopped paying attention to type theory and compiler design in the late 90's.
I wouldn't say it's exactly brilliant - but it's just missing all the really cool stuff, rather than being buggy. It does what it's trying to do, and then stops. It's a bit like C in that regard (and unlike C++, Java, and C#, which tried to do something much bigger and didn't get it right).
It's also a bit like working with first-order propositional logic - well-founded and with its own unique simplicity, but you can't say everything with it and have alternatives.
So yes, I'd say that the type system of Go doesn't suck. It's not very good, and there are so many better things out there (rust, I'm looking at you), but it's a long way from being a disaster.
Go's type system stays out of the way and adds some useful amount of safety. That's more than I can say of any mainstream language. No reason to tear it down just because it doesn't go further.
Go's type system stays out of the way ... No reason to tear it down just because it doesn't go further.
By that line of reasoning, C is a great language. Better type systems exist to prevent many types of bugs. That Go ignores decades of good PL research is a valid point of critique.
> Better type systems exist to prevent many types of bugs. That Go ignores decades of good PL research is a valid point of critique.
Instead of beating around the bush, would you care to name a few essential, modern features the type system of Go is missing and which could prevent bugs in everyday programming tasks?
No, these are not esoteric Haskell features, but have been around in ML since the 70ies. Also, the claim that Go's designers want to keep the language simple is not a strong argument. A language such as ML is simple.
Also, many old(er) imperative languages adopted some of these type system features, such as Ada, C++, Java, and D.
Yes, most of these things are pretty old. Why can't you accept that there are people out there who do not think that these features are unequivocal good ideas or necessary?
I am pretty sure Rob Pike does not fall into that category. My impression is that he is not all that familiar with these features from the ML-derived family of languages. Do you have a quote from him to the contrary (where he specifically rejects these features)?
The Oracle JVM is pretty versatile and has been beaten on in production on an extremely large scale, as much as M$ CLR.
Java as a language though was a primary response to over-correct and over-optimize for secure, correct, safe code based on the history of C's shortcomings. There's are many other lessons that have been learned since.
Java is really hard to beat apart from specialized formal methods verifiers (coq, CVC4), strongly-typed functional languages Haskell and similar derivatives for embedded industrial systems. If you're involved in safety critical systems, you should be using the simplest and easiest to understand formal methods tools as possible. If something's too esoteric, fewer people will be able to double-check the work.
For wider participation, it's a tradeoff to use one of the more popular languages that lack correctness aspects because of the absence of a learning curve.
The trouble with implicit interfaces is maintainability: suppose Awesome gets a func PerformAwesomeness() added to it: what then happens to the code that thinks myType is Awesome?
Not too bad when it's all in one source file, but get a few megabytes of code spread through a few thousand source files, and you've got a formula for chaos and heartburn.
Not sure what you are getting at. If you add something to the interface of Awesome, then any call site which passes a thing not having a PerformAwesomeness() method will fail to compile.
Which means that every time you change an interface you have to compile everything (given that you don't explicitly call out the Awesome interface anywhere, so you don't know whether any particular source file will require the Awesome interface without compiling it).
If you don't have parametric polymorphism, you only have two options to implement new containers: a new implementation for each type or using interface{}. The first leads to a lot of duplicate code, the latter requires you to use introspection to use safely.
So, it does happen a lot, or you are making duplicate code.
Strings basically. I ended up joining Google back when, and ultimately I realized that "the web = strings" and Erlang is particularly bad at that. 128 bits for a 7-bit ascii character?
At the time, there was no good binary<->string utf8 libraries, not sure what the state of the art is now.
Erlang has dialyzer type checker. Depending on how well users refine their types that can produce very good results. Combine that with property checker (PropEr or QuickCheck) + Erlang's default process isolation and fault tolerance, and I think it can make for a more reliable system. I think I'll take that into production over just having a statically typed program. (Just going off my gut feeling and experience).
It's just optional, and therefore doesn't get used much on open source projects. So the average quality of code suffers.
Whereas something like Go where extra import are hard errors, making best-practices mandatory keeps code to certain standard and it's zero work to setup.
Ugh. What's most needed is a erlang-platform like haskell-platform.... bring together vital tools (proper/dialyer/etc), have a great package index (like hackage/pypi/rubygems) + package manager and have a skeleton project generator (rails-ish) that sets up what was asked for.
Convention > configuration
The easier it is to get going and contribute, the more people will use it. (Haskell pushed hard on this later on and has really benefited. Node did it early.)
Interesting to see surprise usages like that for such a small/simple language like Go; makes you wonder about the amounts of trickeries available in more complex languages.
One of the nicest things about Go interfaces is that they encourage SMALL interfaces. In explicit interface implementations, you end up throwing everything including the kitchen sink in the interface, because you might need it somewhere the type is used.
In Go, interfaces are small, and focused more on the function rather than the type. The function defines the methods it needs. So you can have a hundred small interfaces with one or two methods each, and a small handful of types that implement some percentage of those interfaces. Small interfaces makes your functions a lot more flexible. If all you need is a Read() method and a Close() method, instead of a huge number of potentially type-specific methods, then your method that takes a ReadCloser can be used by a lot more types.
You can do that in languages with explicitly implemented interfaces, but it means you need to write down that your type implements these 40 interfaces... and that's a hassle that isn't really needed or useful.
For the second example, the non-implementable public interface, is there a way to allow implementations when testing?
naively, if i define a non-implementable public interface as described in the article inside `foo.go` and put some test code next to it in `foo_test.go` that attempts to implement the interface with a special test version, then as we might expect, that doesn't work. if i put test code inside `foo.go` itself, `go test` doesn't appear to collect the test.
(i have no experience with go, apologies if i am missing something very obvious)
Your example of `foo.go` and `foo_test.go` would actually work the way you want. In go, tests are in the same namespace as the code they are testing so they have access to private members.
Ah, thank you. Where i was going wrong was declaring `foo_test.go` to be a separate package from `foo.go`. When i write `package foo` at the head of both `foo.go` and `foo_test.go` this works fine.
Honestly, I feel the version where he defines the struct as usual is clearer, simpler, and more maintainable. Other programmers will understand it immediately without thought. Allowing weird fancy ways like he raves about to save a couple lines of code is really a drawback in enterprise software development.
the purpose is to not be able to use the interface outside the module, right? why wouldn't you just use an interface type with a lowercase name, which wouldn't be visible outside the module?
The purpose was to not allow defining types which satisfy the interface outside the package. In this case they still want others to be able to use the values of the interface.
It's an uncommon thing to need to do, but this example shows that you can do it.
For reference, here's the exact same thing in Rust:
And just like Go, this ends up being free at runtime.