14 ms·
Things about programming I learned with Go
- amelius 9y ago> Don’t communicate by sharing memory, share memory by communicating. Well, if you're using immutable data-structures, then structural sharing can be very useful. You can then pass large data-structures in constant time, and of course you can "modify" them efficiently using immutable techniques. As long as you don't perform any writes in a shared data-structure, the memory hierarchy should be perfectly happy and no delays or locks should be necessary. Of course, as you start scaling across multiple machines, there will be different trade-offs, as there will not be a shared memory bus.
- masklinn 9y agoYou're not "communicating by sharing memory" though, at least in the idiom's sense (in which "memory" is an alias for "mutable state"), the structural sharing is just an optimisation. Which may not even be desirable: despite being built entirely upon immutable data structures, Erlang only share large binaries between processes (AFAIK even the new maps are copied despite being HAMT) to ensure process heaps and thus garbage collections are completely independent.
- amelius 9y ago> Erlang only shares large binaries between processes to ensure process heaps and thus garbage collections are completely independent. That sounds unnecessarily restrictive. At least they should give developers a choice (e.g., "this process should receive integral data, and should not be disturbed by a GC cycle of other processes"). Also, concurrent (not stop-the-world) GC techniques could make this problem moot.
- masklinn 9y ago> Also, concurrent (not stop-the-world) GC techniques could make this problem moot. It would mostly introduce insane additional complexity. Erlang GC works per-process (each process has its own private heap and stack) and you'd normally create lots of small processes, so the GC is concurrent as an emergent effect of the system construction. Not to mention processes can be distributed across nodes for which your scheme completely breaks down, what's supposed to happen if you ask for memory sharing across the network?
- amelius 9y ago> It would mostly introduce insane additional complexity. GoLang has a concurrent garbage collector. > what's supposed to happen if you ask for memory sharing across the network Yeap, I mentioned that. Again, as a developer you want the choice. You don't want your language telling you "sorry, that's too complicated for your brain, so you can't do that".
- masklinn 9y ago> GoLang has a concurrent garbage collector. Golang also has a single, shared, mutable heap, it does not have tens or hundreds of thousands of individual heaps. > Yeap, I mentioned that. No, you did not. > Again, as a developer you want the choice. Er… no?
- suchabag 9y ago> Authors of Go wanted to give users more flexibility by allowing them to add their logic to any structs they like. Even to the ones they’re not authors of (like some external libraries). Except that you can't. "You "cannot define new methods on non-local type[s],". You have to compose them. That makes the struct function definition syntax a bit moot in my opinion.
- majewsky 9y agoI've never thought of that syntax as conveying a particular statement. They could also have done func *Receiver.method(arg int) instead of func (r *Receiver) method(arg int) The advantage is that you get to name the receiver instead of having to use a keyword name like `this` or `self`. I've come to like this design decision.
- leaveyou 9y agoHe speaks about non-local receivers. You can't have: func (r *otherPackage.Receiver) method(arg int)
- literallycancer 9y agoWhat's the issue with composing a new type that has otherPackage.Receiver in it, and defining the new method on the new type?
- oelmekki 9y agoI came to really dislike embedding interfaces and types, but I know I'm a minority opinion, on this. When I came to go from OO (ruby), and before I learnt to use embeddings, something blew my mind : I could always say where a function was coming from, and which functions were available in the current scope. No need to grep anymore to find from which parent class or which included module a method was coming from. And after I started using embedding all over the place, I realized that problem was back again. Go made me love the idea of "simplicity, not easiness" and the fact that I can get what code was doing immediately, without wondering where parts were coming from. Today, I prefer to avoid embedding altogether. I use function parameters instead of types, and this also has the advantage of avoiding initialization problems (and if I omit a parameter, I'm immediately warned by compiler).
- znq 9y agoDon't want to hi-jack the article, but we've built Bugfender using Go. The problem is, we thought this is an internal (experimental) project and so Go (as a new language) would be fun to try out. But then Bugfender started to take off and we ran into serious problems making Go scale. Not because it is a bad language, but simply we were new to it ourselves. Today we're still running on go and we're more or less "ok" with it now, but it was a difficult path to get there and while it's fun to use a new language (or new anything) it's probably not the best choice for a startup product. Also when you need to hire people. We've summarized our experiences here: - https://bugfender.com/blog/one-year-using-go/ https://bugfender.com/blog/one-year-using-go/ - https://bugfender.com/blog/go-pros-cons-using-go-programming-language/ https://bugfender.com/blog/go-pros-cons-using-go-programming... - https://bugfender.com/blog/three-years-bugfender-9-5m-users/ https://bugfender.com/blog/three-years-bugfender-9-5m-users/
- IshKebab 9y agoI would have thought Go is easy to hire people for. It's such a simple language and has such a well-designed standard library you can pick it up extremely quickly. I think "anyone can learn it" is one of its main benefits. Compare that to Rust or Haskell for example...
- lordCarbonFiber 9y agoI think the issue is finding people that want to learn it. Systems programmers are turned off because of it's limitations and your average pythonista or rubyist isn't interested in mucking around in type definitions or even thinking about concurrency.
- chimeracoder 9y ago> Systems programmers are turned off because of it's limitations and your average pythonista or rubyist isn't interested in mucking around in type definitions or even thinking about concurrency Systems programmers were the bulk of Go's early adopters, even before it hit its first stable release. I can't speak for Ruby, but as for Python - I've been writing Go professionally for five years, and my first talk on Go was actually at the New York Python Meetup. They specifically asked me to speak about Go because they had significant interest from their members in learning or using Go. This was back when Go was still on its 1.0 release. Soon after that, Python added type hinting and concurrency to address needs that Python programmers had. That was enough for some, but there clearly has been interest from Python programmers in Go's approach, and there continues to be; Python is one of the more common language backgrounds for new Go programmers that I see. Furthermore, there's no shortage of experienced developers who already know Go. While the numbers are evening out a bit as more companies have begun to adopt Go, there are still more experienced Go programmers looking for jobs where they can write Go full-time than the other way around. (And that's not even looking at people who are inexperienced programmers or experienced programmers who don't know Go but might be interested in learning.) Go may not be for everyone, and that's fine, but there's really no shortage of good programmers who can write Go. If a company is evaluating languages and considering Go, availability of talent is a selling point, if anything, not a concern.
- krylon 9y agoMinor nitpicks, from someone who really likes Go: > It is possible to have both dynamic-like syntax and static safety Well, you can learn that one by coding in C#, too. In fact, I have the feeling this is a general trend across several relatively popular languages these days - provide as much as possible of the benefits of dynamic typing while keeping the benefits of static typing. I sometimes think how nice it would be if I could write my code entirely without type declarations and have the compiler or some preprocessor figure out as much of the type information as possible. > It’s better to compose than inherit It depends, really. Personally, I think composition is the simpler solution more often than inheritance, but sometimes it is not. I completely agree about the error handling, though - at first it was very tedious to handle all errors explicitly, but after I while I came to appreciate it. Once I had fallen into the habit of checking for errors without having to think about it too much, detecting errors became much easier, and deciding if I could "deal" with some error or escalate it (possibly to the point of terminating my program) became more straightforward, too.
- skocznymroczny 9y ago> In fact, I have the feeling this is a general trend across several relatively popular languages these days - provide as much as possible of the benefits of dynamic typing while keeping the benefits of static typing. Some languages go the opposite way. Dart 1.0 has optional typing, but Dart 2.0 will be statically typed (with type inference though).
- rbehrends 9y ago> Dart 1.0 has optional typing, but Dart 2.0 will be statically typed (with type inference though). Not quite; it's more that compile time typing is getting cleaned up. You can still omit types, which will then either be inferred or set to `dynamic`. The following is valid strong mode Dart: f(n) { if (n <= 1) return 1; else return n * f(n - 1); } The following will no longer work: int x = "foo"; In short, you can still omit types. It's just that if you declare them, they are enforced [1]. Dart will also infer them if possible, i.e. the following is illegal: var x = 1; x = "foo"; Here, x is inferred to be `int`, which makes the assignment of a string illegal. However, the following works: var x = true ? 0 : []; x = "foo"; Here, the type of `x` cannot be inferred, so it becomes `dynamic`, and therefore the assignment of "foo" becomes valid. You can turn this off with --no-implicit-dynamic; with this option, all types must either be declared or have to be inferable. [1] Sometimes not at compile time, though: Dart allows implicit downcasts and covariant generics at compile time and will insert runtime checks to catch those situations.
- rbehrends 9y ago> It’s better to compose than inherit I know that this is just a restatement of the "composition, not inheritance" mantra in Go, but it still makes about as much sense as "product types, not sum types". A more meaningful statement would be: "use inheritance to express sum types, use composition to express product types." There's no "better" relation between the two concepts, each has its own distinct purpose. Yes, inheritance has traditionally sometimes been abused to implement a limited form of product type (for example, to work around Java's lack of proper value types), but that's a misunderstanding of what inheritance is used for. > Go doesn’t have the concept of inheriting structs by design. Go has automated delegation, and as we know, (automated) delegation IS inheritance [1, 2]. Some implementations of delegation are a bit more limited, some are a bit more expressive, but fundamentally they have the same purpose. [1] http://dl.acm.org/citation.cfm?id=38820 http://dl.acm.org/citation.cfm?id=38820 [2] http://dl.acm.org/citation.cfm?id=900985 http://dl.acm.org/citation.cfm?id=900985
- pm215 9y ago'A more meaningful statement would be: "use inheritance to express sum types, use composition to express product types."' I'm not sure how well that works as a meaningful statement, because it assumes that the reader has a solid grasp of what a sum type and a product type are. If not (and I make the perhaps dubious assumption based on my own experience and knowledge that most programmers, especially most people using mainstream languages like Go or C++ or Python will not) then it doesn't really convey any information, unfortunately. I think a lot more people will know roughly what inheritance and composition are.
- nine_k 9y agoIf you find yourself not knowing something apparently important, maybe it's worth spending half an hour with Wikipedia and the like to gain the understanding? Among other things, the idea of a sum type helps understand the nature of the "billion dollar mistake" which is the inclusion of null in C, and why it pops up in other languages, and what more civilized methods of handling it might be. It will help you as a mainstream programmer, too.
- brango 9y ago"There is nothing exceptional in exceptions" No there's not, but it's a royal pain the arse to have to keep passing them up through your function calls to the level that actually cares about them and will do something about them. Try/catch eliminates boilerplate.
- ordu 9y agoDoesn't Go have any syntactic sugar for pass errors out of function? Something like Rust's Carrier/From traits and try! or `?`.
- camus2 9y agoNo, go "errors" are just a convention, there is absolutely nothing special about them. Go has panic/defer, which are exceptions, but done badly. They were probably retrofitted in the language when its creators realized they needed exceptions anyway, which makes the whole "error as value" a bit hypocritical. If they truly cared about error as value, then yes, Go would support some form of try! macro.
- stevedonovan 9y agoYes, Rust would be seriously irritating without try! (and its short friend the question mark operator). So I feel the pain of gophers here.
- pjmlp 9y agoNot really, Go community tends to be very vocal against syntactic sugar.
- jstimpfle 9y agoAnd even with exceptions, C++ is the only mainstream language I know that has a story how to undo any investments so far, including those that are not memory allocations. In any other language, you have to at least write wrappers that execute commands and catch any exceptions to do specific cleanup actions (like aborting a database transaction). And in C++, while it has a story how to do that, implementations must implement their own state to decide whether the reason for quitting is success or an exception. (this is more elegant in Haskell, which can do this with sum types, and for example monads on top).
- andreareina 9y ago> Thanks to goroutines and channels Go programmers can take a different approach. Instead of using locks to control access to a shared resource, they can simply use channels to pass around its pointer. Then only a goroutine that holds the pointer can use it and make modifications to the shared structure. How does this prevent data races if more than one goroutine holds a pointer to the shard structure and they're running concurrently?
- kasey_junk 9y agoIt doesn't.
- viiralvx 9y agoYeah, I think you'd still want mutex to handle that. I always use a mutex when dealing with goroutines and maps, for example.
- mpd 9y agoAs others have said, the scenario you describe wouldn't prevent data races at all. Under the channels model, you wouldn't want more than one goroutine to be accessing the shared structure. One routine would hold the reference to the resource, and other routines talk with the first routine via channels (i.e. share memory by communicating).
- innocentoldguy 9y agoIt doesn't. I think goroutines make concurrency easier that it is in, say, Java, but Go still passes around state and that is where a lot of the issues with concurrency arise. Concurrency is much easier to deal with in functional languages, where data is transformed via chains of functions, rather than stored in state.
- alphaalpha101 9y agoThe difference is that in Go, it is unidiomatic to share mutable state across threads, while in C/C++/Java it's not.
- 9y ago
- erikb 9y agoI'm no Go expert, so the following may just come from having not used it enough. The two things that shocked me out of continuing with Go were exception handling and package management. Exception handling is basically not implemented. Instead the Go developers dediced to add half a step from return codes towards exceptions and work with that. And now the community seems to have decided to just reraise everything that comes their way. Nobodoy seems to see that such a simple basic feature shouldn't require a piece of code, it should be done automatically by the language. And the community doesn't seem to see why it's nearly impossible to debug. I really hope that Go developers have some secret tools I just don't know about, because there are no stack traces, and on the abstraction level of a user getting an error from an underlying framework printed out doesn't really help at all, especially when the code seems to continue after printing the error (making it a bug, by reporting a warning as an error). My favorite example is kubernetes helm reporting a connection error in my job's infrastructure, every time you use it, but after that error it actually switches from IPv6 to IPv4 and just works. But still it feels good about reporting that error. wtf. The next thing is package management. I had high hopes here after going through the trouble of working on that topic in 2012 when the Python community was developing its package management. I mean if you don't add that on top of an existing world, but create it from scratch, there is a chance to just do it right and not have to bother with all the pains of legacy systems, right? Well, Go decided it doesn't need versioning on imports. Just put the Github link there, don't even choose a branch. How can any code ever get finished that way? My best guess is that now they have to develop stuff on top of that already-born-as-legacy system and also try to integrate that with their core. Sad. I really hope someone can add some corrections to this view.
- gjem97 9y agoThe only time I miss exceptions is when I really need non-local return. Stack traces are available, see [1]. Needing non-local return really turns out to be the exception though (sorry). I find I want it when writing recursive descent parsers, and some other deeply nested control structures. In those cases, the panic/recover mechanism can be helpful. But for everything else, returning an error (using a standard error interface) works just fine. As far as package versioning, what we ended up doing is vendoring everything into our source-control system. This fits our use better all around. For our production systems, we don't want inequality bounds on our library versions. We have tested our system with version X, we only want to ship with version X, not, X.1 or X.2. Vendoring allows us to pick a specific version. Admittedly, this makes more sense in the Go world, where executables tend do be statically compiled. Vendoring makes it trivial to track local changes to code in our dependencies, and ship them upstream when appropriate. It makes new developer setup simpler. Finally, the Go toolchain has support for vendored code that makes the setup relatively painless. And near instant compilation means that you don't have to wait very long for your dependencies to compile. [1] https://golang.org/pkg/runtime/debug/#PrintStack https://golang.org/pkg/runtime/debug/#PrintStack
- Ixiaus 9y ago"Error theater" and zero initialized values, while understandable due to other design decisions, are the biggest source of frustration whenever I have to work on go code. Off topic from the article, I think, but what go has taught me about programming is that some parts of our industry are stuck in time and intellectually stagnant. Go is a better C. I would prefer Go over Python. But really, when the state of the art (and production ready!) is light years ahead of Go, it frustrates me to no end to see colleagues spending so much time on things in their day to day that are solved problems in other languages.
- alphaalpha101 9y agoThis sort of attitude is never going to win over people that you are talking about. I hate to bring politics into things, but it's like calling all Trump supporters hopeless and backwards. Yeah maybe they are, but they're going to react to that by never listening to anything you say again, so even though it's true, it's not helpful. The 'state of the art' is increasing, over-the-top complexity to a truly ridiculous level. Go programmers don't want to open up the documentation for a library and see this: http://i.imgur.com/ALlbPRa.png http://i.imgur.com/ALlbPRa.png or look at the language reference and see this: http://en.cppreference.com/w/cpp/language/constraints http://en.cppreference.com/w/cpp/language/constraints and I don't blame them.
- deleted 9y ago[deleted]
- unbearded 9y ago> 1. It is possible to have both dynamic-like syntax and static safety IHMO: This convenience causes more harm than good in the long term. Note: My experience is that this is a minefield of bugs and defeats the point of type safety. Also, because Go lacks generics, there's a lot of boiler plate and interface being used as function argument (just check large open source repos on github and you will notice). > 2. It’s better to compose than inherit IHMO: It's better to have both tools available. Note: Inheritance, like static types, offer a more rigid structure and helps on large project that will exist in the long term. In a language that does not offer something like traits, "composition of behavior" ends up being a hack. Of course, we also have the tendency of blaming the language for the fault of the programmers. I would think the example of the Vehicle is not the best to explain the paradigm of behavior composition. > 3. Channels and goroutines are powerful way to solve problems involving concurrency IHMO: Powerful, yes. But I would use actors instead. Note: Same problem: as your business problem gets complex, goroutines start to become a pain and the code will become unreadable and littered with hacks. > 4. Don’t communicate by sharing memory, share memory by communicating. I read other comments here that express what I think better than I can articulate Note: You still have to "synchronize" if you are working with shared resources. There's no magic solution. > 5. There is nothing exceptional in exceptions Oh, Boy! This causes more wars than "space vs tab". IMHO: Good in theory. Too rigid in practice. Note: While works for small stuff, the the lack of exceptions becomes a problem in large projects, causing people to just ignore "for now" (and usually, forever). In conclusion: The "cool features" of Go might teach some people about programming, but take a toll on more complex projects. I would use Go for small system utilities and tools but would never touch business logic with it.
- mjkpl 9y agoHi, author here. Thanks for your opinion. > 1. On a daily basis I work in a 200k LOC Ruby project and to me from that perspective lack of static typing is a minefield ;) > 2. Since I have finally learned how to do proper composition (~a year ago) I haven't use the inheritance even once. Of course, you may say that my project is special, but I can't help feeling that inheritance is often overused. > 3. Yes, I've also come across opinions that Go's channels are too low level to be used in a large commercial project. But still, as a concept I find them interesting. > 4. True. But, you can have one goroutine that'll just "guard" that resource and communicate with it from many places using one shared channel. > 5. TBH I've seen more flame wars about the "space vs tab" thing ;) As I mentioned in the article I don't think that's the best error handling pattern ever invented, but I just like the concept of treating errors as regular return values. IMO it's good to have it at the back of your head, regardless of the language you use.