14 ms·
I am a heavy user of Go, I use it in my job and for side projects as well, for years now. The lack of generics in Go takes away most of the fun from coding aft
by friendly_chap 13y ago
I am a heavy user of Go, I use it in my job and for side projects as well, for years now.
The lack of generics in Go takes away most of the fun from coding after a couple of month. Despite what is being said, interfaces do not lessen the pain.
With a better type system, taking ides from Haskell (ADT, generics done well, ability to implement interfaces for a type outside of its package etc) Go could be the perfect "dirty Haskell" for me, but now it is just a meh language with a half assed implementation.
That being said, it is still a much better choice then a lot of overcomplicated languages out there.
EDIT:
The biggest practical problem with not having generics is the fact you do not have access to list (slice in Go) manipulation functions. This means you have to implement them yourself, on the fly, for the given type when you need it. This does increase the time it takes to implement something, but also can introduce a lot of bugs as well - you can't rely on (almost guaranteedly) bug free implementations of existing functions.
Of course this runs much deeper than just the list type, but on day-to-day basis I feel this to be the biggest pain. But the rabbit hole is deep: we could get into how the multiple returns values are just a really inconvenient (not composable) workaround of not having a generic tuple type...
- latch 13y agoAlso have large Go systems in production, and I agree lack of generics is a problem. The biggest problem for me though, is the lack of testability. As a simple example, the fact that log.Logger isn't an interface means everyone creates a wrapper just for testing purposes. DI is also complicated enough that in some cases, it's just easier to forget about unit tests and stick with integration tests.
- jerf 13y agoThat is a problem that interfaces can solve. I agree it's a bizarre oversight that the logging package doesn't define an interface, but it's also one very easily corrected, and since interfaces are implicitly fulfilled, the standard logger trivially meets the interface you define. We've found there's a few other places in the core libraries we expect an interface and there isn't one, but, again, fortunately it is trivially fixable. I'm actually finding Go to be one of the testable imperative languages I've used. This isn't everything I've developed for it, but this is one of the fundamental tricks: http://www.jerf.org/iri/post/2923 http://www.jerf.org/iri/post/2923 Also remember that global variables are still Bad. If you've got a global Logger, you've already lost. Fortunately, struct embedding seriously mitigates the pain of passing things around and using them. While this will probably break apart into multiple objects as I continue developing, I've taken to having this sort of structure: type Services struct { *logging.logger // actually our custom logging package database.Database // locally defined interface connections.Tracker // registers connections or whatever } I then have my primary objects embed a Services instance, so logging is just "obj.Error(...)". I pair that with a function that can construction a "null" Services, with a logger that doesn't log, a connection registration system that doesn't register, etc. Combined with what I linked, this enables even rather complicated interactions to be feasibly tested in relative isolation. I've also created some basic mock objects for some of the objects that may for instance assert that the Logger is called when expected, etc. Technically none of this is impossible in other imperative OO languages, but Go has just enough twists on things to make it qualitatively easier, mostly implicit satisfaction of interfaces, which has had consequences beyond what I've expected. Embedding has had surprisingly pleasant consequences too; complicated objects can be safely built up with rich interfaces that do not require you to hardwire them together, or give you excessive access to the internals of the bits.
- JulianMorrison 13y agoYou can SetOutput the standard logger. Go's own "go test" does this. As for DI, just connect your process components through constructors that take interface-typed parameters.
- Skinney 13y agoThis is my oppinion of Go as well. Go has done alot of things right, but the lack of generics is off putting. I would also prefer that the type system could catch null pointer exceptions, but most languages don't give this guarantee, so it's not that big of a deal. Another thing people gripe about is that exceptions (panic) is almost never used, and that most functions return error codes. I've yet to decide what I think about this issue, but sometimes it does feel like Go went for "NO EXCEPTIONS!" and then figured out they needed exceptions anyway and added panic/recover as an afterthought. What I do love about Go though is the implicit interfaces, := assignment, forcing if and for to be followed by {}, no inheritance, concurrency and the non-forgiving compiler.
- nemothekid 13y agoI agree with the panic sentiment. I almost never know when to actually use panic or to just return an error code. My guess is the reason panic is never used is because its "bad go" for a panic to leave your library/package. http://golang.org/doc/effective_go.html#panic http://golang.org/doc/effective_go.html#panic
- ansible 13y agoGenerally speaking, you ought to use error instead of panic. The only time panic is appropriate is when the program really ought to just crash. For example, I'm writing a small program to update a database. If the DB connection can't be opened, the program can't do anything useful, so it can just exit with an error. Any library should just use error.
- grey-area 13y agoIt would be really interesting to see specific examples for this - can you give a short example in code or pseudo-code of somewhere you really wanted them and didn't have them, and how they would improve things? There is a generic append, but do you mean sorting lists or something else? Apologies if this is obvious, but it would be enlightening for those not familiar with generics from other languages.
- friendly_chap 13y agoLet's take a basic list operation, map. Map takes a transforming function and a list, and transforms each element. type MyString string func NewMyString(s string) MyString{return MyString(s)} a := []string{"A", "B", "C"} If you want to create a []MyString from []string, now you have to loop over that and transform by hand, with generics you could do map(NewMyString, a).
- grey-area 13y agoThanks
- dvirsky 13y agoI tend to agree that simple generics would have made things a bit less annoying working with custom containers (BTW - I think the standard library should have a proper set type, it's silly that there are a zillion set implementations out there with slight variations). But the things that bother me most about Go are actually different (I'm saying this being a big fan of Go and having written a few projects with it): 1. The GC is very simplistic and prevents building memory intensive services (at least not easily) without suffering long pauses. 2. The lack of any robust IDE. I'm mainly working with Sublime, and used to work with the IntelliJ plugin. Both are decent, but sublime has a half working gdb integration, and IntelliJ lacks one completely. 3. Community maturity. go-get is awesome, and there are tons of libraries for anything - but most are immature and/or not maintained. Usually you have a problem and find 2-3 projects that look promising that already solve it, but then one hasn't been updated for 2 years, the other looks awesome but is 2 months old and incomplete, etc etc. It will get there, I'm sure, but currently there is an inflation of toy projects, and no mechanism to find the prominent projects quickly (I think pypi's keyword based approach helps with this a bit, BTW).
- judk 13y ago'map' is the standard library 'set' type. Trying to make a 'proper set' is the sort of impractical elegance that the go devs rail against.
- dvirsky 13y agoWhen their map implementation has union and intersect, I'll shut the hell up. In the mean time people who need this need to implement these operations by themselves, or try to figure out which set implementation on github to use.
- babawere 13y ago+1 for lack of good IDE .. See https://news.ycombinator.com/item?id=6808936 https://news.ycombinator.com/item?id=6808936 for Vote For Official IntelliJ GOLANG Plugin
- p0nce 13y agoD is complicated for sure, but it doesn't have the issues you mention, and is still fun after years. I would be curious what fatal flaw you find to not consider using it. To me it certainly feels like a dirty Haskell which values your liberty very much.
- friendly_chap 13y agoI have never investigated D due to fears of becoming unmaintained. There is more to a language than just the language itself, especially when you use it in a business setting. But you made me curious now.
- rdtsc 13y agoIt got a major boost in "popularity" by Facebook announcing they are deploying in production in some system they have. They employ Alexandrescu, a well known C++ figure, who I understand is quite fond of D now.
- corford 13y agoAndrei Alexandrescu did a reddit IAMA recently (with Walter Bright contributing) and if you read through the posts I think you'll come away with the impression that D is very much here to stay (I certainly did anyway). http://www.reddit.com/r/IAmA/comments/1nl9at/i_am_a_member_of_facebooks_hhvm_team_a_c_and_d/ http://www.reddit.com/r/IAmA/comments/1nl9at/i_am_a_member_o...
- itsmonktastic 13y agoWith a better type system, taking ides from Haskell (ADT, generics done well, ability to implement interfaces for a type outside of its package etc) Go could be the perfect "dirty Haskell" for me I wonder if you might like OCaml (which is my sort of "less strict haskell"). There's also rust of course, but that's explicitly not for production use.
- friendly_chap 13y agoYes, OCaml is really similar to Haskell (it is an ML after all), without purity. But there is no support for multicore, the implementation is not really being improved etc, so I decided against using it.
- danieldk 13y agoI find it surprising that OCaml is not more popular. It provides many of the advantages of Haskell (except purity) and is easier to reason about for most newcomers due to strict evaluation. One of its problems is that its concurrency support is lacking. But that doesn't seem to be a problem for other languages to become popular :).
- tikhonj 13y agoIt's easier to reason about performance. Since both are high-level languages, chances are expressiveness and correctness matter more, and I've found Haskell better at both, even for beginners. OCaml had some pretty specific downsides beyond just the GIL (which is pretty absurd on its own). Typeclasses are really big, and the module system can't make up for them. OCaml also had absolutely terrible tooling and an abysmal standard library, although this has been improving pretty quickly in the last few years. Ultimately, after having used both languages quite a bit, I just find OCaml quite a bit more awkward than Haskell. OCaml's advantages just don't make up for this, although I really love some features like polymorphic variants and modules that don't suck. I'd still use OCaml for a few particular tasks, like web programming with the incredible js_of_ocaml, but Haskell is generally my default choice both for programming and trying to teach people.
- zura 13y ago
- Nitramp 13y ago> With a better type system, taking ides from Haskell (ADT, generics done well, ... > That being said, it is still a much better choice then a lot of overcomplicated languages out there. Devils advocate here, do you see a contradiciton between these two sentences?
- friendly_chap 13y agoNo. Having a nice, feature rich type system doesn't make a language overcomplicated. See Haskell's one. I would argue that having a bad type system definitely can make a language overcomplicated, full of edge cases and dark spots.
- tikhonj 13y agoNo, because Haskell's type system (ignoring certain relatively experimental extensions) is actually quite simple. Many people may find it difficult to learn, but only because it is so different and alien and not because it's some baroque monstrosity. In some ways, Haskell's type system is actually simpler than Go's: there's no sub-typing and hence no real casting. Haskell also tends to eschew special cases in favor of more general solutions; for example, Haskell does not need any magical notion of multiple return values built into the language because this is naturally subsumed by tuples. Similarly, Haskell has generics instead of baking a paltry set of data structures into the language. Also, algebraic data types are actually very natural. Tagged unions (also known as variants or sum types), unlike normal union types, are dual to product types (structs and tuples). This means they are actually just opposite versions of the same underlying structure: very symmetric. And just like in physics, symmetry usually means you're onto something. Haskell has an extremely simple and elegant core; just because it's difficult to learn for some people does not mean it's a particularly complex language. It's certainly simpler than Scala, for example! Certain extensions add quite a bit of complexity, but the most complex ones are not super-widely used so you can reasonably get away without ever dealing with them.
- danieldk 13y agoIndeed, and Haskell itself is translated into the (very simple) Core language: http://stackoverflow.com/questions/6121146/reading-ghc-core/6121495#6121495 http://stackoverflow.com/questions/6121146/reading-ghc-core/...
- rdtsc 13y agoI haven't gotten that far. It seem lack of generics is something noticed by those who already wrote quite a bit of code. I also come from dynamic languages, Python and Erlang, so I think about types differently (I like them dynamic but strong). Anyway, I like go because: * of its fast builds. * static compilation. * speed, almost a replacement for C++ in what I do * concurrency: goroutines, this is great compared to threads in other languages * nice library -- json, http parsing, many other goodies, batteries included is good in my book * gc -- nice to have a garbage collector What I don't like as much: * concurrency: goroutines are great, but once you've tasted Erlang's model, this one seems lacking. I like thinking in actors and mailboxes rather than in goroutines and channels. Actor+mailbox somehow matches my world view better rather than nameless goroutines. * no supervision control of goroutines -- I haven't found a way to kill, or watch for death of a goroutine externally. This would make it easy to build a supervision tree and add some fault tolerance features to the language. channel ping plus timeout could mimic it but seems kind of a hack. * gc stops the world sometimes. * goroutines share the heap, I wish heap sharing was an option one had to enable and by default it would work like Erlang (or Dart Isolates). As concurrent systems (which supposedly Go is the prime target for) grow it is really fault tolerance that kills them. One goroutines, killing the whole system with thousands of concurrent requests is so C++-ish, I wish it was more Erlang-ish.
- JulianMorrison 13y agoFor supervision: Go's idiom is to handle errors immediately and locally rather than Erlang's die-and-restart approach, but for panics you can use defer and recover to alert the supervisor over a channel. You'll have to write the supervisor yourself, of course.
- jerf 13y agoI've been developing a supervisor library, written by somebody quite familiar with Erlang as I've been programming in that for years. I've been waiting while I pound on it locally to release it, but I suppose it's time; I don't think I've touched the code of the supervisor itself in weeks. I'll see about getting it up on github here in the next couple of days. It's not as good as Erlang, not by a long shot, but it's still a bit of reassurance, and provides a useful default "restart" behavior out the box. (Naively-written restarts have some bad and easy-to-trigger pathological conditions. Well-written restarts still some bad cases too; crashing software can always crash in more pathological ways than you expected, but it's at least better.) I'm also developing a library that gives you a "mailbox" like Erlang. I'm porting over a project in Erlang, and while I use native Go idioms where possible, Erlang code ends up with the Erlang semantics deeply embedded in it. (My thought is that a new project in Go should never use this library, but porting Erlang code may be eased by it.) That is still very much in progress; I'll only be starting cross-node clustering support here in the next couple of weeks, and without that there's not a lot of value to it. I'm still feeling my way through the right set of functionality and semantics for that, it's not ready for the public yet.
- druska 13y agoGenerics have their own problems: http://research.swtch.com/generic http://research.swtch.com/generic
- liebfraumilch 13y agoHaskell implements generics without having to box everything.
- eeperson 13y agoAs far as I can tell, Haskell does box everything: http://www.haskell.org/ghc/docs/7.0.1/html/users_guide/primitives.html http://www.haskell.org/ghc/docs/7.0.1/html/users_guide/primi...
- dbaupp 13y agoHaskell boxes (almost) everything always, so there's no additional boxing from the generics.
- kvb 13y agoAs many comments point out, he neglects the .NET approach to generics, which avoids most of the drawbacks of C++'s approach and Java's.
- eeperson 13y agoDo you know it does this ? I've heard this before and I'm not sure how C# can do this.
- kmontrose 13y agoIn C#... At compile, there's one version of a generic method (say List<T>.Add). Compilation isn't slowed down like in C++. At runtime, a generic method is compiled (on demand) into unique versions for each T encountered if T is a ValueType (int, double, etc. including user defined ValueTypes). This avoids boxing, like in Java. Also at runtime, for all T's that are reference types a single common version is used with implicit casting as in Java. C#'s draw back is that there is a small cost incurred on the first use of a generic method, and then an additional cost for each new ValueType used (if any). In practice this doesn't ever seem to be an issue, but it's there. See http://stackoverflow.com/q/5342345/80572 http://stackoverflow.com/q/5342345/80572 and http://www.artima.com/intv/generics.html http://www.artima.com/intv/generics.html (which is linked from the former).
- mwsherman 13y agoI’ve started such a thing: https://github.com/clipperhouse/gen https://github.com/clipperhouse/gen Early days, still a lot of work to do, but I think it’s promising.
- jerf 13y agoCool, I'm going to have to check that out. It looks like the right way to do it; I've also examined the options and decided that code gen is the only way to do it. One thing that I would actually accept for "go generics" is the go tool developing instead some ability for users to invoke the same sort of magic that cgo has, to run some code before the actual compile phase. (If this already exists, I haven't found it, but I'd love to hear about it.) Then instead of the language developing "generics", it can simply be an extension that people who care about use, and those who don't, don't, because as you observe, in the end this really is what "generics" are in most languages. That would also open the door to, for instance, some slightly more automagical database bindings that generate them from a schema or something, or anything else that involves generating types from metadata. There's quite a few tools like that that I don't want to function at run time, and I'm perfectly happy to have code generation work with it rather than lard up the language with every feature under the sun.
- pcwalton 13y ago> Then instead of the language developing "generics", it can simply be an extension that people who care about use, and those who don't, don't, because as you observe, in the end this really is what "generics" are in most languages. You'll sacrifice reflection doing it that way. This also causes the same build time problems that templates cause in C++: you have to parse the generics over and over every time you want to use them. You also need to have some facility for collapsing duplicate template instantiations. You really do need to have generics built into the language to do them properly. Preprocessors cause as many problems as they solve.
- jerf 13y ago"You'll sacrifice reflection doing it that way." How? Everything produced by the preprocessor is available at runtime as much as anything else. "you have to parse the generics over and over every time you want to use them." I am not seeing how this affects Go, which does not do inclusion the way C++ does. You run the preprocessor once, it creates some files in the local Go package which you should not manually edit, and from that point on it is just as if you had created them by hand. "You also need to have some facility for collapsing duplicate template instantiations." This one might be true, but given the cost/benefit tradeoffs, I'll pay in some cases. It's not as if we're talking about a happy paradise in which not having generics solves all my problems; it visibly causes me to violate DRY in some of my code. DRY is the highest principle of software engineering. Plus I'm not sure it's true. With what's being described here, you'll only be able to instantiate the generics for a given data type within the package that declares that data type, as it involves adding methods. The result may be that you in fact can't end up with duplicate instantiations; things outside the package can't add methods to data types inside a given package, and true duplicates are trivially detected at compile time. (This is in fact a limitation of the approach, but for this reason may still be a good one. I'm satisfied to solve 90% of the use case for generics if I can get it without dragging in the pathological stuff.)