10 ms·
Channels Are Not Enough
- rdtsc 12y agoYeah it seem channels are basic primitives. Kind of a like a class in OO programming. Just like Erlang has explicit processes and message sending primitives So it is pretty simple and concise at that level. In order to build large systems there is OTP (or e2) that embodies and abstracts away some common patterns/behaviours: gen_event, gen_server, gen_fsm, supervisors, logging, distribution between nodes etc. I imagine over time go will acquire those kind of libraries (maybe it already has them?). There is also an interesting duality between Erlang and Go. In one case there are explicit processes (identified by PIDs) + an anonymous (implicit) mailbox. Where go has anonymous goroutines but explicitly identifiably channels. It seems they are duals. You can simulate one with another. And you can build concurrency patterns on top of them. I personally prefer Erlang's model to think about concurrency. There is an active entity -- a client request, a server, a socket handler, it has a an id/name, it can be monitored for liveliness, it can be halted, upgraded, killed, can send messages to it. Somehow to me that makes it easier to map to concurrent problem domains. Channels can ultimately do the same things but it is harder for me to design using goroutines.
- jorlow 12y agoThe problem (which the article touches on) is that you have to resort to interface{} to make things reusable since Go doesn't have generics. So you have to pick between using a library (which will handle edge cases better) or compile time type checking.
- stcredzero 12y agoHow about replacing certain compile time type checks with smoke tests and runtime assertions, or unit tests?
- pjmlp 12y agoWhy do what the compiler can do for me?
- stcredzero 12y agoBecause you like other features of the language and you just want to get on with it.
- pjmlp 12y agoOthers just switch to a proper language instead.
- ufo 12y agoInserting the assertions by hand is annoying and error prone - even if you want the assertions to be checked at runtime its still very helpful if the programming language inserts the type checking automatically for you. Another thing is that assertions can only check primitive type. To check if a function pointer or object respects an interface you need to add an extra wrapper around it to check all its return values (and this is so annoying to do by hand that noone bothers to do it)
- NateDad 12y agoYou don't have to resort to an empty interface, in fact, mrust experiences go programmers cringe when they see anyone using empty interfaces. The fact of the matter that much code can reused without being "generic" and often your code never gets reused. YAGNI and all that.
- djur 12y agoIt would be really useful if Go allowed you to define methods against types imported from other packages. That way, you could define whichever interface you needed against those types (using only its public API, of course) and then use those interfaces for collections, generic functions, and the like. The closest I've gotten to that has been to create a single-member wrapper struct. Go provides a little bit of sugar for that, but it results in a decent amount of boilerplate and explicit wrapping/unwrapping.
- NateDad 12y agoSimply embedding the type and writing whatever additional methods you need is actually incredibly easy. The boilerplate beyond what is required to actually define the new functions is really tiny type Foo struct { pkg.Bar } func UseIt(b pkg.Bar) error { return otherFunc(Foo{b}) } I think that's actually one of the places Go works really well. It sounds like you want something like C#'s extension methods, which I don't think are a good thing (I used them a bunch in a past job). The problem with them is that it means your code can spontaneously and mysteriously break if you move it somewhere else that isn't including the project that has the extensions. Extensions seemed nice, but they really only made the code a tiny bit cleaner, and the added complexity did not really make up for it, in my opinion.
- tel 12y agoThe point of generic code is not purely that it's easier to re-use. The almost more important fact is that generic code has less information about its inputs and outputs—this leads to a smaller design space and, consequently, an easier time designing the implementation and an easier time avoiding bugs.
- djur 12y agoThis is a really great read, and goes beyond just channels and concurrency. It seems to me that Go is designed to discourage developing higher-level abstractions. That was my sense using it in the past, and it's only gotten stronger over time. Remember that one of Go's primary stated goals is "speed of compilation"[1]. Simplicity and ease of learning are paramount, and it's easier to learn a language where any chunk of code basically follows the same rules as any other. [1]: http://golang.org/doc/faq#Why_doesnt_Go_have_feature_X http://golang.org/doc/faq#Why_doesnt_Go_have_feature_X
- tonyhb 12y agoThere's a way to make channels easy to use and abstract. Just make a typecast function. Details in a comment on the original post. The idea is that you create a library which communicates using channel interface. Great. Now you need to add `i.(myType)` wherever. So, create a function that accepts an interface, switches on type (switch i.(type)) and returns a value with your concrete type. It's a 6-liner solution to most of this rant about channels.
- djur 12y agoIn a statically typed language, it shouldn't be necessary to break type safety to implement basic abstractions. The only thing interface{} provides over void* is safe typecasting.
- frou_dh 12y agoWell that's not to be sneezed at, even though it remains crude.
- tonyhb 12y agoIn other statically typed languages you don't break type safety to implement abstractions. In go, where interfaces are implemented by default and `interface{}` covers everything, you also don't need to. It's just a little bit (~6 lines) of code to add to guarantee it while they figure out a nice way to add generics.
- 12y ago
- chimeracoder 12y agoIt's funny that OP uses Twitter as the case study at the end. I wrote what was then the first (and think still the only) Go Twitter client library that works with v1.1 of Twitter's API[0]. It implements automatic rate-limiting/throttling behind the scenes, and it returns values of concrete types (not interfaces) ready for immediate use. Keep in mind that, were I to write this again today from scratch, there are a number of things I would do differently (since I started it as a relative beginner expanded on it as my familiarity with Go developed, it's grown to be a bit over-engineered in places). But I still think it's a worthwhile example in this discussion. For concurrency, I wouldn't say that what OP is trying to do is going to be easy in any language, because OAuth in general kind of sucks[1] the Twitter API itself has a number of quirks that make it cumbersome in general, irrespective of language[2]. That said, Go was by far the easiest to work with here, because channels allowed me to abstract the pagination and the rate-limiting in a way that it would be invisible to all callers, but "magically" handled behind the scenes. Without going into too much detail, I can see that the way OP has designed his code looks a bit cumbersome. That said, while it's a reasonable way of approaching it, I don't think it's actually the best approach in Go given the language's idioms. One other thing I want to draw attention to is the use of the general-purpose function for issuing a GET request to Twitter, and how that is shared among the various functions that use it to return values of varying, but known, types. I don't want to use the word "generic" here because people expect a certain thing when they hear that word, but I will say that this function is (A) general-purpose, and (B) type-safe - it involves no type assertions, and the functions all return concrete types instead if interface{}. [0] https://github.com/ChimeraCoder/anaconda/ https://github.com/ChimeraCoder/anaconda/ [1] Don't get me started on this [2] I've written client libraries for Twitter in a few different languages, so I actually have a reasonable point of reference on this - at one point, it was my personal "hello world" for testing out a new language.
- dkarapetyan 12y agoI'm curious to see this general purpose function of yours. You are also addressing a different point than the article. The main point is that patterns like parallel map are impossible to implement in Go in a type-safe manner. This is a valid complaint depending on what is meant by type-safe and at this point the arguments for/against Go usually devolve into name calling and matters of culture. If you could factor out all that general purpose functionality from your code base, e.g. the rate-limiting and other things, and turn it into a re-usable library then that would be an entirely different matter. Whether that would qualify according to the author's definition of type-safe is another matter.
- wilsonfiifi 12y agoWouldn't a library like zeromq address the complications/shortcomings the OP highlights in his article, when working with Go channels on a regular basis? Using the 'inproc' for message transport with zeromq should give similar performance to pure go channels no? This Go client implementation https://github.com/pebbe/zmq4 https://github.com/pebbe/zmq4 by Peter Kleiweg also includes all the examples from the online zeromq guide which is great. I know it's not a pure ago solution but the OP does mention that '99% of time I don't really care if the response is delivered with a channel or a magical unicorn brought it on its horn.' I don't know, maybe I'm missing the point of the article.
- dkarapetyan 12y agoSure, but at that point what is the point of using Go? You might as well use some other language like D if all the parallelism and message passing patterns are going to be handled by a library like zeromq.
- spion 12y agoGo doesn't let you build abstractions - it offers what it does, and if its not enough - tough luck. What I dislike worst is the denial of the Go community and creators, claiming that generics are too complex and that you don't really need them. I dismissed Go not because of its lack of abstraction power, but because its authors and community is incapable of admitting problems when they see them. A similar problem with CoffeeScript (conflation of declaration and assignment + scope rules) and the authors' refusal to admit that there is a problem also made me dismiss it entirely. Every language/platform has problems. But not every language is in denial of them. We should all avoid those that refuse to acknowledge their problems - because that points to a much deeper, much more serious problem - a problem that cannot be eradicated with technical means.
- kyrra 12y agoFrom what I've seen is that th Go team has no desire to change the core language syntax at this time. They are spending most of their time on toolchain and runtime improvements. Go does have generics for map but you can't create your own. I think they will add user generics at some point, but it may be a long while before that happens.
- eloff 12y agoLack of generics != lack of abstractions. The Go community freely acknowledges that generics are a nice feature, and that lacking them is a pain point of Go sometimes. Although many Go developers will tell you that sometimes turns out to be not that often in reality, which has also been my experience. Rob Pike has outlined the tradeoffs inherent with generics here: http://research.swtch.com/2009/12/generic-dilemma.html http://research.swtch.com/2009/12/generic-dilemma.html The bottom line is it's not a feature that comes for free, and if most of the time you don't really need it, maybe the costs aren't worth it. That's a bold statement for a programming language these days, since generics is a central feature of every popular, modern, statically typed language. Whether they are right or wrong I won't attempt to say. The language may well get generics one day, but it's early days still and the team is rightly focusing on more important features for the moment.
- 12y ago
- georgemcbay 12y ago"I want all this <-done synchronizations and select sacramentals to be entire hidden" Then perhaps Go is not the language for you. There are a lot of design decisions in Go that seem arbitrarily restrictive at first but are there, AFAICT, to (as much as is reasonable) force programmers to write code where what is happening is explicit and obvious without having to dive down into layers and layers of abstraction to find "the magic". This is, IMO, a feature and not a bug, but YMMV.
- dkarapetyan 12y agoWhat exactly do you mean by "magic"? Most of the time when I hear people talk about "magic" they usually mean a theory or abstraction that they don't understand or don't want to understand. Are generics magical in your opinion?
- georgemcbay 12y agoWhat makes things magical or not (the way I think of magical) isn't about the theory of the feature but rather how they are implemented and how much is hidden from the person using them in terms of code complexity and just overall work being done relative to what they think they did. I'm perfectly comfortable with the "theory of generics", but when generics first came on the scene for C++ back during the time period I used to program primarily in C and C++ they were very magical. Not on the run-time side where the eventual result of any sane compiler is easy to understand, but magical on the compile-time side where when using them it was nearly impossible to determine how much time they would add to the overall compilation and how complex and unreadable the error messages would be if you had a problem. When you make a single simple syntax error and the compiler presents you with an error that is 2 pages long that is the result of an implementation that is, IMO, very "magical".
- dkarapetyan 12y agoThere is a bit of contradiction in your definition then. I don't think anyone using Go understands how the compiler does all the transformations necessary to get from Go to machine code. In that sense Go as a whole is pretty magical but you seem perfectly happy with that. Modern application programmers and even system programmers by your definition rely on a lot of "magic". Even the machine code these days is a layer or two removed from the actual metal with all the caching and microcode that reside on the CPU.
- ericflo 12y agoI think the author could use a technique like this to build the higher level abstractions they want: http://commandcenter.blogspot.nl/2014/01/self-referential-functions-and-design.html?m=1 http://commandcenter.blogspot.nl/2014/01/self-referential-fu...
- ufo 12y agoI remember seeing that post a while ago on Reddit. The impression I got from the discussion is that the whole "self-referential" thing was unnecessary and that the code would be much simpler in a language with generics. http://www.reddit.com/r/programming/comments/1w2y01/rob_pike_selfreferential_functions_and_the_design/ http://www.reddit.com/r/programming/comments/1w2y01/rob_pike...
- skybrian 12y agoRather than arguing about generics in Go again, I'd be interested in reading about how experienced Go developers solve the problems posed in this article. (He may be wrong that there's no elegant solution.) Also, if it can't be solved elegantly, perhaps adding merge() and a few other important functions to the language would be good enough? After all, Go already has the magic append() function for slices and we get quite a lot of use out of it.
- Rapzid 12y agoWere there any unresolved issues? I really just couldn't tell, it all looked like general learning curve stuff to me(standard stuff you have to think about when using Go) and that he was lambasting how he chose to implement and what he had to go through to do it.
- cdoxsey 12y agoIn my experience solutions generally fall into two camps: 1) Create a "generic" version of the function using reflection / typecasts 2) Create a specific version of the function for your use-case I don't have a ton of experience using channels. My code tends to be very imperative and I add the channel layer at the main application level rather than the library level. So from his example: > func merge[T](cs ...<-chan T) <-chan T You can create a function: func merge(cs ...interface{}) interface{} Then call it: merged := merge(c1, c2, c3).(<-chan int) You lose type safety and pay some penalty for performance. Also merge is harder to write than it would be with generics. But even languages with generics often have similar issues. For example you can't write a generic min/max in C# either.
- tel 12y agoYou can in Haskell min :: Ord a => [a] -> Maybe a min = foldl' go Nothing where go Nothing a = Just a go a0@(Just m) a | a < m = Just a | otherwise = a0 You can in OCaml module Min (M : Comparable.S) : sig val min : M.t list -> M.t option end = struct open M let go a0 a = match a0 with None -> Some(a) | Some(m) -> if a < m then Some(a) else a0 let min l = List.fold_left go None l end Point being that these problems with generics are reasonably solved. There are perhaps other problems, of course. OCaml should at least be a suggestion that compilation speed isn't really one of them.
- zak_mc_kracken 12y agoPretty much every article that is posted about Go ends up in a never ending discussion about the absence of generics, which completely drowns the discussions about Go. As a result, the material on the web about Go has a very high noise/signal ratio, which is unfortunate. Hopefully, the Go team will see this as one more reason to add generics to their language, but until they do that, Go will remain a niche language with a severely crippled potential.
- rdtsc 12y ago> Pretty much every article that is posted about Go ends up in a never ending discussion about the absence of generics Well either a lot of developers there looking at Go code are crazy, whiny and in general unpleasant human beings that like to make others' lives difficult or ... maybe it is a problem worth discussing.
- ianlancetaylor 12y agoI have to credit this as being one of the more original arguments for adding generics to Go: we should do it because it will increase the signal/noise ratio in discussions about Go. (Personally, I don't mind the ongoing discussions about generics in Go, I just wish they were less repetitive. It's very easy to say "add generics to Go!" It's a little bit harder to actually do it well.)
- zak_mc_kracken 12y agoYeah I was trying to insufflate new life in this tired debate, glad someone noticed :)
- toleavetheman 12y agoThe interesting thing about Go, that can help make sense of all its oddities, is that it was not created to assist the developer. Go was created for businesses, not developers. The holy grail of a corporate programming language is that all individual developer personality is restricted, such that _you cannot tell from reading code who wrote it_. This is all about long-term, large-scale maintainability for massive code bases at massive corporations. Disclaimer: I work at Google, I do not represent Google, and this is just my opinion after spending time "on the inside".
- the_af 12y agoI understand not having any fancy features in a "corporate programming language", but wouldn't language features aimed at reducing code duplication (like generics) help with maintainability?
- toleavetheman 12y agoI think the extra flexibility gained would be perceived as a negative. The ideal (from their perspective) is that there is only one way to do a particular thing, no matter how verbose it is. Google has recently been mostly Java (Guice, dependency injection everywhere), and has long preferred incredibly verbose code at the cost of occasional duplication.
- the_af 12y agoAgreed, it's likely what you say is the perception. But the thing is: > The ideal (from their perspective) is that there is only one way to do a particular thing The lack of useful tools like generics (and others) means there is no single way to do a particular thing: things must be duplicated everywhere (or ugly workarounds to avoid doing so must be employed, like casting from Object or using interface{} or whatever). Surely the benefits of better tools outweigh their inconveniences, even in a corporate environment? This is not a nerdy programmer's whim, but a major software engineering principle of direct consequences for any business. Taken to an absurd extreme, you can simply copy & paste code everywhere -- that's the simplest programming model there is, and even the most junior of programmers can handle it without having their learning skills taxed in any way. It just leads to maintenance hell, which is why it's frowned upon even in corporate environments.
- disputin 12y agoI stopped reading. The further I got the more the article seemed to be complaining not about channels but that Go isn't a functional language. That's right, it isn't.
- mavelikara 12y agoIn an essay titled "Why Pascal is Not My Favorite Programming Language" Brian W. Kernighan wrote: <quote> The size of an array is part of its type If one declares var arr10 : array [1..10] of integer; arr20 : array [1..20] of integer; then arr10 and arr20 are arrays of 10 and 20 integers respectively. Suppose we want to write a procedure 'sort' to sort an integer array. Because arr10 and arr20 have different types, it is not possible to write a single procedure that will sort them both. The place where this affects Software Tools particularly, and I think programs in general, is that it makes it difficult indeed to create a library of routines for doing common, general-purpose operations like sorting. </quote> Kernighan was one of the early C/Unix developers from Bell Labs. It is amusing to note that Go, whose authors come from the same background, getting criticized for something very similar. [1]: http://www.lysator.liu.se/c/bwk-on-pascal.html http://www.lysator.liu.se/c/bwk-on-pascal.html