As to the minimalism in Go, I wish it came with a few more default data containers.
Sorting a map by using a slice gets old after awhile. Why can't I pick a sorted tree based map (like std::map or std:set in C++) or an un-ordered hash based map like std::unordered_set, etc.? Why do the containers have to be so minimal too?
This just leads to people rolling their own and many times, that leads to poor performance and other issues which ultimately reflect poorly on the lanaguage. I've seen more string implementations in C than I can count. Most of them were bad. I hope Go doesn't end-up being like that.
I too wish there was more standard containers available in Go's standard library. However, I don't think there will be a collections package unless and until generics make it into the language. That said, there are some pretty good libraries out there:
Thank you for the links. I hope Go gets a good, solid container library that is reputable and widely used. And even better if that makes into Go proper some day.
>This just leads to people rolling their own [containers]
Nah, Go was careful to make that impossible. (You know exactly what I'm talking about, I'm not going to come out and say it. What one, specific thing makes rolling your own container in Go literally impossible?)
If code generation is cheating, then that's a shame, because apart from differences in the type of artifact created, that's basically what generics do.
Code generation is one possible implementation of generics. But generics isn't just about the code you run but also about making sure the source code is sound at compile-time, i.e. they also have implications for the compiler's type checks.
Surely if you use code generation to create an "IntFancyDataStructure" then, once generated, type safety is ensured? There's no type erasure, no interface{} etc., in fact from compiler's point of view it's as simple as it gets. Or am I missing something?
> How would you define a function that accepts/returns FancyDataStructure of any type or only of a type that has X embedded?
I'm not sure what you mean by this. If you mean that it accepts any type known at compile time, then you use the code generator (we're talking about a function template, after all). If you want this to work with any type known at runtime, then use Go's interface since you need the vtable.
> BTW type erasure isn't necessarily a bad thing when the compiler can prove the code is correct at compile time.
Sure, though I'm not sure what the performance implications -- and while I can think of some counter-examples were this wouldn't be the case, in most cases the impact would probably negative.
What exactly does "cheating" mean, and why doesn't it count if it solves a problem? With generics, the compiler will internally generate type-specific implementations for each instantation, so the end result is the same.
I'm not arguing against generics at all (I wish Go had it), but I'm also not sympathizing with this response.
You might as well claim C has garbage collection since there are libraries for it. It's a hack. Might as well claim no language benefits from generics since you can always code gen.
For practical issues you have things like compile time, extra tool dependencies and up front setup time to enable a given type.
It makes c++ templates look down right elegant in comparison.
Yes, people absolutely use code generators with C. But they do this because of deficiencies in the language, and everyone basically agrees that C is the lowest common denominator in terms of languages -- it's portable assembly essentially.
Go missing generics except for built-in containers; and then not having anything but the bare minimum for those built-ins is a critical defect of the language. The fact that you can work around that defect doesn't make it any less of a defect.
It's not cheating, but you haven't rolled your own container: because in the end you don't have one.
Let's make an analogy. Suppose a language didn't have tail recursion, but by using goto you could "roll your own." So we're on the same page, right? you get what I'm saying, and how rolling your own might look and work, right?
Now suppose I said that without goto, it is literally impossible to roll your own tail recursion. (ignore whether this is true it's just an analogy.) You are saying, no it's not: you can patch the generated executable, so as long as it is possible to patch executables using go (i.e. if it can read and write binary files), then you can roll your own tail recursion even without goto, through binary patching post compilation. It might just be tedious or special-cased to one specific file but you can do it (you're saying)
See the difference? It's not that patching an executable is "cheating." It's just that it's outside of the definition of rolling your own tail recursion.
Likewise it's not that source code generation is "cheating." It's just outside the definition of rolling your own container. it's simply not what rolling your own means. after you've done it, you don't have a container.
my analogy is a bit leaky but I think it gives insight, and I hope you see what I mean.
I don't understand your analogy at all, but I believe the principal difference is that a code-generated container is a final, closed thing — it is not, for example, composable at compile time, so it doesn't fit into a larger type ecosystem.
For example, you could write a Vec<T> and instantiate it for Vec<string>, which is fine, but then a different package may want to declare func sort<T>(values Vec<T>). Maybe one is an app and the other is a library. How do you now fit the two together? If the tool is smart enough and both of them are generated at the same time using the same tool and module files, it might work, but I don't think there's anything like this for Go right now, and either way, you have the "same tool" problem.
Exactly! Which is why currently in Go rolling a container is "impossible." (it requires templates, as you showed with your Vec amd sort examples....something missing from the language.). It's very clear.
Containers are the level 0 of generic data structures: not only is an "array" significantly less useful than an "array of T" it also leads to loss of safety (you may intend your array to be an array of int, but codegen aside there's nothing precluding a user from shoving a string in there) inefficiencies (since the type of values must be erased you need an indirection, usually a pointer) and usercode-complexity (the user needs to downcast all values to the data type they hope they are).
As to the minimalism in Go, I wish it came with a few more default data containers.
Sorting a map by using a slice gets old after awhile. Why can't I pick a sorted tree based map (like std::map or std:set in C++) or an un-ordered hash based map like std::unordered_set, etc.? Why do the containers have to be so minimal too?
This just leads to people rolling their own and many times, that leads to poor performance and other issues which ultimately reflect poorly on the lanaguage. I've seen more string implementations in C than I can count. Most of them were bad. I hope Go doesn't end-up being like that.