I think you can really judge a language accurately by checking out its standard library. This is one of my favorite things about Go.
By comparison,
* The C++ STL. Fast and useful, but the implementation is nearly unreadable due to template soup. Here's one of the simpler parts! https://www.sgi.com/tech/stl/stl_vector.h
But of course the Java standard library is immense and has a lot of cruft in it. To write clean Java you really need to avoid much of the standard library (read Effective Java by Josh Bloch) and add a few important missing parts (use Guava).
Golang is really unique in that regard. You can learn Go by reading the standard library. It is beautiful, linear code. The library is pretty complete--eg you can write a one-line HTTP file server out of the box--but nothing feels extraneous. Lastly, I think it gets close to Knuth's ideal of Literate Programming. Paragraph-length comments thoughout the standard library explain what's happening, how, and why.
For example, the post talks about how io.Copy is awesome. For a concise English explanation, why not go directly to the source!
One thing Golang did that was really smart was to have a very slick documentation generator run from unformatted comments, using just a convention for how to write the prose. You have an incentive to write good comments (they're how you document your library to its users), and the comments godoc demands you write also work naturally as comments qua comments.
A lot of Golang is like this: very simple, almost trivial-seeming decisions that potently improve the language in practice. It's a fundamentally simple weapon, it's forged from Damascus steel.
I'm not sure I see how generics help at this particular problem. Functional languages have advanced type systems, but still use pattern matching all the time.
You've missed out a huge sector of programming languages; I'd be more inclined to take your claim of "unique" seriously if you'd included e.g. Haskell or OCaml.
More importantly, it's a massively biased comparison to look at a language from 3 years ago and compare it to those from 20, 30 or 40 years ago. Of course the standard library will be cruftier in an older language. The Idris standard library is beautiful, really state of the art - as you'd expect from a language that's only been written in the last couple of years.
It will be interesting to see what the Go standard library looks like in 20 years' time; I'm betting it will fare less well than Java's, because the workarounds and cruft necessary to avoid breaking changes will be worse in a language without Java's type system. In the meantime, the fair and informative comparison to make is with languages of a similar vintage.
> Java. A bit better. Compare the readability of OpenJDK's ArrayList.java to the STL vector.h, which does essentially the same thing: http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/00cd9dc3c2b5/s.... But of course the Java standard library is immense and has a lot of cruft in it.
Really? Java's success is - rightly - attributed to its simple and usable libraries.
> To write clean Java you really need to avoid much of the standard library (read Effective Java by Josh Bloch)
Lol, the standard ArrayList.java is written by ... Josh Bloch!
Yeah, ArrayList is fine. I was saying the code is cleaner than its C++ equivalent.
There are many parts of the Java library to avoid. Some are obsolete and terrible, like CORBA or java serialization. Others are gotchas w weird semantics, like Object.clone(). Others are just bad API design--eg the built in IO libs don't require a Charset argument. The default is the "system default charset", which is apparently still the obsolete MacRoman if you're on OSX.
The most annoying are those shortcomings of the std library that cause extra complexity. For example, the built-in JUL logging framework sucks enough that almost everyone uses log4j or logback instead. But now you have user libraries using incompatible logging frameworks. Enter slf4j, a shim that ties them all together.
Another example of "Wtf were they thinking when they wrote this?" in the Java stdlib is MessageDigest. To build a digester object, you must pass it a string literal to tell it which algorithm to use, and catch a NoSuchAlgorithm exception. Instead of, you know, just using an enum for all supported algorithms.
I think standard Java library API doesn't know what algorithms are supported on particular platform, additionally you can implement your own messagedigest.
The important thing is to make sure that your 'upgrade' is semantically indistinguishable from the non-upgraded version, and to the extent that it differs, document it. There's an old blog post[^1] where someone describes code that performed this kind of "interface upgrade" in a way that broke underlying assumptions about the code. This is a horrible (and unexpected!) kind of bug. One should be really, really sure that this is the correct way of solving a problem before actually reaching for this tool.
I'm wondering what programmers in functional programmers do about this sort of thing? It seems like they have much stronger type constrants; subclassing and upcasting just plain don't work. And if they did work, it would invalidate the sort of proofs that functional programmers like to do.
Treating "upgrades" as a specific case of reflection, typically what happens is that the function that wants to do reflection declares that it wants to perform reflection on its arguments via the type system. For example, in Haskell this mechanism is called "typeable" and in Rust this is called "any".
The nice thing about this approach is that it signals to callers that reflection is going to happen, so callers can be ready for it—the types become a form of documentation. If reflection is unrestricted it's easy for callers to get surprised when methods are called that they didn't expect. (Go has had breakage during point release upgrades from this, for example.)
This stricter approach requires that you plan ahead for "upgrading" in advance since you can't change a non-upgradeable argument to an upgradeable one without changing the type signature and breaking your callers. On the other hand, your callers are secure in the knowledge that you won't do that: they know that if you didn't give them access to one of your type's methods they can't sneak around and get access to it through the "back door". Non-upgradeable interfaces can also be more efficient since there is no need to store the reflection metadata at runtime; with this approach you only pay for what you use. Altogether it's a classic static-versus-dynamic-typing tradeoff.
Usually we have a more powerful type system that means we don't need to. E.g. in something like Go you might have a Reader, whereas in a functional language you might have [A: Reader], i.e. a generic type A for which a Reader typeclass instance exists. This means you don't have to throw away the specific type (ReaderCloseable) to be able to use it as a Reader.
If you really need to cast, most functional languages will still let you - but as you say, it invalidates the proofs so is very much discouraged (e.g. the scalazzi safe subset requires you to not cast).
Has anyone quantified the runtime cost of doing interface upgrades? I have a vague memory that the vtable for a specific type/interface pair is constructed at runtime and then cached -- is this true?
An interesting question. Using [1], which note can't be executed on the Playground but you can execute by copying and pasting into a file "upgrade_test.go" in a new directory and executing
For clarity, I've elided the "execution count" column which is not useful information for us here.
InterfaceUpgrade is testing the cost of having an interface-referenced value in hand and asking if it implements another interface. My belief based on the understanding of the runtime is that this is all a static check at runtime (i.e., taking the type of the value, looking that type's info up, and then reading from that type whether it implements a given interface, with all calculation done at compile time and only the lookup being done dynamically), and that while that number may go up a bit if there are more interfaces in play it shouldn't go up much.
DoNothingInterfaceCall is the time to call a function that does nothing and returns nothing through the interface. I tried adding a benchmark time for a do-nothing static call but Go inlines it away into nothing, making it a useless test of how fast the loop itself is running. 0.89 ns, FWIW.
InterfaceUpgradeAndCall combines the upgrade check and call into one pass. InterfaceUpgradeFail just checks to see if the fail case times very differently (no).
It's fairly easy to tweak these if you'd like to ask different questions. (Note some of the tests have a bit of spurious stuff at the end to make it so Go doesn't complain about unused values. Go really doesn't like to see unused values.)
Also, for those who may have spent some time benchmarking the more dynamic languages, I call your attention to the "ns" there. That's not milli- or microseconds, that's nanoseconds. (I once benchmarked something between Go and Erlang, and mistakingly though the Go was slower when it took 700 and Erlang was taking 70, but it turned out to be nanoseconds and microseconds respectively. FWIW, the code wasn't doing exactly the same thing, either, it was just some code I was benchmarking at the time, and it is expected that the Go code was doing less; point being, watch your units.)
I understand it fine except this bit right at the end:
var _ http.CloseNotifier = &fancyWriter{}
var _ http.Flusher = &fancyWriter{}
var _ http.Hijacker = &fancyWriter{}
var _ io.ReaderFrom = &fancyWriter{}
What's the purpose of this ? According to effective go this just silences the unused imports but I don't think there's a need for that here.
It allows you to make sure fancyWriter implements all those interfaces, so that any future refactoring that breaks compatibility breaks at compile time.
You know how Go makes interfaces concordance implicit ? Whatever has a Read() method is a io.Reader without asking for it ? This explicitly tells the compiler (and other developers) the same thing.
They are static assertions that fancyWriter implements all those interfaces. They are compiled away to nothing but provide a kind of test of correctness.
Go's interfaces are great, but one of my annoyances is that you can't define an interface with values (methods only). Which makes little sense considering it works just fine with getters and setters. i.e:
type Blah interface { Var string }
wont work, but
type Blah interface { GetVar() string }
does. I'm sure theres a reason for this. Can someone shed some light?
The purpose of the interface is to abstract how something is done.
In your example, GetVar() can be implemented in a number of ways. Returning Var field of Blah struct is only one such way (and if there was only one implementation, there would be no need for an interface).
In that light, adding variables as part of interface doesn't make sense. Variables are fixing the thing that interface is meant to make flexible.
In Go, you can achieve re-using of a bunch of variables (and their methods) by embedding - put the variables you want to re-use into a separate struct and embed that struct in other structs. See https://golang.org/doc/effective_go.html#embedding
It seemed to me that interfaces have a second function of providing partial uniformity across types (not just abstracting procedures). I.e the Animal interface has a method `Speak() string` and you have a function that takes an Animal that prints something like "Animal type {species} says {speak}" you would have to define a getter on the Animal's species even though its not a really a procedure. But I see what you mean though. Thanks!
If you have a function that requires both the species and a speak message from an animal, it would make sense for the interface to have a function that returns both the species and speak strings. I think that this [0] does what you would want.
If you want to guarantee that two structs contain a set of the same required fields, I believe the best practice would be to include an anonymous field for a struct with the fields across both. [1]
An interface takes any value for which the required methods are defined on it.
Methods can be defined on any type declared in the package.
A type in go can be a lot more that just a struct; it can be any type in the language. So while it may make sense to have values on interfaces for structs, values don't make any sense for functions, pointers, interfaces, or extensions to built in types.
I think you can really judge a language accurately by checking out its standard library. This is one of my favorite things about Go.
By comparison,
* The C++ STL. Fast and useful, but the implementation is nearly unreadable due to template soup. Here's one of the simpler parts! https://www.sgi.com/tech/stl/stl_vector.h
* PHP. So bad it's basically a strawman. I'll include it because it's hilarious: http://www.forbes.com/sites/quora/2012/08/31/what-are-the-mo...
* Java. A bit better. Compare the readability of OpenJDK's ArrayList.java to the STL vector.h, which does essentially the same thing: http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/00cd9dc3c2b5/s...
But of course the Java standard library is immense and has a lot of cruft in it. To write clean Java you really need to avoid much of the standard library (read Effective Java by Josh Bloch) and add a few important missing parts (use Guava).
Golang is really unique in that regard. You can learn Go by reading the standard library. It is beautiful, linear code. The library is pretty complete--eg you can write a one-line HTTP file server out of the box--but nothing feels extraneous. Lastly, I think it gets close to Knuth's ideal of Literate Programming. Paragraph-length comments thoughout the standard library explain what's happening, how, and why.
For example, the post talks about how io.Copy is awesome. For a concise English explanation, why not go directly to the source!
https://golang.org/src/pkg/io/io.go#L329