Rather than arguing about generics in Go again, I'd be interested in reading about how experienced Go developers solve the problems posed in this article. (He may be wrong that there's no elegant solution.)
Also, if it can't be solved elegantly, perhaps adding merge() and a few other important functions to the language would be good enough? After all, Go already has the magic append() function for slices and we get quite a lot of use out of it.
I think that experienced Go programmers simply write the code using the types they need.
I think the essay is entirely correct: there are certain patterns that are difficult to write in Go. It's hard to write generic code that merges two channels into one. It's hard to write generic code that acts as a future.
But it's easy to write type-specific code to perform those operations. Most Go programs do not need to merge channels of many different types. So people simply write the code they need when they need it.
This is not to say that there is no use for generics. There is clearly a use for generics, and examples using channels are among the strongest use cases. I'm just trying to answer your question about what Go developers actually do.
In my experience solutions generally fall into two camps:
1) Create a "generic" version of the function using reflection / typecasts
2) Create a specific version of the function for your use-case
I don't have a ton of experience using channels. My code tends to be very imperative and I add the channel layer at the main application level rather than the library level.
So from his example:
> func merge[T](cs ...<-chan T) <-chan T
You can create a function:
func merge(cs ...interface{}) interface{}
Then call it:
merged := merge(c1, c2, c3).(<-chan int)
You lose type safety and pay some penalty for performance. Also merge is harder to write than it would be with generics.
But even languages with generics often have similar issues. For example you can't write a generic min/max in C# either.
min :: Ord a => [a] -> Maybe a
min = foldl' go Nothing where
go Nothing a = Just a
go a0@(Just m) a
| a < m = Just a
| otherwise = a0
You can in OCaml
module Min (M : Comparable.S) : sig
val min : M.t list -> M.t option
end = struct
open M
let go a0 a = match a0 with
None -> Some(a)
| Some(m) -> if a < m then Some(a) else a0
let min l = List.fold_left go None l
end
Point being that these problems with generics are reasonably solved. There are perhaps other problems, of course. OCaml should at least be a suggestion that compilation speed isn't really one of them.
Were there any unresolved issues? I really just couldn't tell, it all looked like general learning curve stuff to me(standard stuff you have to think about when using Go) and that he was lambasting how he chose to implement and what he had to go through to do it.
There's no generalized solution to every generic problem. The "cast to interface{}" is the closest to a linear mapping of generics, but if you're reaching for that all the time, you're doing it wrong. So the only sane answer is, "it depends".
The only use case that I have encountered that is essentially impossible in Go is the "generic data structure". Mind you, that's a bit of a big deal, even if it covered over by "generic" arrays and maps (which, further, does cover over a lot of the cases), but it is also the only case where it is actually a problem. Despite the fact Go is not functional, like, at all, I still find myself using my functional training in minimizing the surface area of an "object" as much as possible incredibly useful when programming in Go. I think if I were only an OO programmer trying to come over to Go I'd have a much harder time of it.
Most of the rest of the time you can solve your problem by asking what the code is really trying to do, pull that up into an interface, and carry on. A substantial portion of the rest of the use case can be covered by providing wrapper objects that compose in another object, probably using an interface as the composition point.
I think in practice, about 80% of the problems that people trying to jam generics into Go are encountering are because they are trying to program C++, Java, or C# in Go. Despite their substantial superficial similarities, Go is not any of them, and idiomatic answers differ substantially. Let me rhetorically underline that... Go really looks like a Java or C# clone if you just read down the bullet points, but there are enough important differences that it's a substantially different experience to program in it. Trying to program Go as C# is only slightly less frustrating than trying to port FP idioms into Go (which is a complete waste of time). The remaining 20% are real problems that Go hasn't got a good answer for beyond duplicating code.
Now. All that said, despite the fact that people not used to idiomatic Go are significantly overstating the problem that is the lack of generics, it is a problem and were I in charge, I'd be trying to work something out on this front. (I actually have a proposal I've probably put about 4 or 5 hours of thought into. In defense of the Go developers, this is a nontrivial problem when you stop just waving the word "generic" around and start trying to seriously create an implementable suggestion, accounting for all the use cases, grammar interactions, semantics, etc.) My best argument is that for a language as mature as Go, this is embarrassing: http://golang.org/pkg/container/ Those are the "generic" containers in the standard library that ship with the language.... all three of them as of this writing, plus array and map. At this point that ought to be significantly richer.
(So, that ought to just about piss everybody off...)
Oh, I ought to add for context that I'm actually pretty fluent in Haskell, and my primary work programming language up to this point have been dynamic languages (Perl, Python, etc). So it's not like I'm not comfortable with generics or incapable of understanding how to use them or anything. If anything, I dare say my openness to other answers and then finding ways to express them in Go may be part of why this doesn't bother me too much. There are other options, most of the time, and they are not generally "compromises", either... they are often perfectly sensible options, or even better options than are available in other languages on a bang/buck basis, such as the trade for making composition very easy and inheritance something fairly difficult.
Talking about how to implement generics generically isn't really responding to my question. The original post had questions about how to implement specific concurrent constructs like pipelines, futures, and so on. So the question is how do you handle concurrency in Go? Are we missing important features that should be part of the language or standard libraries?
Some of these may be one-off generic functions like append() that could be added to the language without adding full-blown generics.
I know this is going to sound like an evasion, but the answer is that idiomatic Go doesn't involve pulling in patterns from other languages, but using ones native to the language.
Futures I can be specific about, though. You simply don't use "futures" in Go. Futures (as the term is used by most languages) are a hack around missing concurrency constructs, and in Go, they aren't missing. Use channels instead. This covers rather a lot of patterns from Javascript and the weaker languages, actually... you don't port them, you simply don't use them. They aren't that appealing once you learn the native idioms, which are, generally, quite a bit better.
Putting a "future" library up on /r/golang is a pretty common occurrence... but they're so trivial they're worthless. It's harder to "use" a future than it is to just do thing the future library is doing.
For pipelines, you just "do" them... you don't set up some declarative pipeline, you just... program them. Sort of like how in the Python world they prefer the for loops to map statements? Same thing, really... if you want a pipeline, just hook things together manually. They may miss out on some abstraction, but, honestly, in most imperative languages I'm underwhelmed by the concept of really generic pipelines. Haskell's making some progress on that front and I've actually got an "in the wild" five-stager, but in the imperative world I'm not sure I've ever encountered in the wild some sort of 6-stage pipeline that was better written other ways.
If any major concurrency pattern is missing from Go, it's an explicitly asynchronous concurrency pattern. However, this can be fixed by library code: https://github.com/thejerf/reign
Interessting. I have not done any go. Could you review go in spirit of you beeing a FP guy? Why does go make it so hard?
Im mainly a clojure guy and we generally belive the CPS and FP go together very well. The new interduction of transducers makes this even clearer and it seams to me this is a feature that go should really think about.
In a nutshell, map, reduce, filter, and function composition are all generic functions, in exactly the sense of generic that Go doesn't have. Lacking all those things, trying to do FP in it is a waste of time. Lacking this foundation, most higher-level FP stuff is also a waste of time, unless there is some other path to it. Even if Go some day picks up generics and gets map, reduce, and filter, my guess is that function composition will never be something you can do in Go.
Note that it does have closures, and makes heavy use of them. It just managed to pick up closures without picking up the rest of FP.
I think this generally fits into the spirit of Go being a really good language for working in a larger development environment, where you can't expect everyone to be expert FP programmers. And, honestly, an intermediate FP programmer is sorta dangerous to your source code, in much the same way an intermediate OO guy is. Go does a surprisingly good job of preventing the intermediate guy from messing up your code too much.
To expand on what I said about the principles of FP being helpful to me in Go even so, here's an example: http://www.jerf.org/iri/post/2923 In theory, there's nothing that prevents another OO language from doing that, but without the structural polymorphism that Go has with its interfaces it's so much less convenient that nobody does it, and, more importantly, nobody thinks that way.
In fact, in some sense I'd say Go is inspired by CSP, but to the extent that CSP involves the creation of these complicated networks of data flowing around, it doesn't really do that. On the other hand, I'm not convinced that's a very practical way to program... complicated networks just become one more source of "magic" in a program. Using CPS-esque tools to make it easy to connect components together is itself pretty powerful.
Also, if it can't be solved elegantly, perhaps adding merge() and a few other important functions to the language would be good enough? After all, Go already has the magic append() function for slices and we get quite a lot of use out of it.