7 ms·
Avoiding Complexity with Go
- cageface 12y agoI guess having to reimplement basic algorithms for every new container type is an example of avoiding complexity then?
- cjslep 12y agoFrom the article: > Go isn’t perfect for every task. [...] Ultimately, it is up to you and your team to decide the best language and tools with which to build your application. While making that decision, I hope that you will take a moment to weigh the trade-offs that come with your choices. It's a tradeoff based on your real, hard needs. If you decide that only having Array<T> and Map<T> is a deal breaker, and the complexity of creating a tool to autogenerate the go code for a large number of SinglyLinkedList<T>/DoublyLinkedList<T>/CircularLinkedList<T>/etc... outweighs the uses of Go, then don't use Go. If you are fine writing a quick dirty code generator that can look for some <T>-like syntax and do simple replacement, or only really need Array<T> and Map<T>, then consider Go. It's not like the language is forcing you to use it against your will.
- account_306 12y agoNot yet, anyway.
- xg15 12y ago> If you decide that only having Array<T> and Map<T> is a deal breaker, and the complexity of creating a tool to autogenerate the go code for a large number of SinglyLinkedList<T>/DoublyLinkedList<T>/CircularLinkedList<T>/etc... outweighs the uses of Go, then don't use Go. With all due respect, but how is having to build a custom code-generator first, before you can write your actual program not accidental complexity?
- cjslep 12y agoI wasn't claiming it isn't accidental complexity. Just pointing out the author essentially says to weigh the decision appropriately. Looking at the sibling comment about "go generate" by buro9, it looks like a custom code-generator won't be needed either. EDIT: Added buro9, and emphasis
- dilap 12y agoCome now -- you don't have to re-implement containers for specific type in Go any more than you do in, say, Python -- you just have to cast when you remove objects from the container. Not great, since you're losing compile-time safety for the type -- but no worse than every single line written in a "dynamic" language like Python, and no worse than pre-generics java, say, or obj-c.
- dons 12y ago> no worse than pre-generics java exactly.
- deleted 12y ago[deleted]
- m0th87 12y agoPython's dynamic typing saves you a lot over static typing without parametric polymorphism, as in Go's case. As a concrete example, take sorting. In Go, you have to implement `sort.Interface` for each custom type you define. An example taken from the `sort` package: package main import ( "fmt" "sort" ) type Person struct { Name string Age int } func (p Person) String() string { return fmt.Sprintf("%s: %d", p.Name, p.Age) } // ByAge implements sort.Interface for []Person based on // the Age field. type ByAge []Person func (a ByAge) Len() int { return len(a) } func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } func main() { people := []Person{ {"Bob", 31}, {"John", 42}, {"Michael", 17}, {"Jenny", 26}, } fmt.Println(people) sort.Sort(ByAge(people)) fmt.Println(people) } An equivalent in Python is quite a bit simpler: class Person(object): def __init__(self, name, age): self.name = name self.age = age def __repr__(self): return "%s: %s" % (self.name, self.age) people = [ Person("Bob", 31), Person("John", 42), Person("Michael", 17), Person("Jenny", 26), ] print people people.sort(key=lambda person: person.name) print people If it were just sorting, it wouldn't be that big of a deal. But there's a lot of nice abstractions missing from Go due to the lack of generics - e.g. higher-order functions like `map`.
- buro9 12y agogo generate is in 1.4 which will arrive shortly The proposal (the implementation is pretty much this) is here: https://docs.google.com/a/golang.org/document/d/1V03LUfjSADDooDMhe-_K59EgpTEm3V8uvQRuNMAEnjg/edit?pli=1 https://docs.google.com/a/golang.org/document/d/1V03LUfjSADD... If you feel a real strong need for generics, for something like a "sort by property of my custom type... do it for n types", then it's now trivial to generate that. You can code like you have the features the core doesn't support, whilst having the succinctness, performance and maintainability that the language delivers. I've coded Go almost daily for 2+ years and only a couple of times had the urge/need for generics... I dunno, maybe I'm the exception here.
- bradgignac 12y agoI've had the same experience - very little need for generics in Go on a regular basis. I really appreciated the following snippet from http://robnapier.net/go-is-a-shop-built-jig http://robnapier.net/go-is-a-shop-built-jig: "Probably the biggest complaint people have with Go is the lack of generics. And I did run into that in just the first couple of weeks of work on my project, and I wound up with a bunch of duplicated code to work around it. And then, when it was all working, I refactored out the duplicated code. And I refactored again. And in the end, the whole thing was simpler and shorter than what I would have done with generics. So again, in the end, Go turned out to be a language for solving real problems rather than a language filled with beautiful tools, and so you build real solutions rather than finding excuses to use your beautiful tools. Don’t try to make Go what it isn’t. If you’re trying to solve abstract CS problems in their most generalized forms, then you may find it frustrating. But if you’re trying to solve specific, practical problems in the forms professional developers typically encounter, Go is quite nice."
- mike_hearn 12y agoSo ..... he ended up doing far more work than he would have done had Go been like any other modern language? Without detail about what these refactorings were and why he didn't write the code that way at first, what lessons he drew from it etc this anecdote is not very useful.
- 12y ago
- melling 12y agoPeople need to start downvoting the "Go doesn't have generics" trolls. This has been addressed many times. If it really is a showstopper for you, that's fine. There's no value in trolling Go submissions.
- erikpukinskis 12y agoIs there some rule that only people who are done talking about generics may participate in conversations about Go? I was happy to see this discussion here.
- melling 12y agoIt's your "Ground Hog Day". I'm not sure how much more there is to get out of the comment: "Go doesn't have generics, doesn't work for me."
- voidlogic 12y agoI write Go all day every day and have since before Go 1.0, and I see this troll/complaint at least 100x on hacker news for each time I have to do it once in real life. On the rare event this actually matters I use templating tools if performance is needed, but if not (which is usually the case) I use an interface. But like I said, this is so rare. P.S. I write distributed databases / big data engines and distributed content optimizing proxy servers.
- Jare 12y ago> I write Go all day every day and have since before Go 1.0, and I see this troll/complaint at least 100x on hacker news for each time I have to do it once in real life I suspect that there is as much trolling in people who complain about generics, as there is survivor bias in those who reply they haven't needed them in X years of go. If you felt that you needed them, you wouldn't have lasted long using go!
- voidlogic 12y agoThose are fair points. I also think major part of the issue here is all the folks that try to write Java/C++/Python,etc in Go, rather than Go in Go. People install Go, write something in Go, paying no mind to go idioms, and complain. It happens on the Go mailing list all the time.
- NateDad 12y agoI truly don't see many people talking about leaving go after getting frustrated with no generics. If this were survivor bias, where are the casualties?
- Jare 12y agoI assume most people who complain about Go's lack of generics have at least tried the language. It doesn't take long to run into your first interface{} and subsequent typecast. So, if you define "leaving Go" as "not switching to it", there are many. :)
- rakoo 12y agoI guess real-world softwares don't need fancy containers other than a list and a map, and when they actually really do need them the copy-paste turns out to not be that much of a burden ?
- zeeshanm 12y agoCouldn't stop recalling this quote from pg while reading this article: "Really good software is not software that is written according to some particular design methodology. The really really good software is software that does something fabulous. Although, if you’re trying to do something fabulous it is unlikely you’d write the software in a lousy way, at least, and succeed." - pg
- leetrout 12y agoSource? If this is a snippet from one of his essays I'd love to read the whole thing.
- deleted 12y ago[deleted]
- zeeshanm 12y agoFast fwd to 2:37: https://www.youtube.com/watch?v=BDA0t49AaZ4&feature=youtu.be&t=2m37s https://www.youtube.com/watch?v=BDA0t49AaZ4&feature=youtu.be...
- cantankerous 12y agoThis article is about an opinion on writing software well, not writing good software.
- rubiquity 12y agoIt's hard to believe that this article would be on the front page if it didn't contain a reference to the Go programming language in the title. There is no technical meat about how to avoid complexity with Go in this article. It's a rehash of all the same things that have been circulating around the internet about Go recently. Yes, the standard library is great. Yes, it's nice your programs compile to binaries. But what about the criticisms of the language that are always left unanswered? I like Go a lot as a language for writing networking software. But if Go becomes the next mass adopted language for writing business applications, that will be a huge mistake for our industry.
- bradgignac 12y agoFair comment -- thanks for the feedback. I've received similar feedback from a friend who reviewed the post, and I completely agree. To be honest, I had to go ahead and publish what I had or it would have remained in my drafts forever. However, I'd like to follow up in the future and address some of the points you raise.
- jasode 12y agoIf you're taking feedback, I'd like to disagree on your examples of "accidental complexity". To me, accidental complexities are quirks of design that everyone would agree to do over differently if history replayed itself. Examples like that would be: -- Java calendar (and C Language ctime) months have index starting with 0 which means 0..11 for Jan..Dec but for days, it's index 1 which means 1..31 -- PHP inconsistencies such some functions being verb_obj while others are obj_verb and some have underscores and some do not -- MS Excel has incorrect leap year calculation for 1900 which means all other spreadsheets must duplicate this "bug" to be compatible Those are the types of "accidents" that arise out of ignorance or uncoordinated thinking. Andrei Alexandrescu (expert in C++ and D) has a similar concept he calls "unforced errors". On the other hand, you compared a Tomcat servlet container as being "accidental complexity" and Go as "reduced complexity" because the http hosting is all built into the exe. To me this is an example of moving complexity around so you don't feel it for your particular use cases of the Go language. In other words, if the Java community had a chance to do it again, I'm not convinced that Tomcat servlet would be baked into the standard Java distribution. If you bake it into the JVM, you've made the JVM Specification[1] more complex. If you leave it out of the JVM but include it as a standard class library, you've made the API library reference more complex[2] (Alternative universe: Why is the class library reference 3000 pages long?! Because it includes 200 pages to document a servlet container that most don't use.) Complexity (in totality) wasn't reduced. It was only shifted around. I'm not saying moving complexity around isn't desirable because it is (hence we have "abstractions") but it's not an example of solving accidental complexity. I think what happens is that we programmers find some language, or framework and if it matches our use cases, it's subjectively less complex. For the others who need generics to write algorithms, they write homegrown template code generators or resort to copy&paste which is more complex. So sure, Golang omits generics but it only makes it less complex from a language-specification perspective. However, it's actually more complex from a total project perspective. I think this explains all the contradictory posts about Django/Rails/etc being "simple" while other posts say it's "complex pulling teeth". At this point, I don't buy that golang without qualifiers of use-cases is objectively less complex. I'm writing server-side web services with golang and for that use case, I think it's less complex than other alternatives such as C++ or Nodejs. For Windows GUI apps, using C++ with Qt is less complex than Golang. [1]http://docs.oracle.com/javase/specs/jvms/se7/html/ http://docs.oracle.com/javase/specs/jvms/se7/html/ [2]http://docs.oracle.com/javase/7/docs/api/ http://docs.oracle.com/javase/7/docs/api/
- herge 12y agoI can understand his comparison of networking libraries in go vs python, but a lot of the benefit of go is that it's networking libraries were written within the last decade. How will go manage complexity around adding new (and better) networking code while maintaining backwards compatibility? Python fell into the trap of urllib, urllib2, urllib3, urllib 42, etc.
- bradgignac 12y agoIMO, this is one area where Go shines. In a non-compiled language, you have to bear the burden of the upgrade process when writing code AND when running code. For example, if you update Python on you application servers and there is a breaking change, you break running applications that may not have been touched for weeks, months, or even years. With Go, you build a binary at the time the code was released and you never need to touch that binary again. Your binary can run forever even if breaking changes are introduced later. As with Python, you still need to update code in subsequent releases to work with the breaking changes. However, in my experience, this isn't the hard part. The hard part is keeping your existing code running while staying on a recent version of your favorite language runtime. This is even more painful in an environment where multiple application run on the same server. If you write a new application using Ruby 1.9.x but an older application will only run on Ruby 1.8.x, you need to either split them across multiple servers or update the old application to run on 1.9.x. There are tools to handle this (rbenv, rvm, chruby, etc), but this is exactly the type of complexity I talk about reducing in my post.
- deleted 12y ago[deleted]
- jephir 12y agoGo still has some unecessary sources of complexity, for example: 1. It uses null (the billion-dollar mistake) to indicate optional values. A better designed language uses an option type. 2. It has conflicting idioms for conditions. Error types use the `if err != nil` idiom while map access uses the `if ok` idiom. This means that you read the main execution path downwards and the error path to the right, unless you access a map, then the execution path goes to the right and the error path goes downwards. 3. The `:=` operator declares new variables, unless you use it to declare multiple variables where one already exists in the same scope. As a result, you don't know if you have actually declared a new variable using `:=` unless you read upwards to see if a declaration for the same variable name already exists.
- nkozyra 12y ago#3 has always bothered me. I feel like: someString := "hello" someString, err := ReturnStringAndError("world") if err != nil { fmt.Println("there's no way this could happen") } else { fmt.Println(someString) } Should produce an error.
- nhaehnle 12y agoIt's a matter of pragmatism. When you have a sequence of functions that all return (res, err) pairs, it's extremely helpful to be able to use := even though the err is not redefined. Occasionally, I wonder whether := with several variables on the left hand side should have been to defined to redefine variables (shadowing the earlier definition), but obviously the Go people thought that such shadowing would be worse.
- NateDad 12y agoYup, this is why you don't need err1 err2 err3 err4... (I've had to do similar things in C# before). Also, := does shadow in a sub-scope. The difference between shadowing and simply assigning in the same scope is negligible (i.e. either way you can't get at the old value). err := foo() if err != nil { err := bar() // err shadowed here } f, err := baz() // err assigned here What would be the difference between shadowing and assigning on that last line? You can't unshadow without leaving scope, at which point the value you were shadowing also leaves scope.
- llambda 12y agoWhenever the topic of "simplicity" in software comes up, I feel obligated to point to the superb "Simple Made Easy" talk: http://www.infoq.com/presentations/Simple-Made-Easy http://www.infoq.com/presentations/Simple-Made-Easy From my personal perspective, I do not see Go achieving the kind of simplicity Rich talks so eloquently about. Instead, Go seems much more like an "easy" language. An example of easy versus simple in the OP's article is pointing to on boarding: Sure, your on boarding of new engineers may be /easier/ because Go is an ostensibly "simple" (they actually mean small) and familiar syntax. But that does not imply any correlation with writing simple software. I would argue the difficulty of writing abstractions in Go (especially around channels) actually tends to yield the opposite! Much like ORMs are a trap because they seem simple, so too are technologies which have such a specious quality of simplicity. It is important to establish how a given technology actually achieves simplicity in practice and I do not see how this article argues that successfully--that is not to say Go cannot achieve simplicity, but merely that this article does not seem to make a solid case, in my opinion.
- fedesilva 12y agoI agree with you. There is a difference between simple and simplistic. Easy is not always simple.
- edwinnathaniel 12y ago> Much like ORMs are a trap If that is the case, the same can be said with JavaScript, Rails, Ruby yes? (all of them looked simple yet you can screw up really bad, like awfully bad, like worse than Java complexity bad). I use ORM to do simple-to-medium complex queries enough to avoid N+1. My ORM also have tools around it to help me generate DDL from code as part of my build (of course one still have to ensure the generated DDL is correct with proper relationship and constraints and all that jazz, but my point stands). My ORM gives me the ability to write in either JPQL and SQL to do certain tasks like deleting a bunch of rows based on conditions. Those are handy enough. My ORM also helps me prevent against SQL injection attack too. How are these abilities are "traps" for me just as much as the C++ complexity are traps?
- 0xdeadbeefbabe 12y ago
- serve_yay 12y agoWe talk about complexity a lot, to the point where "simple" often really means "I like this", and "complex" often means "there is something about this I do not like, or do not understand". About the reference to "Go is a shop-built jig", it seems to me one way of reading that piece is that Go offloads the complexity to you to deal with, rather than offloading it to the toolchain (all the Ruby Gems, rbenv, etc stuff the author mentions). If you like to work that way then cool, but I would question the conclusion that the overall complexity has really changed.
- tonyplee 12y agoI have been playing around with go and found this issue. http://stackoverflow.com/questions/26411121/go-calling-c-function-order-of-import-fmt-import-c-is-causing-build-erro http://stackoverflow.com/questions/26411121/go-calling-c-fun... Folks from SO gave very good explanation on why. But because of the problem, I somehow felt cgo is like a "hack" because of the requirement that the C block code is in comments block and must be immediately follow by import "C". An additional empty line before import will cause the code build to fail. What do you guys think? p.s. Now I know requirement and I can live with it. But for a full day, I have hard time figure out why my code build ok and next moment after some "cleanup" it won't build anymore. Not until I finally id the line space is real reason the build fail. The error message also doesn't make any sense.
- al2o3cr 12y agoThere's an unstated tradeoff relevant to this piece: "accidental complexity" versus verbosity. The Go community seems to have come firmly down on the "typing more simple code is better", but that's not a choice that everyone's going to agree with. As a human-language equivalent, compare a jargon-laden sentence to the equivalent paragraph in Basic English. The sentence requires prior understanding of more things, but the paragraph may take a LOT of words to say anything.
- jaswilder 12y agoI believe the author was really focusing on "operational" complexity more than code complexity and language features. For example, a language choice like using Python can add accidental complexity because it usually requires a container (uwsgi, gunicorn, etc..), sometimes a front-end reverse proxy (nginx, etc..), some way to package up your application and dependencies (tarballs, virtualenv, etc.). You also have to deal with deployment host issues like is Python2.6 required and available on your OS, do you have multiple apps with conflicting shared libraries that need to co-exist on the same host, do you need some system libraries installed on the hosts, are components compatible w/ each other (nginx x.y.z + uwsgi a.b.c) and can developers run that on Macs (dev laptop) and prod (linux). I believe this is the accidental complexity the author is trying to highlight that can be avoided with Go. I think if you look at the overall system (dev workstations, deployment envs, OS choices, CI, config management, ops, etc..), projects that use Go tend to have fewer components to manage and integrate and result in a simpler overall system.