Hacker News new | comments | show | ask | jobs | submit login
Idiomatic Generics in Go (bouk.co)
77 points by bouk on Sept 30, 2014 | hide | past | web | favorite | 102 comments



If a language is not dynamically typed then it's cumbersome not to have generics, that's for sure.

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.


> It doesn't matter how clever those features are, overall simplicity and consistency matters more.

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.)


Generics add addition type checking at the the cost of complexity in other areas: syntax, compilation, runtime[0]. Heavily debated there, but what they are trying to get at is that generics have a cost associated with them, adding them is not free. The Go team is trying to search to see if there is some better way to solve this problem right now.

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.

[0] http://research.swtch.com/generic


Generics added complexity to Java because of:

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.


I don't think you understood my point. To repeat: there is no cost in adding generics to Go, because they are already there!

They are just non-generic (pun intended) - you cannot add it to every type, only to "blessed" types - arrays/slices, channels and maps.


> To repeat: there is no cost in adding generics to Go, because they are already there!

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.

[1] - https://news.ycombinator.com/item?id=8316995


I'm sorry. In all honesty, I have no idea how generics are currently implemented in Go. If you know, I'd kindly ask you to explain why you think the current implementation could not be extended to user-defined types.

> 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.


> It's like saying that functions aren't free.

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.)


boy I hear you there. That drives me completely insane about Go. They choose to have generics in these specific instances because if they didn't the language would be cumbersome. It's not enough.


It's still a vastly simpler language than pretty much any other widely used language out there. Consistency is superseded by pragmatism in the language design.


C ?


Go actually manages to be less powerful than C. You could do this generic stuff with macros in C.

Honestly, Go is probably one of the only language that is clearly not expressive enough.


I'm sorry but this is utter nonsense and shows the quality of most "Go lacks generics" discussions. Go manages to do so many things so much better than C [0], the boldness of your statement leaves me speechless.

[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.


You misunderstand me.

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.


You could compare any language to machine language and you can say that language is less powerful. Go is not even close to the power of C and assembly. I'm not gonna even try to argue.


I think that when you call something utter nonsense, and name a lot of reasons why you think it's utter nonsense, you should be obligated to explain at least one. It's not clear why the features you mention are better than their C equivalents.


Uh, which of those aren't just blatantly obvious? (Totally serious question)


None of them are obvious to me at that level of detail. (Totally serious answer)


Well it's pretty close to Java pre-1.5, which I guess was fine back in the 90s.


Precisely, more than 10 years have passed. And Java evolved for a reason, I think. And releasing Java without generics may not have been a smart thing even back then.


Except go has value types and closures and first class functions and implicit interfaces and multiple return values....


Yeah it wins over Java 1.5, but it looses against so many 80's and 90's languages, which offer all of that and, wait for it...., generics.


Oh yeah, and generic lists and maps And built-in testing infrastructure, performance testing, code distribution, linting, code formatting, parsing and AST libraries...


And a built in dependency manager, build tool, documentation generator...


But generics crosses the line because compile-time code generation is so much clearer and simpler and doesn't add any overhead to compilation times because code substitution is not compilation.


pre-Java 1.5 already had all of that.


Where was pre-Java 1.5's generic HashMap? Or closures?


> Where was pre-Java 1.5's generic HashMap?

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.


That is not a generic HashMap. See masklinn's response.

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.


In the discussion's context, "generic" is about parameterized types (type-safe collections), not about working on any object.


Which Go lacks and the OP was stating it has.


* Go has a generic hashmap, the builtin map type is one of the magical special-status generic collections (with arrays, slices and channels).

* pre-1.5 Java did not have one, its only parametric type was the low-level array


Ah, ok. That is correct.


I agree and I think Go has wholeheartedly adopted Java's philosophy stated[1] in 1997 by James Gosling -- "Java is a blue-collar language" -- and expounded in JavaOne 2005 in the following slide:

    * 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.

[1]: http://dfjug.org/thefeelofjava.pdf


> 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.


Well, Clojure isn't Forth and Go isn't BASIC, and they both have a similar number of built-in concepts, so I'm not sure your distinction is so clear-cut. But Clojure is certainly much more clever. As someone interested in large systems, I usually dislike cleverness in programming languages, but in Clojure the cleverness seems to be contained (though I'm not sure it's contained enough).


I always find this simplicity argument weak: SML is a simple language, simpler than Go (at least, in my opinion) and yet is more powerful with its Hindley-Milner type system and module system.


"Power" is relative; it's very hard to put values with different types (where the set of types is not known in advance) into a list with SML, except by manually re-implementing the equivalent of Go's interfaces.


It is very easy to solve. Two solutions:

* 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.


> it's very hard to put values with different types (where the set of types is not known in advance) into a list with SML

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 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.


It does not matter what kinds of children they make... a collection has a well-typed-usage. When you get an item out of a collection, you have two options:

(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?


That's fair. I don't know how SML deals with this, but Haskell handles it with type classes and (in your UI example) existential data types.

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{}.


It's interesting that the pendulum has swung the other way in favoring readability over features. In many ways, Go seems to have something Pascalian lurking under its surface - WOCA with static compilation (and fast compile times), readability favored over terseness/expressiveness, and deliberately trying to limit what can be done (i.e., no pointer arithmetic) so that there is a 'sane subset' by default.

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++)


It's a miscalibration to compare Rust to the complexity of C++. Rust is more complex than Go, absolutely and unequivocally, but that's because Go is hanging out with Lua waaaay out over on the simple side of the spectrum of programming language complexity. Given the enormity of its task, Rust is actually a rather minimal language itself (though there are definitely still a few features that I think could be made simpler).

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.


In what way are any of these examples more readable than "proper" generics? A copy pasted solution with 3 different specializations is three times the code! The replace-"T"-solution is hardly any better in terms of readability than a modern (D/C#/Rust/Java) generic Collection<T> would be?


What you are forgetting is that the Pascal community also had Ada, Delphi, Modula-3, Active Oberon, Zonnon as languages in the Pascal family with some form of support for generics.


This is a non-argument on all fronts. I also noticed the other day that embedded developers for the betterment of assembly languages wrote a modern OS with just plain assembly and some Perl scripts to fill in the parts where assembly was not cutting it because it would have required too much copying and pasting.


I personally like to think of simplicity as the opposite of entangled / intertwined, which is often at odds with easiness / familiarity. Rich Hickey has a very good presentation about it. I like doing that because then simple and easy are expressing 2 different things, both desirable but kind of orthogonal.

> 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


Extending the language is exactly what the go authors and most experienced gophers don't want. I shouldn't have to learn your whole DSL / language extension to use or even read your code. Go intentionally disallows this so that everyone writing go is using the same language, rather than some subset, or god forbid superset, of the language.


Well, it's a wonder that you're speaking English then, since English is routinely extended by means of new words and metaphors. Look, you just used "DSL" and "code", words part of a domain specific language that regular folks do not understand, plus "subset" / "superset" which require basic knowledge of set theory.

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.


There's a difference between adding nouns and verbs, and changing the very structure of the language. If I say "I like to flargle my pofdin in a huffalop" you can still pick out the verb and the nouns, but if I say "#&_35( bar mdfsgh" you can't even tell if what I wrote is valid English.


We aren't talking about changing the structure of the language, but about adding a feature to the language, generics, that would make it easier for people to build reusable libraries, especially libraries that treat functions (the verbs) as values that can be passed around, reused and still keep the static type safety of a language that is static.

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.


When you said "a small set of features is bad if users cannot efficiently extend the language because the needed features aren't included" I assumed you meant something like operator overloading or macros or compile-time templates like in other languages. It seems like I was misunderstanding you. If all you meant was "a small set of features is bad if users don't have generics", then yes, my earlier response was not appropriate.

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.


I also miss generics in Go. It is especially a pain to implement a nice high performance collections library without them. I have been working on and off on a data-structures library and you have to go through a lot of hoops to make it nicely usable and your still end up with `interface{}` everywhere.[1]

It should be noted that this is not the first attempt by any means. droundy implemented basically this several years ago.[2] The problem is these things are non-standard and I don't feel comfortable using them. I want generics but I want them as part of the language not as some weird third party CGI script. I also want them integrated into the template system. That way I can create type variables which must implement an interface. However, when the code is actually run, it isn't an interface object it is the actual object. Features like that would make them fit much better with the rest of the language.

[1] https://github.com/timtadh/data-structures [2] https://github.com/droundy/gotgo


The key innovation here is that it uses the URL to do the parameterization. That's not what is implemented in your reference [2].

I think this approach is actually quite clever. It's clever because URLs belong to a global namespace. If you have third-party code that is also using an instantiated generic, your code should be compatible with it because you'll be using the same URL. Whereas locally-generated templates are more problematic - compatibility depends on build scripts etc.


I agree this does solve the `go get` problem. However, a better thing would be to improve `go get` and make it use an actual build system. `go generate` is a step in the right direction but they expect you to check the generated sources in. I would prefer a tool chain that allowed your API customers to do code generation from your library. However, these things are more complicated and the Go Author's are probably right to do the simplest thing first.


It is worth pointing out that if the Go community settles on something like this as the solution, it negates one of the underlying arguments that the Go designers have against generics in the language. They have a set of criteria necessary for them before they are willing to put a solution in the language [1], and one of them is that they aren't willing to do C++-style template-based generics that involve compiling separate functions for every generic instance, causing the binary to grow in size.

But all the other solutions that are headed into that gap are just one level or another of convenience wrapper around exactly that. If the Go community just starts doing this at scale, then there's really no reason not to pull that up and make it official.

[1]: Whether or not you believe that they're just against them full stop and can't be budged no matter what, there's at least some claimed reasons.


Hm, I don't get this argument, does not the internal implementation of arrays and hash tables have to do the exact same thing, that is specialize to the type it holds? I get that it potentially significantly slows down compilation, since C++ style templates are antimodular. But you could easily compile a module containing templates in some intermediate form that allows for efficient instantiation, languages like OCaml demonstrate that you can have highly polymorphic modules through functors and still efficient and fast compilation. Of course it is probably too late for Go to get an ML style module system, because they chose to use much less expressive interfaces.


For context: Go has been my primary language both at work and for personal use for over two years now.

> It is worth pointing out that if the Go community settles on something like this as the solution, it negates one of the underlying arguments that the Go designers have against generics in the language.

It wouldn't really negate the underlying arguments; it would just mean that the community had grown and attracted developers who may not necessarily share that part of the language creators' original vision of the language.

> But all the other solutions that are headed into that gap are just one level or another of convenience wrapper around exactly that.

Well, focusing on "the other solutions" misses two things:

First, many (I dare say most) Go developers[0] don't really use any "solution" for this other than the features built into the language itself. In other words, they're not out there writing alternative solutions because they genuinely don't perceive a lack of generics to be as big of a problem as other things, which they do write tools for.

Secondly, there is a way to use interface{} to accomplish some (not all) of the same things that generics do, while still preserving type safety. Look at the functions in this Twitter client library I wrote: https://github.com/ChimeraCoder/anaconda/blob/master/users.g...

All the endpoints use the same apiGet() function ("generic"), but they all return concrete types, and they are all typesafe - you will never get a runtime panic from any of those functions. This is more than Java's generics guarantee, due to the quirks of the way type erasure works there[1].

No, this isn't "generics", but it provides an alternative way of solving the problem most of the time (in practice), and isn't that what matters? And yes, this is not quite as clean as some languages which focus on having a very expressive and precise type system (ML, Haskell, etc.), but it provides enough type safety for me without requiring me to think consciously about the types that I am using (which I have to do when writing ML or Scala).

[0] I mean people who write primarily Go at least 4-5 days a week, for work, as opposed to people who dabble with it for side projects but primarily use other languages.

[1] TL;DR: Java's generics are converted to "Object" after compilation, which means that you can actually end up with runtime type errors that are detectable at compiletime, due to inheritance. (It's been a while since I used Java, so I won't provide an example, but they're easy to find online).


I fail to see how your example is anything else but:

    Something do(Foo foo, Bar bar) {
        Object result = API.call((Object) foo, (Object) bar);
        return (Something) result;
    }


I write a reasonable amount professionally (not full time), and while in my personal code I don't feel the lack of "generics" all that often, I do frequently feel the lack of data structures, which I'd note is what the web page uses as its example.

As I've been thinking about this issue, it occurs to me that there are a few problems that get wrapped up into "generics", and "generics" aren't always the only solution, and people tend to conflate all the subproblems in their head when they say "Go doesn't have generics". For instance, one aspect of "generics" is to write "general algorithms" that don't care about the underlying data structure. Interfaces basically solve that problem. You write the "general algorithm" to a specified interface, and provide things that match that interface. It's pretty much "concepts" in C++. This is what the Go community correctly refers to when it says that we don't always need "generics" and that interfaces can be used instead... there is a specific and important use case for "generics" that is indeed covered by the interfaces.

The community is correct when it points out the problems are not as large as some external people are supposing, and the external critics are also correct when they observe that not all useful use cases are covered by interfaces in Go.

And I'd say the biggest uncovered useful use case is type-safe containers that aren't slices, arrays, or maps. What's more, Go's core target, network servers, is also precisely where the runtime characteristics of many of these more exotic data structures are important in the first place. Entire scripting languages are perfectly happy with arrays and hash tables as the only two effective data structures (or even just hash tables with arrays emulated on top of them!), but they pay a level of speed a Go programmer is not necessarily willing or able to pay, because network servers very often become performance sensitive. So my opinion is that whether or not that solution is "generics", Go badly needs some solution to the type-safe container problem.

(And I consider the "you can do it with interface{}" argument to be a dodge. It's a very convenient moment to suddenly decide you don't need static typing in an otherwise statically-typed language. Why stop there with that argument?)

Cover that one use case, and what's remaining isn't worth worrying about... it's not like the world is full of languages with perfect, quirk-free generics implementations either, once you start studying them they all have issues of their own. There is a reason the Go designers are resisting them, after all.

And I can't resist generalizing a bit... what's important is not the how of a solution, but the what. If you have a problem than in some other language you'd hit with generics, then try to pick up Go, then scream because Go doesn't have generics, that's not interesting if in fact interfaces would work. The problem with "Go lacking generics" isn't, well, Go lacking generics... the correct question is, what set of problems are there than Go lacks good solutions to? And, as I show above, I'm not saying that because there are no answers to that question... I named one, after all, and there's a couple of more I could at least argue sensibly about. The point is that the conversation ought to be focused on solutions to the specific problems rather than insisting that "the stick I know from another language" has to be ported in. And, it shouldn't be focused on things that Go does indeed have perfectly cromulent solutions to, because interfaces really do cover a lot of the use cases of generics, in much the same way that in C++ templated generics can be used to implement "concepts". There's definitely some overlap here which careful thinkers should be careful to keep separate in their heads as they think about the problem.

That said, I have to admit I haven't been able to sketch a solution to my type-safe data structure problem that doesn't look an awful lot like generics. But the solution space to this problem isn't bounded by my imagination. (There's some possibility that one could restrict the "genericity" to the point where it could only be used for type-safe data structures and dodge the other issues, but I haven't had enough time to noodle that over.)


And I'd say the biggest uncovered useful use case is type-safe containers that aren't slices, arrays, or maps.

For that, my current preferred approach is to wrap a generic implementation (using interface{}) with a concrete type. Like this:

http://play.golang.org/p/LkVWkyph75

There is some boilerplate code needed for each concrete type, but it is relatively small and easy to verify.


Just like Java, Object Pascal, Oberon and C++ back in the old days...


> First, many (I dare say most) Go developers[0] don't really use any "solution" for this other than the features built into the language itself. In other words, they're not out there writing alternative solutions because they genuinely don't perceive a lack of generics to be as big of a problem as other things, which they do write tools for.

I dare say that most people don't use generics, because most people are not library authors and abstraction builders. As soon as you get into that territory, the lack of generics starts to seriously hurt. Because it's one thing to build a component that's very specific to a job at hand, it's quite another to build something reusable and I'm not even thinking about reusability by other people.

One example would be data-structures. Sure you can build your own concurrent data-structure for moving bytes between threads, sprinkled with domain specific utilities that operates on it, because that's what your application requires. It's quite another job entirely to come up with a set of reusable data-structures that can be used no matter the contained values and that exposes high-level operators for manipulating them and then you end up building half-baked data-structures for each job at hand you have.

Another example is abstracting over common patterns by means of higher-order functions. Say you want to send messages between channels, threads, address spaces or what have you, in classic producer/consumer style, but without the producer carrying about whom subscribes to its notifications. Then you realize that your stream could be manipulated for fetching just the information that you want by means of `map`, `filter` or `flatMap`. Say you want to abstract away the loops one normally does with the atomic CompareAndSwap, so you think of operators like `transformAndGet(f: OldValue => NewValue)` / `getAndTransform` and `getAndExtract(f: OldValue => (Extracted, NewValue))`, which are not uncommon in other languages, so it's not like we are being original here.

Basically, every piece of abstraction that could happen with higher-order functions is overkill for a static language without generics in it. And people don't see it, because most people aren't thinking of doing abstractions by means of higher-order functions. And Go's interfaces are seriously not enough because we are talking about a different kind of polymorphism here.

And in regards to people coming up with their own solutions, I see it no different than with Perl and the lack of OOP-ish facilities baked in the language that drove people to building multiple, often shitty and incompatible alternatives. Yes, this eventually led to Moose, but the ecosystem is still plagued by older implementations, because open-source never really dies. And I would remind you that most people that were using Perl in the beginning had no use for OOP, it just so happens that people eventually did need the libraries that needed OOP, going back to my original point that it doesn't matter if most people don't feel a need for generics, the more important question being what do library authors think about it and I'm pretty sure that people publishing libraries aren't happy.


Go, which claims not to have either generics or objects, has both for built-in types. Channels and maps are both opaque objects and parameterized types. The argument that generics are unnecessary would be more convincing if those built-in types were not needed. The problem is adding generics without making a mess of things. This is harder than it looks.

In the example, there's a set of "int", and a set of "string". A new set is created with

set := set.New()

in both cases. It looks like you could not instantiate "set" for both types in the same program without a name clash. That's no good. Parameterized types for Go make sense, but the types need to be given a user defined name each time you create one. With "generics as a service", you'd have to encode that name into the "import" statement, which is getting a bit clunky.

Go's lack of generics is a reaction to the mess C++ made of them. In C++ the generics system turned into an entire compile time programming environment based on term rewriting rules. Nobody wants to go there again. But Go went too far.


> Go, which claims not to have either generics or objects, has both for built-in types.

Who is claiming that?

> The argument that generics are unnecessary would be more convincing if those built-in types were not needed.

This is a straw man that you're tearing down. The Go devs have always acknowledged the utility of generics. They just have a different set of trade offs that they are targeting.

More to the point, there's absolutely nothing inconsistent with the position, "If we can make writing code easier with a little blessed generics without sacrificing our project goals, then let's do it!"


Go seems to be re-inventing C++ and Java one step at a time.

Go didn't learn from history, and is doomed to repeat it.


That was my thought, unless the Go guys send a signal what they will add in the future. Otherwise you'll have tons of competing solutions and a lot of anger when a winner is chosen a year or two from now.

With C++ you have boost as a funnel, C# has MS giving previews of what they will add, similar for Java. If Go chooses to stay purist their world will fragment.


Whenever this issue comes up (and on HN, it never appears to die), I'm reminded of an eye-opening moment I had reading Rob Pike's excellent essay "Less is Exponentially More" (http://commandcenter.blogspot.com/2012/06/less-is-exponentia...).

An excerpt (original emphasis): "Early in the rollout of Go I was told by someone that he could not imagine working in a language without generic types. As I have reported elsewhere, I found that an odd remark...What it says is that he finds writing containers like lists of ints and maps of strings an unbearable burden. I find that an odd claim. I spend very little of my programming time struggling with those issues, even in languages without generic types...But more important, what it says is that types are the way to lift that burden. Types. Not polymorphic functions or language primitives or helpers of other kinds, but types."

It seems to me that people program heavily with types because that's what compilers are really good at. Therefore that's what most languages give us. But I don't want to be constructing huge, complex type hierarchies with every application. If I'm spending more time on that than the actual problem domain, generic type systems are more of a problem than a solution. I don't use a hammer because I love driving in nails, I use it because it's the best way to stick two pieces of wood together.

(edit: perhaps I should clarify that I'm referring to type hierarchies within the application itself as an intrinsic part of design, not language type hierarchies)


> It seems to me that people program heavily with types because that's what compilers are really good at. Therefore that's what most languages give us. But I don't want to be constructing huge, complex type hierarchies with every application

That makes no sense, most languages with good generics support barely have type hierarchies (let alone complex one).


I must be using a different definition of a type hierarchy.... Don't C# and java both have generics and type heirarchies?


Why in god's name would you take these as references when discussing generics?


Because they're the most commonly used statically typed languages with both type hierarchies and generics?


>> [...] most languages with good generics support barely have type hierarchies [...]

> Don't C# and java(sic) both have generics and type heirarchies(sic)?

Do you have an issue with understanding the meaning of the word "good"?


What's wrong to think that types are important? From mathematical point of view, types are interpreted as theorem statements and functions(values) that typecheck are considered proofs of those theorems. Types are fundamental.

See Curry-Howard correspondence http://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspond... or, if feeling adventurous, Homotopy type theory dubbed to be a new foundation of mathematics: http://en.wikipedia.org/wiki/Homotopy_type_theory and http://homotopytypetheory.org/


I don't disagree. I'm not saying types are bad or unneeded, simply that they can be overused. I think we are encouraged to use types to solve so many problems because they're one of the few things that compilers can easily check, rather than because types are the optimal solution.


If workarounds like these are common in the use of Go then leaving it out of the language was a mistake. The argument that generics comes at a complexity cost is nonsense: unless used it has no cost, the code would look like code does today. The complexity added by NOT having generics in a strictly typed language is enormous (as these examples show).


I honestly have mixed feelings about this. It is a really nice showcase of what the standard library can do (walking the AST and modifying it) with little amount of code.

OTOH it would be better to not make this a web-service. As much as it may seem to be convenient, integrating it with "go generate" (when it's ready) would probably be easier to use and not pose any security risks ;)


Since go generate isn't out yet and the web service can work now, it's a good prototype of what can be done. Obviously once go generate is official anybody could do something like this themselves, even if this particular implementation is never ported.


I agree the web-service is a terrible idea, I just wanted to see if I could implement it. It's also super insecure, as go fetches the package over HTTP (which you can see when you do go get -x gonerics.io/d/graph/string.git)


Actually, I think the web service is the only interesting part of the idea. Generics via string substitution are as old as the hills, I remember doing the same thing for Pascal 20 years ago.

(The factual accuracy of the statement that it was 20 years ago disturbs me. I'm getting old!)


100% agree. it's a break through not because of the problem it's trying to solve, but the approach.


What happens if you want two different types of set? As-imports? (I've looked at Go, but not written in it.)



But then I'm right back to having types named "IntSet" and "StringSet". Though at least they're not using copy/paste code implementations.


Well, they are using copy and paste, it's just automated.

But is Set<int> really better than IntSet? Don't they carry the same information?


being "idiomatic" is now the most important thing in Go, apparently.


Idiomatic thinking in an idiotic language.

(I don't really think Go is idiotic, although I do think it is lacking -- even when you consider its aim to be minimal.)


How about my own types?


You can use them like this: gonerics.io/d/set/github.com/crazychrome/package.git.Type

The things it supports can be found in the tests https://github.com/bouk/gonerics/blob/master/template_params...


Awesome!

Although I'm not a believer in generic but I think it's a bfd in the programming field which will be as important as the REST thesis, or ror.

How about a service with similar approach that offers Parse to appengine migration: user calls a rest point like: generics.io/parse/github.com/crazychrome/package/user to configure fields and types requirements, then the service generate a repo contains codes can be deployed to appengine and 100% compatible with what Parse offers.


"If you think it's simple, then you have misunderstood the problem."

Bjarne Stroustrup


I found a better solution - abandoned the language.


What do you use instead now?


Mostly C++. As for Go's "niche", I'd still fallback to Erlang.


troll#, of course ;)


Throwing type-safety out the window is so 2006. Just use Scala.


CGI...shell script...


:)


anyone tested it yet? I mean, you can't put an unpatched shell scripted site in HN right after a new CVE and TELL us its a shell script... right???


Of course I patched it, and I'm not using bash in the first place https://github.com/bouk/gonerics/blob/master/cgi.sh




Applications are open for YC Winter 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: