> How to make it reusable? `<-chan interface{}`? Welcome to the land of types casting and runtime panics. If you want to implement high level fan-in (merge) you’re losing type safety. The same (unfortunately) goes for all other patterns.
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.
Language idioms are not set in stone (mostly). You have functions, variables, containers etc there. It's not like they invented a whole new way of computing. Not unreasonable for people to ask for some decent ideas that solve problems in other similar languages. Like generics.
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.
All understood, and you're right, but the tone of this article and that of its kin is at least equally bad at idea discussion -- it's not weighing out positives and negatives or trying to get an idea of why things are how they are, it's just angry that it can't do the thing it wants to do, and it wants you, and everyone else to know.
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.
The Go community seems to throw "you're using the language wrong, do it like this..." or "copying code is the way to go" or "those things you use in other languages shouldn't be used in ours." or "I have never needed to use that in my code"
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.
I think they are not workarounds so much as compromises made with a deference to the language as a whole, which fall at a different optimization point than what a lot of programmers are used to. Succinctly, features in Go are chosen so that they compose well with (i.e. are orthogonal to) all other features. The bar for new features is thus very high. They must be backwards compatible, and they must interact cleanly with the language as it exists. To focus on any individual missing feature in Go as evidence of anything is to miss the point. Quite literally to miss the forest for the trees.
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.
I think the mistakes Go authors recognize and Go critics talk about have very little overlap. So I won't be surprised if none of those complains get addressed. Especially since those problems can simply disappear by moving to numerous better languages available to all.
There has never been an argument on how concepts like .map, .reduce, .filter, functors, promises, futures are inferior to what Go programmers currently use.
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.
I don't think there's the same feeling of "community" with Go as with other languages, in the same way that there's not really a C community - more a bunch of authors, evangelists and contributors who have a "stake" in the language. That's sort of the point, though. Go doesn't really need much more than it has already. It's a deliberately simple language, with standard format and documentation, a plethora of examples and a comprehensive stdlib. It was designed to serve particular purposes, and it does what it does really well. All it really needs in the way of community is a bugtracker and a StackOverflow section, and I suspect that the a lot of the "Go community are arrogant/negative/dismissive" type complaints can just be attributed to it being the same few people hearing the same complaints about lack of My Favourite Feature X over and over, despite the many blog posts, release notes and list discussions explaining why it's not there.
As long as we're talking about purely generics with no introduction to type classes, the obvious ones people point out:
- 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.
> Either way, the complexity is seemingly already in the compiler.
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.
I think you are right. And I feel like if I'd read something like this about any other platform I'd feel the same as you.
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 reminds me very much of the history of the Java language. The interesting thing about Go is increasingly the runtime/VM and decreasingly the language itself. The same became true with Java vs the JVM. Remember that people like Gosling and Van Hoff used to proudly proclaim how hard they worked to keep out features from Java? Remember how eventually languages like Ruby, Clojure and Scala kept getting more interesting while Java festered (and eventually began stealing from these other languages)?
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.
Go runtime is not independently available. This is first time I heard that people are more interested in Go runtime than Go language. It is likely many people care about language being interesting but, where I work, most debate / arguments are about product features not language features. We have been using Java quite productively for long time and do not see any need to change.
> 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.
Swift isn't a fair comparison at all because it's a mandated language by Apple. For me Scala is just an improvement on Java. It's valuable to people who fetishize complexity, IOW, people who use Java and like it.
Scala is a highly versatile hybrid language that can be used in a way that doesn't promote needless complexity. Just like you need not use the most advanced OOP features, you need not use advanced FP features, nor do you have to use macros. If you don't want to.
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.
> Just like no sane person would start a new SaSS using Java
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.
And yet Swift can implement generics with just as much performance as Go. Swift has had limited forms of generics in the first few releases and in the 2.2 developer releases there are full generics; yet they compile to the same performant native executable code that Go does.
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.
Its not about performance, its about keeping the language simple. As someone who writes in Go, I really find this an attractive feature. I learned it in a few days, which is pretty fantastic IMO. Although I'm not sold on the Go shouldn't have generics argument, it does seem like Go has more pressing issues than implementing generics tho. Like bring the compile time back down to near instant instead of 300ms. lol
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.
I agree that systems and libraries you don't deal with every day should strive for simplicity and learnability, but I think simplicity is a much lesser virtue in programming languages.
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.)
To be simple is wrong, what is needed is to be minimal and to the point. Most of the time, the minimal tool is simple enough, but sometimes complexity is a necessary evil.
Driving a car is more complicated than running. Why do we invent cars in the first place?
> 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.
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.
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.
> 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.
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 :-)
There is a large cohort of programmers that don't understand map/filter/reduce. They love Go (and C) for not allowing it and forcing everyone to think purely imperatively.
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)
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.
Beyond Go and channels, this article/post touches on a subject that is personally a real pain to me.
> 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?
This is why clojure is awesome. It's a nice enough language, but where it really shines is that it gives you pretty much every concurrency tool under the sun. You have the entirety of java.util.Concurrent + all the cool clojure tools like agents,core.async,atoms,transactional memory, etc.
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.
Parallel concurrency is the Great White Whale of our time. Languages like Python and Ruby give you a toothpick to hunt with. Java gives you a letter opener. Go gives you a decent bowie knife. Clojure gives you something like a proper harpoon. But something eventually has to come along at some point to make it possible to not have to take down Moby Dick in the first place. I don't know what that is but I'd love to see it.
There is quite simply no argument that Java doesn't provide better complex concurrency support than Go. You can argue that the golang defaults are better, but there is nothing you can do in go that you can't do in Java and quite a bit the other direction that reverts to "program C with FFI".
I think this holds true for only Haskell and Clojure. You're missing something important everywhere else (especially STM).
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.
I have never seen Refs(STM) being used in a Clojure open source project, looks like they are great in theory but in practice they are another story, on other hand, core.async is much more prevalent, I do hear a lot about the use of Haskell's STM thought.
Yeah I think there is a very real cost to going the STM route when you can have arbitrary side-effects, so it's a lot harder for that tradeoff to be worth it than in haskell. That's not to say there aren't any downsides to a language that prevents uncontrolled side-effects, but being able to make more guarantees is one of it's greatest upsides.
In your run of the mill webapp you rarely have state outside the db and caches. What little you have can often be modeled with a single agent or atom. So no need for refs.
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.
I used to be a big proponent of Clojure because of the tooling and solid library support (what you mentioned, but also persistent immutable data structures). However, I never got over the runtime errors that kept cropping up due to a dynamic type system. So many typos, nil propagation, subtly incorrect function application, confusing macros etc.
I used to miss generics in Go, but after a few years of coding in both Go and Java I recognize the costs, but also the benefits of their absence.
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)
> 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?"
This has nothing to do with generics and everything to do with Java's heavy-handed approach to OOP.
High level abstractions are nice and I see myself rewriting certain patterns. But that's not only true for concurrency patterns, for object oriented patterns as well. Still most people write these things each time by hand, even if some language have baked in support for simple patterns like Delegate.
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.
If people are still writing it by hand each time, it's a problem with tooling, specifically dependency management. If it's that much of a pain to distribute a library, then people are going to write it themselves.
That's not a reason not to include generics, however. It's an entirely different problem.
This post is from Sep 2014. I know the author, he's a nice guy, but when he was at Golang Meetup, he was desperately trying to convince people to switch to Clojure.
It's all you have to know about the bias of this article.
Curious: Can you expand on his "desperation"? Handing out money? Begging on his knees? Giving away little fluffy stuffed animals? A promise of a date with Rich Hickey?
Hah. "Begging" is close :) He wasn't a speaker, but asked for a mic and started 15min speech for the newcomers, explaining that "Go has no immutable types", "channels cannot solve all the problems", "don't use Go, switch to Clojure" and so on. He also made up some example of board game, as a proof that language without immutable types is flawed and channels are not enough.
It was pretty awkward, really.
That's funny... Usualy those types of people are just a bit crazy and unable to make any kind of descent reasonning. But in that case i found the blog post pretty in-depth and well said.
A point often missed by new Go programmers is that channels are only for select. In other words, channels are a primitive for handling nondeterminism at the runtime level, not a general purpose queue. If there is no select statement, there is no actual need for a channel.
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.
You aren't wrong, but the strongest arguments for generics in go (and in many other languages) have to do with the abstractions around concurrency.
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.
People need to stop trying to force themselves using Go. Yes, good concurrency perks and easy deployment are handy, but Go basically demands people to code 1993 C-Object style since its authors basically rejected 20+ years of type theory. If it feels dirty don't promote that, don't promote a community that is in total denial when it comes to its language shortcomings.
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...
Yes, good concurrency perks and easy deployment are handy, but Go basically demands people to code 1993 C-Object style since its authors basically rejected 20+ years of type theory. If it feels dirty don't promote that, don't promote a community that is in total denial when it comes to its language shortcomings.
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.
> ... don't promote a community that is in total denial when it comes to its language shortcomings.
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.
> 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.
Nobody claims that they know better than everybody else, that's just your words, which you then contradict without the slightest bit of evidence. Truth is, the Go developera have a proven track record over several decades, and that gives their ways of doing things certainly more weight than those of young whippersnappers who have never built several operating systems, programming languages, or other large and complex pieces of software.
A lot of people are not free to decide that. Go has become very popular in young, mid-size tech companies. Most of the alternatives you listed are too new/fringe/unproven to consider. And, well, they're not made by Google
(corporate backing makes a big difference with new languages).
There seem to be a bunch of blog posts popping up recently criticizing golang channels (speed/ease-of-use advantages of mutexes, stdlib prefering mutexes, channels forcing awkward code, all the points in this post), and the lack of generics.
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)
I've been using golang for the past 3 years almost exclusively for very high traffic applications. I still maintain applications I wrote in the past in different languages, but any new application uses go on the backend if I get the final say on it.
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.
If you've only been doing something for 3 years you haven't been banking any longevity or career on it.
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.
If I work on projects for clients, golang is used as the backend of the project after I explain the benefits of the language and process I use as I'm able to get the job done quicker, that's all I mean by that.
I love go, we couldn't have built out our current software without it.
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's not a modern Java, it's an antiquated Java. It might aim to be modern, but Go is far from being a modern programming language, even if it was implemented in modern times.
I disagree; it's modern in its sensibilities about which features to restrict in order to fulfill its design goals, and it is informed by experience distilled from millions of developer hours at large companies since Java was designed.
Go will never have functions (well, this is a class in Java) like RequestProcessorFactoryFactory.RequestSpecificProcessorFactoryFactory, an actual apache xmlrpc server class name.
There is some irony to your use of the term modern here, as the original modernists coined the phrase less is more which nicely sums up the Go authors' approach.
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.
I have compiled two codebases this week; the first is an ios app that loads a screen and some sprites, and lets the user touch the sprites, then plays a sound.
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.
I really really doubt Go will ever get generics better than Java's, if ever. Generics bring in inherent intellectual baggage that the Go team very much tries to avoid (unsuccessfully fwiw: Many people eventually hit the question of "Why can't I pass []Type to []Interface if T fulfills Interface?" or in other words "why aren't Go slices covariant?")
Simpler enough to use and reason about that tools in such languages have been mathematically verified a lot by relatively small teams. The imperative, popular alternatives are usually PhD projects for people attempting such things. Speaks volumes about the intrinsic complexity of reasoning about such things.
You like the dependency management & testing stories of go? I'd say those are pretty common universal complaints from the people I know programming go.
Are you referring to vendoring? That's where I usually see the most complaints -- I mean the fact that something like "go get" was baked in from the get-go (to be fair, just about every modern language no considers this important). Not all languages came with any pre-baked (and decently considered)
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?
Also I wanted to add, if you are having problems with testing in golang, I've found great success with Gomega, Ginkgo and Agouti. They are fantastic libraries
And then some guy somewhere will tell you using third party testing libraries is not "idiomatic" and shame the maintainer publicly in a blog post like it happened before. "You don't need that in Go"™
Given the generalized complexity of getting sync stuff done correctly, I'm not going to try to bash this out fully in an HN comment, but you can do less-boilerplate Promises (in the sense the term is used in the blog post) by using composition instead:
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.
> 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
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.
"Because what you wrote isn't a promise: it's a condition variable."
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.
> 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.
I'm not planning on putting it in there, because it's one of those things that programmers use as an excuse. "Oh, I can't afford a stack, I need my code to go quickly. Even if it takes me longer to write. And crashes more often. And is harder to debug. And I can't use half my language features. And I'm just writing CRUD apps for my bank."
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".
I completely disagree. :) Stacks have a large performance cost when you get to this the last level of optimization; we're talking 2x or more in tight syscall loops when you take cache misses into account. Moreover, the memory costs of stacks compared to the most optimized state machine can easily be 10x. You may not need this level of performance, but some people do.
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.
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.
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.
"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."
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.)
Yes, by no means let me claim that the idea is new.
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.
I stopped after reading "reason for panic?" paragraph. Basically, if you don't like the tool or you are not too comfortable with it, don't use it. If you want to pass arbitrary stuff through a channel you can for example serialize to JSON or even use things like protobuf if you are religious about memory. If you really want to send something random, then there is something wrong with your project.
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'.
I think I was not clear - I mean essentially what you replied. I think this is still a try to apply mindset of one thing to another. You can't do generics in Go (well, you can use template system if you really want), so why would you try to solve the problems with generics in mind? It is like you were really upset that getting family in an industrial truck to Disneyland is not comfortable. If you have a problem that requires generics, use something else than Go.
The kind of channels* the article talks about are not delivery mechanisms, but primarily a synchronization primitive. They have little to do with serialization, and indeed "sending" references to data without having to encode them is critical in many cases.
Besides, serializing data throws away type safety just as much as a catch-all type (in go, interface{}) does.
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.