Golang: channels are not enough
gist.github.com
gist.github.com
I can understand arguments for generics, but they're complaining about trying to warm a whole pizza with a toaster. You need to put individual slices in, it doesn't work like an oven. Cook it with an oven if you want, but don't try to make it out like the toaster is offensive and useless.
Yes, you might need to re-implement these patterns each time with separate types (slice up the pizza), but it's not really a lot of work for the extra speed and crunchy pizza it gives you.
I'm pretty tired of the frequency of these articles where someone takes a concept from a language, tries to force it into a different language with little care for its idioms and then complains that it doesn't work.
Maybe I like digging holes with a trowel, maybe I like the precision it gives me, maybe I've worked out a way to with a little extra effort accomplish the same work. If you work with a spade and a bucket all the time don't complain that you can't throw a trowel around like a spade.
Maybe if enough people complains about their toaster's pizza heating capabilities, manufacturers would release pizza heating toasters. Or at least explain why, technically, it's not a good idea. Not yell people "you would burn down your houses! idiots! we are looking after you."
At least it feels like that to me, the condescending tone of designers / advocates is irritating in Go land.
I'm saddened that you feel like the Go community doesn't answer these questions, and I'd love to write an article on why these things are how they are and how they can be useful, but that's beyond the scope of a HN comment.
Yes, I disagree with the author of the article, but the primary thing I intend to satirise is his angry tone. If you've felt that the Go community has been condescending, I hope the condescending articles get equally as many critics.
There has never been an argument on how concepts like .map, .reduce, .filter, functors, promises, futures are inferior to what Go programmers currently use. Things like simplicity are thrown around but calling these proven abstractions "complex" is wrong. That's why they're useful fundamental abstractions -- because they make code simpler and reusable.
Generics have been around for a very long time. They have been studied in-depth in academia and industry. They're universally accepted but in Go.
Generics need not be like C++ templates. There are simpler implementations of generics. They are a solution to a real problem, specifically in libraries and creating type-safe abstractions. If you don't want type-safety, then why are you using a language with static types?
- Code copying is a workaround, not a solution. - Code generators outside the compiler is a workaround, not a solution. You're essentially transpiling a language that is no longer Go into Go because of Go's lack of support for such things. - interface{} is a workaround, not a solution. It automatically shows that Go cannot express a generic variant safely. - reflections is a workaround, not a solution. See previous point.
If the Go community is happy with these workarounds than that's cool, but be honest that's it's a workaround, a stop-gap to the lacking of Go's type system.
That said, I think the authors and the community learned a lot since 2009. I think a Go 2, correcting the mistakes of the initial implementation, and adding important missing features like parameterized types and native functional transforms, would be very compelling. But to do it without sacrificing any of the other good things about Go, like the compilation speed, or the easily parseable source, or the concurrency story, or interfaces... well, I'd love to see the (concrete, specific) proposal, because I don't know if it's feasible.
You've elaborated on the benefits. What do you think the costs of generics are?
- More compiler complexity - Compilation speeds might be effected - Without type inference, type signatures could be a pain point - Basic type parameters are not able to express all intents. Until you implement more complex like higher-kinded types.
Go's compiler already works with generics, although it might be built in an ad-hoc way. Either way, the complexity is seemingly already in the compiler.
Before saying "Compilation speeds might be effected", we should probably get some numbers to back that up. So I don't buy this cost right now.
The third reason is very valid as Go doesn't have type inference. You probably won't get Java's verbosity with signatures but you'll be specifying type information in a few places, at least.
I do not agree with dismissing this complexity on the hunch that the complexity is already in the compiler. Implementing generics for a few select types/functions is a completely different story than real useful generics in the hands of a user.
Consider, for example, how much larger the language specification would be. I think you've neglected this cost.
I also find your dismissal of type classes to be curious. I wonder how useful, for example, parametric polymorphism would be without some constraint based polymorphism, even neglecting higher-kinded polymorphism. I suppose ML might fall into this category at first glance, but its module system makes up for its limited parametric polymorphism in many ways.
> Before saying "Compilation speeds might be effected", we should probably get some numbers to back that up. So I don't buy this cost right now.
I think there's a mountain of real evidence that generics impacts compilation speeds, assuming monomorphization as an implementation strategy. Exactly how much isn't clear. There is a comparatively smaller amount of evidence that suggests compilation can still compete with Go's compilation speed. (I'm thinking of D's compiler, although I've never seen any rigorous benchmarking.)
My suggestion to you is: instead of being so quick to imply those that disagree with you are dishonest, perhaps you might consider that there are indeed trade offs involved with more sophisticated forms of polymorphism that Go doesn't support, and therefore, the costs may not necessarily be worth the benefits. Not only is this a valid and honest position, but it's a reasonable position. Abstractions aren't always free, and too many of them can become quite costly.
With regard to futures and promises, I find the article What color is your function [http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...] very convincing with regards to their problems. The article touches on part of why Go's concurrency model is superior, but for an even more compelling argument, you should watch Rich Hickey's talk[http://www.infoq.com/presentations/clojure-core-async] about core.async which is Clojure's library implementation of CSP[https://en.m.wikipedia.org/wiki/Communicating_sequential_pro...], which is the concurrency model which Go has language level support for. The argument briefly is that CSP allows for concurrent code that reads sequentially and is therefore easier to reason about.
However, when I think about it, If I was moved enough to write something about the pain points of Go I would probably do it like the author did here. Not because I think the tone is correct in general but because I've seen that constructive criticism got thrown out by the Go community (I mean the people in decision making, not the entirety of users) over and over. I'd just be following their lead on the tone.
Also, this article does not have the same tone as the infamous PHP article (https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/) as in mocking it. It feels more like someone passionate about Go getting angry because they can't use it in it's full potential. It's good to have people like that.
Not the best way to do it though. You are right. But as I said, this has been the way Go people have been treating criticism.
It sounds a lot like the Go culture. Go 1.6 has no new features. Ho hum. Eventually Gisp will get STM, refs, agents and atoms. Just like no sane person would start a new SaSS using Java when they could choose other languages to run on the JVM that are much better, for example; eventually nobody will write new code in Go when they can use Gisp or some other language that has the missing features. It will happen.
For me (today) I just want to be able to add an element to the middle of a slice in Go without having to read the documentation.
> Just like no sane person would start a new SaSS using Java when they could choose other languages to run on the JVM that are much better..
I think Scala is 12 yr old and fair to say it has not taken software industry by storm e.g like Swift. It has maybe few per cent point market share of all JVM languages. And the superiority of their language that Scala developers claim all the time may ensure that Scala will not get any more popular than it currently is. In my interaction with Scala developers are they are more interested in talking how cool is Scala rather than what cool software they have developed with it.
This choice is what makes it an interesting and pleasant language to work with. I've often dealt with Scala code that is much simpler to understand than comparable Java code, but still more often than not, Scala code is made needlessly complicated.
That choice implies a certain kind of respect for the programmer: here's a very, very powerful tool, and use it wisely.
Go is the antithesis of this, and I get its point, it's the opposite philosophical direction. I think it's a direction that will not move the industry forward, it's a direction that will undoubtedly help us produce value---software, reliably---instead of doing something amazing.
Woah you're way off here! Java is still used very heavily in new SaSS. Very very heavily. Easily in the top 5 languages used by new SaSS companies for backend servers.
In fact, under the hood, Swift uses a similar error handling model to Go, in that multiple values are returned (normal/error). The difference is that in Swift, the syntax and semantics of the language handle that for you instead of requiring the programmer to type in "if err != nil { return nil }" repeatedly.
It's one thing to argue for performance and conciseness in a language, but being dogmatic to the point of refusal to adopt anything that was invented in anything more recent than the 1980s is really going to hinder Go in the long term.
Also Swift's compiled code does not get near the same performance as Go's compiled code. Even when you unsafely compile Swift's code, it doesn't get near. When you start embedding C function calls into Swift and turning off Swift's safety features then it has been shown to get near Go.
Please explain how .map, .filter, .reduce, .pmap are complex?
Swift is also built with LLVM, a backend that is far superior to Go's handmade one. I doubt the performance issues will persist long-term.
Go makes it an enormous effort to make something complex. You can do it, but you'll be typing a LOT. I think this is a disadvantage from both a writing and reading code perspective, but the Go mailinglists keep on repeating how it's a huge advantage. I may see a small advantage if you're trying to prevent simple programs from turning complex, but if you're trying to do complex things Go simply makes you suffer.
Writing out map/filter/reduce code invariably leads to 10-15 lines and 2-3 functions per map call, and more for reduce. You'll constantly be reading a lot of text and reducing it back to the map/filter logic in your head. Meanwhile there are a lot of things you need to remember.
I don't like it either. Things that are a 2-3 line map/list comprehensions in python and haskell are 2-3 files with 100 lines each in Go. And God help whoever needs to have map/filter working on things that come in over channels, then the complexity truly goes through the roof.
(for C: yes macros can "fix" this. If you don't mind horrible code)
Do you have a source for this?
Swift had the long term in mind anyway. The effort to create SIL, along with the industrial-strength LLVM pipeline, has led to a rock-solid foundation. It's hard to compete with a backend that has 4 separate IRs, each with its own suite of optimization passes, including hundreds of algebraic simplifications, a highly tuned register allocator, an instruction scheduling pipeline, and an autovectorizer, just to scratch the surface. The stage is set for an excellent compiler architecture, even if they're not there today.
http://benchmarksgame.alioth.debian.org/u64q/compare.php?lan...
You can see like most Swift benchmarks, its compiling with the -Ounchecked flag and using C.
-edit- I have no doubts of LLVM's capabilities. It just seems like Swift is probably already taking advantage of its optimizations.
The Go compiler performs nowhere near the level of optimization that LLVM does; LLVM is years and years ahead. However, LLVM is fundamentally a C compiler and so there can be impedance mismatch between Swift and LLVM. Hence SIL. This is what you're seeing, and once the gap is closed then Swift will reap the benefits of the hundreds of optimizations in LLVM.
fannkuch-redux looks near, mandelbrot looks near, n-body…
Swift is new to Linux; there's a new compiler snapshot about every 10 days. Swift has only been available on the benchmarks game 3 months for people to contribute programs. Early days :-)
https://github.com/apple/swift/commit/259d57bbe7784955108caa...
The problem was that the print operations was being done character-by-character and it resulted in the printing being a significant overhead in the execution of the tests themselves. With this change they saw a speed up of many times simply because it was using buffered output.
A programming language is something you use every day, so you'll inevitably invest time in learning it properly. A language you can learn completely in a few days is unlikely to give you as much power, convenience and maintainability as a more complex language. Choosing Go might get you and your team started faster, but wastes a lot of potential over time.
(Of course complexity on its own is not a good thing, some complex features may be prone to abuse, orthogonality is still important etc., but Go is way too conservative to be competitive long-term I think.)
Driving a car is more complicated than running. Why do we invent cars in the first place?
Go has very limited polymorphic capabilities (implicit interfaces that have huge limitations). And struct embedding isn't composition. Now if you're talking about interface { } everywhere, sure, Go is as polymorphic as Javascript.
I don't know why you claim that struct embedding isn't composition.
> It’s so cool to find yourself writing tutorial for beginners! And it's a bit painful when you are trying to implement big and sophisticated system(s). Channels are primitives. They are low-level building blocks and I highly doubt you want to work with them on daily basis.
Posts/talks that relate to beginners.
You know the drill, you only have a limited time to talk about something in a conference and you only have limited time to write a blog post. It's easy and tempting to cover the basics without diving in too much into the details.
What you are left with as a professional is very thin data on how to accomplish complex stuff.
If you look at Docker and all the talks/posts around docker, they cover the absolute basic. Where's deployment to production? where's alerting/monitoring?
One of the few people I see solve this problem is: http://patshaughnessy.net/.
I've been trying myself to do more in depth posts that cover more than just the basics, it's really hard work.
Can you shoot yourself in the foot here? Sure. But you also have the right tool for the right job if you know what you're doing.
One nice thing about STM in Haskell is that the type system will help prevent you making mistakes. I've benefited from the types tracking how I'm creating and combining database transactions, which makes it easier to compose them into one big DB transaction from smaller ones.
What you do have in webapps is synchronization between concurrent processes. For that the STM primitives don't offer much. The core.async on the other hand is a great fit.
How about something that was built on the design principles of Erlang, but with much faster execution speeds?
We use generics to define new abstractions. This allows us to reduce redundancy in code and provide succinct way of expressing complex concepts. The main cost of not having them is that we have to fully write out the algorithm more often.
At some point I realized that when I read someone else's Java code I often find myself digging through countless layers of abstraction, factories, builders, suppliers, visitors, delegates and each each time I have this thought in my mind: "so where the hell is this place where the stuff actually happens?" In Go, this place is right there inline in front of you or very few hops away. This is particularly important if you're trying to understand a corner case, e.g. how does this function/class behave when I give it an empty list or null.
Faced with the choice between having to understand an abstraction or an algorithm I prefer an algorithm. Most mainstream languages have generics. I'm happy there is one that does not.
(Disclaimer: I am by no means a Go apologist: I have very mixed feelings about the language overall. One thing I was hoping would be discussed in the article was that channels don't compose well with other concurrency primitives, e.g. one cannot select on a channel and a condition variable)
EDIT: formatting
> channels don't compose well with other concurrency primitives
This has nothing to do with generics and everything to do with Java's heavy-handed approach to OOP.
I'm actually quite fine orchestrating concurrency with channels and the sync package but I use Go since 2011. I suspect that people new to the language are tempted to write things more complicated than they really are. I understand that it's difficult to find out when to close a channel. But in reality, when it's complicated figuring that out, probably channels aren't used optimally.
Regarding Generics, there have been plenty of discussions on the Golang Google Group. Keeping the language lean and consistent also seemed to be the main priority.
That's not a reason not to include generics, however. It's an entirely different problem.
A well designed interface, not a primitive, is the correct way to express an Object or an Abstract Data Type in Go. Specifically, prefer a Queue or Collection interface over a raw channel. (Unless you need to use select.)
This is our fault though, for putting channels in every beginner example, while never really explaining select properly.
Golang: I still want generics
This post has little to do with channels.
In a language that supports generics you have a fighting chance of adding good concurrency pattern support above and beyond what the designated language gatekeepers designate. In go, you have to resort to codegen.
Plenty of alternatives like D,Nim,Vala,Crystal,Erlang and many more. You want the same developer experience as Go (go get,gofmt,golint,go-code...)? then develop the tools for these languages and drop Go. Don't settle for an half baked language just because "it's easy to start with",it's easy to start with because the standard library is dumbed-down. Just look at Go projects on Github where developers spend their time trying to fix the Go std lib in their own code...
What languages are popular and what languages are people getting stuff done in? Maybe the people in denial aren't the ones supposedly denying the supposedly "better" paradigm/model/type system? Maybe the ones in denial are the "misunderstood" languages?
For our young field of programming, this now qualifies as an old story: A "superior" computer language is misunderstood by the mainstream, and progress is tragically held back. It's happened before, and it will happen again. But I posit it would be better to think of each language as an iteration and to look for lessons learned.
As someone who spent time in such a language community, let me point out one such: Technology and paradigmatic superiority are only two ingredients out of many that contribute to the success of a language community. Do outreach. Make your stuff "just work" for the beginner. Be able to solve real problems while not creating too many new ones. Also mind the overarching lesson of all software history: Pragmatism wins the day.
The point of Go was not to be "up to date" on type theory. The point of Go was to have a language that minimized some of the problems that you run into when you try to maintain 10 million lines of code for two decades. The authors/designers of Go didn't feel that "modern" type theory was the key to doing so. Of course, the modern-type-theory people think the Go designers are wrong.
Who's right? It's too early to tell. But for what it's worth, I'm pretty sure that the designers of Go have more exposure to modern type theory than the modern-type theorists have experience with maintaining ten million lines of code for two decades.
See, the problem right here."Go designers know better than everybody else". No they don't. The standard library itself is totally inconsistent and goes against what Go designers themselves promoted as being idiomatic.
> The point of Go was not to be "up to date" on type theory.
Sure it's a dynamically typed language that mascarades itself as a statically typed one, a bit like C with its void pointers everywhere... Except it doesn't have C macros, which makes it even more painful to program than C.
The Go designers know the problem area that they're trying to solve better than (almost) everybody else.
I'm starting to think I might have drank the kool aid too early (grateful, however, to read something that caused me to reassess), as I have recommended Go at least once to coworkers.
Is it fair to say that these are some of the last remaining warts of the language? I still like a bunch of other things about go (super ez cross compilation, package management system, fantastic stdlib & testing support)
As far as testing goes, I find the stdlib to be adequate, especially for relatively simple programs. Are the complaints your familiar with over the lack of more powerful testing tools like quickcheck?
Yes but not just that, the stdlib test library is missing basic functions you will find in other testing libraries (things like equality asserts etc).
It is of course adequate. But its certainly not great.
I've heard ppl complain about dependency management but what about testing ? Are there specific issues with testing in golang?
Channels are .. not great. They are too slow for many use cases.
That said, I'm building out a tool right now that uses channels in a nice way; it takes input from a bunch of spots, and the channels magically force it into an orderly queue which is operated on. That's quite a lot of code I don't have to write.
If the channel messages went up past a certain amount, mutex contention and throughput worries would push me onto a different angle of attack though.
Anyway, push go all you want. It compiles fast to a wide variety of statically linked binaries and is performant and easy to deal with. Most of the complainers about go are coming at it from what I would call a craftsman approach: go is a modern Java, not a Haskell with C friendly syntax. It doesn't want to be Haskell or Lisp, those languages can be very hard to use and maintain for junior developers in large teams.
It compiled fast
The second loads over 100GB of data and uses 60+ go libraries.
The ios app takes over 5 minutes to compile from scratch, the go program takes 16 seconds.
Actually, I've also compiled haskell from scratch (oh wow, that's like https://xkcd.com/303/ slow). At least 30 minutes.
And, I compiled the go compiler from scratch today to try out something on go tip. Compiling a separate compiler for something like 20 different cross compilation targets was under 5 minutes, total.
It's snarky to complain; while the compiler is slower than it was, it adds significant features and performance. It's emphatically not 'slow' in my opinion.
Go will never have functions (well, this is a class in Java) like RequestProcessorFactoryFactory.RequestSpecificProcessorFactoryFactory, an actual apache xmlrpc server class name.
Go is a modern language, in every sense of the word. You may not like it, other languages might work better for you or for certain situations (which is great, it really is), but don't pretend it is antiquated or created in ignorance.
Can you expand a bit on the difficulties that these junior developers had with Haskell?
People have been complaining about generics ever since I've began following golang, and people say that it'll never stick around and never find it's following in enterprise, but every month I see another large company launching a product written in go, or rewriting an existing product using go which saved them time and hardware resources or get job offers from recruiters specifically looking for golang devs.
Don't be afraid that you've drank the kool aid too early, I've been banking my career on it and the longevity of it and have had no regrets.
I don't have any opinion on the long term viability of go vs other languages, but "amount of jobs available" is certainly one of the weaknesses of go vs something like java. Which easily scales to the same levels of go.
type Promise struct {
*sync.Condition
err error
finished bool
// note no "payload" here with interface{}
}
func NewPromise() *Promise {
// return a new promise
}
func (p *Promise) Wait() error {
// code here to use the condition and bool to wait, or get the error
}
func (p *Promise) Finish(err error) {
// code here to mark the promise as finished or errored
}
type AccountPromise struct {
*Account
*Promise
}
func GetAccountPromise(f func() (*Account, error)) *AccountPromise {
promise := &AccountPromise{nil, NewPromise()}
go func () {
var err error
promise.Account, err = f()
promise.Finish(err)
}()
return promise
}
The boilerplate would only be that last function. The promise is used as: err = accountPromise.Wait()
// accountPromise.Account is now valid if err is nil
That's the reflect-free version. If you're still feeling crabby, I believe you could safely reduce the boilerplate even farther with some reflect usage. (By "safely" I mean that you can statically guarantee the loosely-typed reflect usage would still be correct.) That can be used with arbitrary callers of .Wait(), without an explicit queue (shared lock for the condition functions as the de facto queue).Though I still would ask serious questions about Go code that has extensive use of "promises". Generally I'd expect there's somewhere that sync.Once could be used, or some other solution native to Go rather than porting in promises. You don't really need promises in Go; for instance, to head off the objection that I'm not showing how to do any sort of ".Then()" function so this isn't "real" promises, Go's code "color" [1] is already fully asynchronous, so you don't need the usual array of Promise combiners to create a new function color. Go is already basically promise-colored itself.
Not that it's perfect. Generics to put together useful channel patterns would be legitimately useful. I've missed that several times.
But the composition is generally underrated in Go. The programming community has decades of experience in inheritance-primary language, but composition-primary languages afford a different style, even if both are OO in some sense.
[1]: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
That is a legitimate objection. Because what you wrote isn't a promise: it's a condition variable. The point of promises is to write asynchronous code.
I think the defensible argument here is not "you can write promises in Go" but rather "Go doesn't need asynchronous programming because threads are efficient enough in its implementation". That is an interesting argument, and there's some good evidence for it, but it's not the same as "you can write promises in Go with little boilerplate".
I always found the "what color is your function" article a bit confused for similar reasons. The points it makes are valid, but it goes off the rails when it claims that Go succeeds in marrying the two models (asynchronous and threads) without friction, when what it really did is just to optimize threads in an effort to make asynchronous programming not needed. If you've got a stack and a program counter, you're a thread, regardless of whether your implementation maps 1:1 to the kernel's notion of a thread or not.
Yes, I'm sort of cheating, because what the original blog post implemented isn't really promises either. It's hard to "really" implement promises in Go because whereas in Javascript you're implementing a different "color" language, in Go you're simply re-implementing the same color again. (And quite poorly, given Go's very weak/simple type system.) If you do good software engineering and remove redundancy, promises simply collapse down to conditions, or disappear entirely.
I've tweaked my post to clarify I'm propagating the original post's usage.
The point I'm really shooting for is that you can avoid a significant amount of, but not all of, interface{} usage when you're trying to use something like that Promise by writing the logic in an object, then composing that in to another type-safe struct. You can not, of course, implement true generic containers that way.
I dislike not having true generics in Go, but I have found I like the composition instead of inheritance default quite a bit, once I adjusted. I think there's room for another new language to adopt that default and make some progress in spaces where Go simply refuses to play.
Re: threads, I agree completely. What I care about is having a stack, so I can do structured programming even in my "async" code, not the hardware representation of the threads. Really need to finish up my post on that someday.
When you write that post, make sure you talk about performance. :)
In particular, there is always a performance cost to having a stack, and not having one is much of the reason why nginx is so fast. nginx could not switch to a goroutine-like model without losing performance. There is no free lunch; threaded programming (goroutines, etc) is better for developer ergonomics, while stackless/state machine programming is better for performance.
It's a last-ditch optimization for when you've got nothing else left, or perhaps in your case, something that a language designer worries about. But at the moment, I see a lot more people grossly underestimating the costs of throwing away the stack than underestimating the benefits. It's a rare program that has been so hyperoptimized that there's nothing left to do but throw away the stack, and even if you get that far, you need to understand the full import of what that means, which is actually a great deal more than just "oh, I don't have these bytes in RAM anymore".
This leads me to the exact opposite conclusion that you have. I think that, in terms of language design, we should be figuring out how to design stackless coroutines in a way that is as ergonomic as the stackful ones instead of throwing up our hands and saying "we're going to eat this large performance cost because there's no way we can possibly design a language that makes stackless programming ergonomic and robust".
As language designers, we shouldn't be giving up before even fighting the battle. Because, if we do, nginx will be around and winning the performance fight, and people will (rightly in many cases) never switch away from it, and that old-timey C will persist forever. My former colleague Robert O'Callahan had a great view on "abstraction taxes": if your abstractions aren't zero-cost, you're incentivizing programmers to not use them, and you're forcing programmers to choose between good engineering practice and good performance.
IMHO promises make a lot of sense in JS because callbacks are just everywhere. But Go has channels to keep indentation levels flat...
Interesting, in that Smalltalkers in the early 2000's were having conversations about how inheritance was way overrated, and composition was perhaps a better way of doing things.
I've gotten in such traps myself in my current Go project, and I will be changing something that was trying to use "inheritance" into something that looks more like composition and the Strategy pattern.
Yes, by no means let me claim that the idea is new. I think the idea was well on its way to becoming the conventional wisdom before Go was even conceived and would have continued advancing even had it never been born. But, well, this is one of those things that disproves the claim that the computing world moves really quickly, because none of the other popular OO languages right now make composition easier than inheritance. The syntactic default matters a lot.
I'll say it again, because I really want to see it: I think there's room for another language that sits a suitable distance from Go, is still OO, but privileges composition over inheritance. (And has generics.)
You may also enjoy this: http://www.jerf.org/iri/post/2945
Didn't mean to seem that you were claiming that. I just think it's interesting how long this idea has been lingering.
The syntactic default matters a lot.
Oh yeah. We Smalltalkers, true to the Hot Air balloon mascot, talked a lot about such things, but we never did anything about it that mattered. A couple of people implemented Traits, and a few shops probably used them, but since there was no Smalltalk standard with real teeth, no one would bother to implement anything.
Because then it would be the same as the other languages.
func merge[T](cs ...<-chan T) <-chan T
reference?
The author doesn't want to send something random, they want to send things of a single type, where the type is defined at compile time, without re-writing functions like 'merge'.
Besides, serializing data throws away type safety just as much as a catch-all type (in go, interface{}) does.
* The naming "channel" is old: https://en.wikipedia.org/wiki/Communicating_sequential_proce...