You mean, except the lack of simplicity when you have generic arrays and maps, and the lack of consistency when no other type can be generic? (Personally, I don't think generics are complicated, but people say that Go designers think so.)
As well, from what I've heard of Java's generics implementation, it add a huge amount of complexity to javac and the JRE. The Go team is small and probably didn't have the resources to do it out the gate.
1. if you have sub-typing in the language, then you'll naturally have to think of co/contra-variance, which leads us to ...
2. Java had backwards compatibility concerns, for example they were unwilling to deprecate the standard collections, so they chose to implement use-site variance with wildcards
There are several problems there - first of all you cannot declare variance rules on the classes themselves, even though there are legitimate use-cases for that, like immutable data-structures have no problem in being covariant. Hence generics in Java are much less useful than they would be with declaration-site variance. Then subtyping + variance, especially with wildcards which are basically existential types, is undecidable, so by definition people have a hard time in producing a reliable compiler.
Here's a fun example that could crash a compiler:
class C<P> implements List<List<? super C<C<P>>>> {}
And doesn't that suck?But seriously, Java's designers could have chosen a better, saner path. Declaration-site variance is so much better. And we aren't talking about really expressive type-systems, with higher-kinded types and type-2 polymorphism, for starters the basics would do just fine.
I mean, Go's standard data-structures are generified. Why not provide the same capability to its users? This situation is exactly like with Java's arrays.
They are just non-generic (pun intended) - you cannot add it to every type, only to "blessed" types - arrays/slices, channels and maps.
Could you please stop spreading this nonsense?[1] Generics aren't free. You will always pay for it in terms of some combination of implementation complexity, runtime performance and compiler performance.
> Generics aren't free. You will always pay for it in terms of some combination of implementation complexity, runtime performance and compiler performance.
I don't argue about implementation complexity and compiler performance, I'm just saying that I assume that most that complexity is already present in the Go compiler.
However, I think that saying that generics have runtime performance cost is disingenuous. It's like saying that functions aren't free. Now, obviously it's possible to program without function by using loops, gotos or inlining code. However, if you want to make your program more abstract, you'll end up implementing functions, along with calling conventions, register saving, varargs, ... It's easier if the compiler does that for you (also, sometimes the compiler will compile functions into loops, gotos or inlined code, reducing the performance penalty).
It's the same with generics. If you have an algorithm that you want to use with different data types, you can either (1) implement it multiple times, or (2) manually box and unbox all values. That's exactly what the compiler can do for you if you write the code using generics. The fact that most compilers use just one of these strategies is not a deficiency of generics, but the deficiency of compilers. Both Scala and .Net use both specialization and boxing. Therefore, in all honesty, I really see no additional runtime cost to generics that wouldn't be present in equivalent non-generic code.
They aren't free...
I literally don't know what to say here. The whole point is to analyze the trade offs of generics rather than start out with a judgment that they are always worth it. In order to have generics supported in a language, you must pay for it some way or another. If you just box all of the values like (I think) you're suggesting and provide compiler support for it, then programs that use generics are going to pay a cost in runtime performance compared to programs that don't use them. In other implementations of generics, it's possible to remove that runtime cost (or almost remove), but usually at the cost of something else like implementation complexity or compiler speed. Programs that use generics with that implementation choice won't pay a runtime cost compared to programs that don't use them. This obviously has a significant impact on the decision procedure of whether to use generics or not at any given point in your program.
And yes, absolutely, saying generics has a cost is exactly like saying functions have a cost. The only difference between them is that there is clearly still a substantive amount of disagreement over whether full blown generics is always worth its weight in value (compared to the cost of not having them).
Currently, Go does not have anything resembling the term "generics." (Except for a few built in types and functions defined in the language specification.)
Honestly, Go is probably one of the only language that is clearly not expressive enough.
[0] Just to name a few: Go has packages instead of header files, one simple build command instead of Makefiles, embedded structs, interfaces, allows you to declare functions everywhere, multiple return values, panics, a huge (!) platform independent standard library, native platform independent concurrency, saner string handling, lists, dictionaries, iota, cross-platform and cross-architecture code via file names instead of macros + autotools, ...
I really can't understand how people dismiss all of this and claim a programming language is useless because it lacks a single feature.
I am not saying C does things better than Go. I am saying it is less expressive.
In the sense that there are a lot of things you simply cannot implement in C, whereas you can implement them -- in an ugly and unsafe fashion, it is true -- in C.
Generics is not the only example. You can easily get exceptions in C with setjmp and longjmp for instance. You can even get garbage collection in C (and not only talking about the conservative Boehm stuff, you can use fat pointers -- yes it's unwieldy and ugly, but it works).
As for all the niceties, yes there are there, but you can implement most of them in C also (and, in fact, people have).
You'll notice I did not even say C was better than Go.
My actual thoughts on the matter is that they're not made for the same use cases -- albeit one of my problem with Go was always figuring its use case. Apparently I'm not the only one confused, because the language creators expected to get C users, they got Python user instead. As you said yourself, it has more niceties, so maybe it is kind of a middleground when more performance or concurrency is needed, but C is deemed to tricky or unsafe. As I have learned to navigate my ways around C's pitfalls (and boy, there are some pitfalls!), I don't think I'll ever be tempted to use Go (at least, if the language doesn't change significantly).
I'm also not some kind of C fanatic, I use Java, Ruby or Python if it gets the job done; and while I don't really use them for actual work, I have a soft spot for some functional languages.
In java.util.Hashtable as implementation of the java.util.Dictionary interface, since version 1.1 back in 1997.
Implemented using s/interface{}/Object/g with ability to use any type as key, as long as, hashCode() and equals () are overriden.
Also allows for fine tuning of the capacity and load factor.
> Or closures?
In anonymous inner classes. A pain to use when compared with real closures, but doable nonetheless.
The fact is, the comparison between Go and "pre generics Java" is a bad one. There are many substantial differences, and blessed parameterized types in Go is absolutely one of them.
* pre-1.5 Java did not have one, its only parametric type was the low-level array
* Reading is more important than writing
- code should be a joy to read
- the language should not hide what is happening
- code should do what it seems to do
* Simplicity matters
- a clear semantic model greatly boosts readability
- every "good" feature adds more "bad" weight
- sometimes it is best to leave things out
Obviously languages evolve with time. They need to change when their usage changes (for example, when they're used to build bigger and bigger programs) as well as when fashions shift, but when they do, they must do so carefully and conservatively. As a general rule, Java only adds new features not when they make something nicer, but only when they solve something that's really painful.I think this approach works especially well in large projects designed to last for years, made by large heterogenous groups of developers who might come and go over the lifetime of the project.
One language that is somehow confounding in this respect is Clojure. It is minimalistic, but at the same time it's clever, so I'm not sure whether it belongs more in the minimalistic language category or the clever language category.
Truly minimalistic languages can only be clever, because at one point you need to be able to build the language in itself, and that naturally opens things up to the user.
Go isn't a minimalistic language, it's a simplistic one.
* Implement open types for the language. OCaml did this in 4.02, and it is very useful. * Heterogenous containers: https://github.com/MLton/mlton/blob/master/lib/mlton/basic/h...
It has its uses, albeit it is rare such a use comes into play.
Probably because that's a terrible thing to do.
If you want to keep a collection of things of different types, you should wrap them in a container type using ADTs.
You can't; as I've said, the set of types is not known in advance. For example, you're building a GUI framework, where a widget has a list of children; you don't know what kinds of children the users of your library will make!
The only way to solve this in a HM system is using closures (existential types), i.e. instead of adding a widget to the list of children, you can add it's .draw() method. This gets complicated the more method you need; you'll end up with a record of methods, i.e. an interface.
(1) You inspect its type with reflection, and make a choice of what to do accordingly. This requires tagging the type at runtime, and is ugly no matter what.
(2) You will use the intersection of features provided by all the types. In other words, some interface representing what each object in the collection can do.
Either way is pretty straightforward to represent. In haskell, the first is a Dynamic type, and the second is a record containing the interface functions. I am pretty sure any other case is a runtime error, unless I missed something?
You don't know everything about the children's types, but you know enough to constrain them into being type safe.
People sometimes do this safely in Go, but all too often they use interface{}.
I've played around with Go (worked through the Go Tour and implemented some trivial site scraping/JSON client stuff). It's an enjoyable, straightforward language, and it makes it very easy to get started, but I fear that it will be (like Pascal) eventually overtaken by something hairier. (Go : Rust(?) :: Pascal : C++)
FWIW, I think it's misplaced to fear that Go will be "overtaken" by Rust. They both excel at different domains, and the systems software that you'd write in one is probably not the systems software that you'd write in the other.
> overall simplicity and consistency matters more
Go took some good steps towards simplicity. OOP as present in languages such as Java is overrated and a primary reason for the complexity created.
We could use some variations for this need for ad-hoc polymorphism that we have. Unfortunately Go doesn't go far enough by not addressing the need for type-classes or something similar. And the language doesn't have to end up with a type-system as sophisticated as Haskell if you want type-classes. Clojure has a similar mechanism called protocols and yet it doesn't have OOP in the traditional sense, Scala has implicit parameters solved at compile time that can be used to model type-classes and overall type-classes are a simplification because they cleanly separate data from behavior.
Go also provides those channels as a more useful abstraction than plain threads. That's cool and all, but channels are not enough and a language should allow other people to evolve the language with even more abstractions for dealing with concurrency and parallelism, because there's no silver bullet. On top of the JVM people have built frameworks and libraries for actors, agents, csp, futures/promises, parallel collections, light-weight threads, STM, reactive programming / streams and most other things you can think of and the best a language can do is to allow evolution.
One should not confuse a small set of features with simplicity. Java was also a language that refused in the beginning to have generics and then in version 5 it got an abomination - as in generics that don't specialize for primitives and that solve co/contra-variance by means of use-site variance and freaking wildcards, instead of the much saner declaration-site variance. This happened because backwards compatibility is important and an established language needs to be evolved by providing a sane migration path. Go is slowly losing its window in which it can make bold changes.
Going back to your original point, a small set of features is bad if users cannot efficiently extend the language because the needed features aren't included. Having a small set of features is great, but it matters what those features are. I invite you to watch Guy Steele's "Growing a Language" presentation on this point, it's an astonishingly good presentation: https://www.youtube.com/watch?v=_ahvzDzKdB0
Besides, every time you create a new type or interface, you just extended your language with a noun or adjective and every time you come up with a function, you just created a verb. And I have to learn your nouns and your verbs to understand your code, even if you try explicit naming that don't really say anything (my personal favorite being variations on "processItems").
Go is not the first language to take this approach, it won't be last last either. Unfortunately people never learn and it feels like groundhog day, the movie.
Thing is, if you want a language without generics, then you want a dynamic language, because a static language without generics is overkill for building reusable stuff. Go's structural typing is cool and all, that's ad-hoc polymorphism done tastefully, but we are talking about a different kind of polymorphism here with different use-cases.
Watch that presentation, it's genius.
However, I would still have to disagree, given that I have written a lot of very useful code in Go, much of it quite reusable (without interface{} or reflection).
I did go click on that link and started to watch the presentation, FWIW. It's too long for me to watch now, but it definitely looks interesting, so I'll try to watch it in the next couple days. Thanks for pointing it out to me.
On the other hand what I really like about go is that they are the only popular recent language that pushes back against a lot of modern features and says: "It doesn't matter how clever those features are, overall simplicity and consistency matters more."
The point is that we really need this experiment. It's in everybody's interest that the go people push back and continue to keep the language minimal. In a few years time someone will say on the internet that unless you have features x, y, and z in a language you simply cannot be productive/concise/maintainable/performant/safe/not-a-blub. At that point it may be possible to point at some evidence and say "Well I noticed that those machine learning guys at myAppsTheGreatest.com wrote a large system in go". Then you can go study it and see if they really needed those features. You can also ask the developers who used it and hopefully some of them will also have experience in [insert crystalline functional language here] so you can get them to weigh up the pros and cons. Go is an experiment that is needed.