I like Go, but I feel that it only addresses one of the urgent problems in computer programming, concurrency. Another very important problem IMO is correctness and I find Go lacking in that area; the onus is squarely on the shoulder of the programmer. Languages like ML, Haskell, or Rust offer facilities to make correct code easier to write (or incorrect code harder to write, depends on your view point).
Rust appears very promising, but having followed it for some time now, it's still severely lacking in terms of stability.
I'm talking about the stability of the language feature set and the amount of change it's undergoing, rather than the quality of the implementation or anything of that sort.
While it's good to see the language evolving and improving, this type of churn also means it isn't very practical to use.
For example, some experimental Rust code I wrote a mere 6 to 8 months ago is now basically unusable with recent versions of Rust due to language functionality changes, syntax changes, and library changes.
I keep hoping that it'll start to stabilize, even just on a feature set and standard library that may not be perfect, but will at least be usable for longer than a few months. Yet this doesn't seem to be happening. This lack of stability will start to significantly hurt its adoption at some point.
Go has taken a better approach, in my opinion. It isn't perfect, and it's lacking some important functionality, but at least there's a version that's usable, well-supported, and usable well into the future, even if the language does continue to evolve in incompatible ways.
> While it's good to see the language evolving and improving, this type of churn also means it isn't very practical to use.
That's right. We never claimed otherwise.
> I keep hoping that it'll start to stabilize, even just on a feature set and standard library that may not be perfect, but will at least be usable for longer than a few months. Yet this doesn't seem to be happening. This lack of stability will start to significantly hurt its adoption at some point.
> Go has taken a better approach, in my opinion. It isn't perfect, and it's lacking some important functionality, but at least there's a version that's usable, well-supported, and usable well into the future, even if the language does continue to evolve in incompatible ways.
Go also started earlier and is less ambitious of a language (and I think everyone on the Go team would agree with that statement). It has nothing like the borrow check that has required delicate care in design, and research in every sense of the word, to create something usable. Earlier versions of Rust were not really usable for what we want to use it for—Servo—and there is no point sticking to a language that isn't the language we need. Even current Rust is not ideal for Servo (lack of custom smart pointers, most notably) and that's why we continue to evolve it. Nevertheless, we have a good idea of the feature set we want and the language is stabilizing.
I agree. This is going to affect Rust's adoption if they aren't careful. People are hesitant to write tutorials, documentation, or code that may need to be rewritten later.
But I still give Rust a pass on this for the time being. Rust is a younger language than Go (Rust is not yet at version 1, while Go is at 1.1.1), and Rust supports far more compile-time features.
A little known fact is that Haskell's motto was to "Avoid Success At All Costs", i.e. that a long development cycle was incredibly valuable for a language. It takes a long time and much experimental use for features to settle into a good configuration. Rust will suffer from poor adoption for a long time if they wait on that. Rust will also be a better language for it.
Other languages went through a lot of change too. They just did it behind closed doors. We could have done our development behind closed doors too so you wouldn't see all the churn, but that's not the way we work at Mozilla.
Furthermore, Rust is quite ambitious. Getting lifetimes and the borrow check to work right is hard and involved research in all senses of the word; read the borrow check source code sometime. We did not just change things because we felt like it.
Rust is starting to settle down, and once it has settled, it will be very stable. In particular, the 0.6 release was a huge breaking one, and 0.7 was less so. As it approaches 1.0, things should calm down quite a bit.
I'm not sure that you're interpreting such a stance correctly.
Any further 1.x releases should clearly try to avoid incompatibility as much as possible. Anyone seriously using a programming language does require a relatively high degree of stability with respect to language changes, library changes, and so forth.
But that in no way means that there can't be work done on a 2.x release that does introduce incompatibilities in an effort to improve the language and any standard libraries it may offer.
This situation is much more about controlling change, rather than preventing it outright.
We have no interest in stabilizing Rust 0.5, 0.6, or any earlier version. They are versions of the language that are useless for our needs—they weren't even memory safe (and fixing that required breaking some code).
The most realistic way to address your criticism would just to have been to keep the language a secret until it's mostly stable. This is what most other companies do (for example, this is what Google did with Go). But then you couldn't use it at all.
Non-nullable pointers, a type system which is not full of holes, inability to cast. C and C++ obviously add (remove?) such things as complete lack of memory safety, unchecked out-of-bounds accesses or lifetime management. And Go's inability to assert any immutability is also a strike against correctness (at least C++ has consts, though they're complex beasts)
One thing to consider about C++, though, is that it's powerful enough to already allow you to duplicate or very closely approximate the functionality you listed. This is the essence of what some people call "Modern C++".
C++ functionality like references and smart pointers make pointer nullability and memory safety irrelevant, or at least much, much less of an issue than in C or older C++ code.
RAII significantly helps with lifetime management of all sorts of resources, from memory to database connections to files.
There are various collection libraries for C++ that make out-of-bounds accesses a non-issue, at a very minimal cost.
Likewise, it's quite easy in almost all cases to avoid using unsafe casts and other behavior that may cause problems, when taking a more modern approach to C++.
With a small amount of discipline, C++ of today can be much different than C++ of the 1980s or 1990s. Many of the pitfalls of that era can be avoided with remarkably little effort, especially when using C++11 and modern implementations. Yet it still easily gives you access to very powerful functionality for those rare cases when it is needed. This is much harder to do in many other, more modern languages.
> C++ functionality like references and smart pointers make pointer nullability and memory safety irrelevant, or at least much, much less of an issue than in C or older C++ code.
This is dangerously incorrect. Having spent much of my career finding new and exciting ways to break modern idiomatic C++'s memory safety, I can confidently say that no reasonable subset of C++ is memory safe.
There's no question that you _can_ write safe code in C++, but it's very, very hard.
> With a small amount of discipline,
I would just like to point out that when dynamic language advocates suggest that 'with a small amount of discipline, you can write reasonably safe dynamic code' and you laugh them off with comments about how static typing saves you from errors at compile time.
The same is true with languages like Rust or Haskell; you don't need the discipline, the compiler catches such things for you.
The discipline involved with writing safe C++ code is somewhat different from the discipline involved with writing safe code in some dynamic language.
In C++, it's mostly just a matter of understanding the language features you're using, and then avoiding ones that can be harmful in some cases, often in favor of much safer alternative approaches.
The C++ compiler will still catch many other types of problems at compile time that wouldn't necessarily be noticed (assuming they even are noticed by the developer(s)) until runtime when using a dynamic language. The safety net offered by the C++ compiler may not be cast as wide as that of Rust or Haskell, but it's still there, and it does catch many common issues.
And I think it's wrong to say that Rust and Haskell don't require discipline; they do. It's still easy to write inefficient or unmaintainable code in those languages, for example. You still need to know what you're doing, and how to use the functionality that they offer, in order to avoid problems like that.
Now, the discipline needed when using a dynamic language not only includes the discipline needed when using C++ (that is, "don't do stupid stuff"), but it also includes not making trivial mistakes like typos, or missing some changes when refactoring, and so on. Much, much more discipline is needed when writing code in dynamic languages.
That is the kind of discipline that can be automated away to a very large extent, however. Personally, I do think it's silly and wasteful to willingly take on responsibility for ensuring that sort of discipline for anything but the smallest scripts. So, yes, I will speak out against it and those who suggest it's acceptable, especially in larger systems.
> In C++, it's mostly just a matter of understanding the language features you're using, and then avoiding ones that can be harmful in some cases, often in favor of much safer alternative approaches.
The number of security bugs that every large C++ codebase sees suggests otherwise. C++ is neither safe in practice nor in theory. (I can come up with many ways in which modern idiomatic C++ can be broken, often in an exploitable manner.)
> And I think it's wrong to say that Rust and Haskell don't require discipline; they do. It's still easy to write inefficient or unmaintainable code in those languages, for example.
Yes, but the subject was correctness and the discussion was about the fact that these languages makes it easier to write correct code than e.g. Go or C++.
Region-based memory management in Rust makes it possible to deal with shared memory without having to deal with life-cycle maintenance that you deal with in C. C++ has some similar concepts but like most things in C++ they are horribly complicated and rely on large template libraries.
Go is an interesting and promising language and I look at it as the spiritual successor of the C programming language. That said, I don't find it all that easy to pick up. At all. Despite having some experience writing in ANSI C (C89) I find getting started with Go pretty painful, not for the syntax (it's simpler and more readable/declarative) but for everything else, starting from the library. It's not straightforward or intuitive how to perform even the basic tasks (as basic as reading/writing from files/sockets) and even the online documentation isn't that good: it's just a bunch of prototypes. There are many new concepts/constructs that you may first think as a useful addition but that are actually a pretty fundamental part of the language (e.g. slices).
I'm sure that with time and the right mindset one could actually enjoy writing software in Go.
Weird. I had the exact opposite experience. Extremely easy to pick up, especially if you are familiar with both C and Python, since Python lists are fairly similar to slices.
There are many examples in the Go docs, e.g. http://golang.org/pkg/net/http/#pkg-examples (runnable and editable too!) But I agree that there aren't enough, and that's probably the worst thing about Go documentation, especially when dealing with the lower-level libraries.
Just to be clear, I don't think verbosity means that documentation is better. Documentation is better when it gives you the information you need, and nothing more. That being said, I mostly think Go's documentation is better than Java's, and on par if not slightly better than C#'s. Here's a better example of what I mean, e.g. http://docs.oracle.com/javase/6/docs/api/allclasses-noframe.... vs. http://golang.org/pkg/
Granted, if you have no experience at all with the language--e.g. you haven't even taken the tour at http://tour.golang.org/--the documentation may be hard to read (what does "[]" mean, for example), but that's not the fault of API documentation, which rightly is clear and concise. Rather, that's the job for a comprehensive tutorial, which Go has: http://tour.golang.org/
I see you now link to completely another example. But the example of the listener you linked first, that is, at the time I've made a comment still just contains
Like I said, I changed the link two minutes after I posted the comment (way before you posted yours, but whatever) because I messed up the link. You can find the listener example here: http://golang.org/pkg/net/#pkg-examples
I can side with OP, I find the documentation very disappointing. After several days, I eventually gave up trying to append to a log file in Go. I just couldn't figure it out. I also read a book on Go but it only covered reading files.
It covers everything you need to know. The MOST important part being "example". I don't see any on Go's documentation. That just led me to being frustrated. Also, I actually only see a list of functions and a very short, vague description.
I don't really know Go except for like 5 min of reading up on its syntax, so I could be wrong, but this assertion that it was not obvious how to write to a file in the documentation surprised me. So I clicked on the stackoverflow link.
Method description says "Sync commits the current contents of the file to stable storage. Typically, this means flushing the file system's in-memory copy of recently written data to disk." Isn't that what you want?
Was it as clear as the C# example? Probably not. But I don't think this was terribly hard to find. Code examples will come in time, as the language matures.
IMO whether to put code examples into the documentation is really a choice by the language maintainers. Lots of code examples can also clutter the docs. I prefer to just have the docs tell me what each function does. If I really get confused, I can google for examples. Java also doesn't really have code examples right in the documentation, but they aren't hard to find because it's been around for a long time. For example, Googling for "java code example how to append to a file" and click on the first link.
Okay, it might not be totally obvious, but File implements io.Reader and io.Writer (you can tell by the Read and Write methods.) Therefore, you can use all the functions that take an io.Reader or io.Writer, i.e. all the functions in http://golang.org/pkg/io/ and http://golang.org/pkg/io/ioutil/. It also implements io.Closer (Close() method), which you can use to flush and close the file when you're done.
It would probably be helpful if there was some kind of auto-generated list of the known (or at least stdlib) interfaces a given type implements, even if you don't actively declare it (like Java 'implements'.)
I was going through the learn go lessons from the main website, and when I got to the portion about creating a custom image interface, found it very difficult to piece together a working example.. I finally got that frame done, but when I had finished it took so long that I had to do something else and have yet to get back to it.
It was pretty interesting, and I find a few bits were harder to deal with than others (such as {} being required for conditionals, even when on the same line) in terms of my own habits. The language wasn't particularly difficult, but when I got to a few of the higher lessons/challenges, getting the answer took more time than I thought it should, or would with any other language for similar problems.
Searching for go examples is worse than when C# first started out.. searching "golang" yields slightly better results, but still not the easiest thing to search for examples with/on.
If you come from a Scala background, you will find Go pretty 'Meh' feature-wise (No offense). It's a great language, but still it has a long way to go. With that said, I personally want Go to succeed as I love it's syntax (compared to Scala's slightly hair-pulling syntax).
Also, the lack of robust battle-tested web-frameworks is right now a show stopper for me.
One interesting thing to consider about the increase in Go's popularity is that it has been driven, to some extent, by programmers who really only had experience with PHP, Ruby, or JavaScript.
These are the kind of developers who have by now been burned by the inherent dangers and drawbacks of dynamic languages. They know enough to know that there must be a better way to develop safer, more reliable and better-performing software. They just aren't familiar enough with existing statically-typed compiled languages to know what's already possible.
Go may just be in the right place at the right time. It is simpler than languages like C, C++, Haskell, SML, and even Scala, for example. And it is getting a lot of hype just when these dynamic language refugees need something better, and are actively looking for it.
While it's good that Go offers a much better environment and infrastructure than many of these developers are accustomed to, this may also be harmful in some ways. It may be just "good enough", at least compared to the alternatives that they're familiar with, that they don't feel the need to explore more established languages which do offer much more.
Even if they don't move past Go any time soon, at least it's encouraging to see them using a more responsible language for larger-scale software projects.
This defiantly jives with some of my experiences. I've been saying that "Go makes me feel like an old man," because other than a _fantastic_ development experience, I don't actually see anything about Go that's particularly interesting and/or worthwhile.
I also remind fellow Rubyists who say "Go is boring, it's supposed to be that way!" that when they talk shit on Java, that was one of its rallying cries as well.
It's all just a bunch of cycles, what's trendy comes and goes, is boring and then interesting again...
If you think lack of features is a knock against Go, you aren't really in the right mindset (Go might just not be for you). The lack of remarkable features is what is remarkable. The same with heavily built web-frameworks versus like Gorilla (a series of take it or leave tools).
Scala has every feature and language construct under the sun and full access to the astounding landscape of Java -- as well as amazing tools like Akka. This is all wonderful, and I enjoy using Scala because it is rich and you can treat is very functionally.
That said, I now prefer Go to it after shipping two production products on it. A deploy that is second to none, amazingly fast development cycles, easy to build rich custom tools on top of it (for example go-diff, which ignores syntax differences and only shows functional differences), the easiest cross compilation I have ever used, and a focus on this exceptional clear and easy to read code (lack of implicitness / magic). It reminds me a lot of what I like about Erlang -- with some of the more annoying bits stripped away.
But, at the end of the day I am using Go for three reasons:
1) Lets me get work done quicker than other languages (and win contracts / beat competition)
2) I can keep the entire spec (50 pages) in my head, with very little time spent. I have used Scala far longer, but would never make such a claim about it.
3) The tool chain (build, cross-compile, test, format, doc) that comes with it is amazing, the least obnoxious I have ever used. It has warts (get), but all in all -- it makes using anything else exceptionally painful. Once you get used to "go test ./... && go install ./..." to build your app and put it in ./bin -- and you get used to doing deploys with "scp bin/foo server..." everything else starts to feel stupid.
The lack of features actually is a feature. It is a different approach to language design. The Go devs wanted to keep the number of language constructs small.
Except that Scala lacks Go's headline feature: green threads. Being able to write event-based code without the use of callbacks is a major paradigm shift away from almost everything that came before it.
Yes, Scala has loop/react ... but they are really just a different way of writing callbacks, and don't allow you write truly synchronous-style code over an event based system.
This, and your replies below, read like trolling written by someone who hasn't used the features he is claiming are unnecessary. They repeatedly fail to address the point: you actually can't begin in Scala with no notion of lightweight threads, coroutines, or continuations, and simply make a library that does one of those things.
"The style one can program in and the style it is implemented isn't connected the way you think it is."
The above might be alluding to techniques for pretending to have these language features when one does not. You can write CPS manually, and you might even be able to make a machine apply the CPS transformation automatically to your procedural code. Neither of these things sound particularly fun, compared to the alternative of writing procedural code and then debugging the code that you've actually written. It's little help that you have to return every once in a while to avoid a StackOverflow due to the language's embarrassing lack of TCO.
Edit: Apparently if you use Squawk you can have green threads. Can you have OS threads as well?
In consideration to anyone who actually tries to read all this, I was wrong. Scala really does have continuations. You won't learn about this by reading Martin Odersky's book on the language (even though the book is for Scala 2.8, the version in which delimited continuations were introduced), but googling around for delimited continuations, shift, and reset will result in a fair mix of useful notes and completely inscrutable notes.
This whole sort of back and forth exchange of escalating condescension is not rare on the internet, but I was surprised that dino wouldn't actually tell anyone why we were wrong. Generally I've found this quote http://bash.org/?152037 to be more or less true; asserting that a system is incapable of things it is capable of is met with people pointing out how to make it do those things. In this particular instance the response from dino involved much more effort than a simple lmgtfy link while accomplishing dramatically less in the promotion of his faith, or even the promotion of any sort of knowledge at all.
This feature is a little obscure, and one could easily spend a great deal of time writing Scala professionally without ever passing "-P:continuations:enable" to anything. I believe that one would be missing out.
This comment is hilarious. I guess I can now spend the rest of my life searching for the super-performant yet somehow extremely obscure JVM implementation with OS threads, green threads, and TCO.
Edit: I am still seeking the legendary JVM implementation with TCO. I guess the argument is something like, oh well at some point in the future some JVM could conceivably have that feature, therefor it is wrong to say that Scala does not have it! Rather than writing code that assumes Scala is a complete joke of a functional language, one could simply write the code the natural way, verify that it compiles, and then wait until a JVM with TCO exists, so that it will stop crashing! In the mean time, one can enjoy the entertainment provided by Scala's inability to compile shift in an if statement without an else clause. Truly I am writing code exactly as I would if(it_was_synchronous) else { cpsunit }.
I feel as though you told me reset and shift, although you did not. So thanks for that!
I am still very interested in learning about the runtime you are using :)
After a significant amount of effort (googling for stuff about scala, JVMs, and TCO mostly gets hits with people complaining about their code not working) I discovered something called Avian, which I can try out soon. I still think it is a bit weird to spend time engaging with a stranger who wants to know something very simple, but to expect the stranger to dig around for it for a few hours instead of telling him.
As a general policy, if I spend time explaining things I require people to do some homework on their own.
This way people who are lazy, whose interest is just strong enough to make ridiculous claims and complaints but don't actually care about the topic at hand, don't get things for free.
> The style one can program in and the style it is implemented isn't connected the way you think it is.
The implementation determines what happens when synchronous, blocking code, well blocks.
In Go it yields to another green thread (Goroutine).
In any language implemented on the JVM (including Scala) you block an OS level thread.
The way languages without green thread support allow non-blocking, event based code is through callbacks, which can get pretty horrendous in large, complex programs.
> Unlike Go, where people seemingly put random stuff into the language, it's just a bog-standard library.
I'm not sure why that is relevant (Actors seem a pretty core part of Scala), but I guess it is unsurprising that a mere library on top of the JVM is still constrained by the kind of concurrency supported by that platform, i.e. the lack of green threads.
> In any language implemented on the JVM (including Scala) you block an OS level thread.
Wrong. Whether JVM implementations use green threads or system threads is an implementation detail. There are implementations for both cases.
You realize that many JVM implementations started with green threads in the early days and abandoned them not much later? There is a reason for that.
> The way languages without green thread support allow non-blocking, event based code is through callbacks, which can get pretty horrendous in large, complex programs.
Wrong. I repeat, the underlying threading model does not necessarily enforce some style of API.
> I'm not sure why that is relevant (Actors seem a pretty core part of Scala)
Actors are a library, just like Scala's async/await, the Future/Promises library, the continuation library, Java's Fork/Join library.
Actually, Scala just replaced its “official” Actor library in the last release. Go will be stuck forever with its ad-hoc stuff which has been hard-coded into the language.
> it is unsurprising that a mere library on top of the JVM is still constrained by the kind of concurrency supported by that platform, i.e. the lack of green threads
LOOOOOOOOOOOL. Since when are green threads the “new best thing ever!!!11!!”? Did I miss a memo here?
You know what? Wake me up when Go has fixed their broken scheduler. Until then, I just keep watching how the JVM embarrasses Go on pretty much every concurrency and scalability workload.
> Wrong. Whether JVM implementations use green threads or system threads is an implementation detail. There are implementations for both cases.
Obviously it is an implementation detail, and for any JVMs that support green threads (none of the major ones do), then it would be a different matter entirely.
> You realize that many JVM implementations started with green threads in the early days and abandoned them not much later? There is a reason for that.
Java was created in the mid-90s before every man and his dog was trying to solve the c10k problem on a $5/month VPS. Hardly surprising.
> Wrong. I repeat, the underlying threading model does not necessarily enforce some style of API.
But it does allow for it, or make it easy. Why do you think Scala has both blocking, and "event based" (loop/react) API's?
> Since when are green threads the “new best thing ever!!!11!!”? Did I miss a memo here?
Quite possibly, you must have been living under a rock if you haven't heard of callback hell in Node.js. Funny because Node was the coolest thing ever a couple of years back, how times change.
> You know what? Wake me up when Go has fixed their broken scheduler. Until then, I just keep watching how the JVM embarrasses Go on pretty much every concurrency and scalability workload.
Java has great MT concurrency. It's light-weight concurrency (NIO) is about as good or bad as any other event-based systems out there. What is lacks (my original comment) is the ability to write event-based code using a synchronous, blocking style (without callbacks).
The only contenders there are Go, Haskell, and possibly some of the scripting language extensions (Python/Gevent, Perl/Coro).
And if by "broken scheduler" you mean it's lack of preemption...then I would say rethink your design, you should be handing long-running jobs to a work queue anyhow.
> [...] then it would be a different matter entirely
Have fun moving the goal posts around.
> Java was created in the mid-90s before every man and his dog was trying to solve the c10k problem on a $5/month VPS. Hardly surprising.
So what? C10k isn't a hard thing to do on the JVM and it probably beats Go by a large margin in terms of latency and performance.
> Why do you think Scala has both blocking, and "event based" (loop/react) API's?
Your point is ...?
There are plenty of different libraries, because concurrency can be tackled in different ways. Use the right tool for the job. Unlike in Go, where you pretty much have to shoe-horn everything into “goroutines” or channels.
The lack of developers which run around and try to tell everyone that they found the silver bullet for solving concurrency in Scala is a sign of a mature community which has experience and expertise and doesn't just repeat whatever Rob Pike says like Go users seem to do all day long.
> Quite possibly, you must have been living under a rock if you haven't heard of callback hell in Node.js.
Eh ... so what? It's not like Node's terrible approach is the only alternative to Go's approach.
Claiming “A is the best, because B is even worse” just shows a complete lack of knowledge of existing solutions.
> What is lacks (my original comment) is the ability to write event-based code using a synchronous, blocking style (without callbacks).
Again: No it doesn't. Do your research instead of claiming blatantly false things.
> So what? C10k isn't a hard thing to do on the JVM
It is using the traditional threading model, which is all it had before NIO came along.
> Your point is ...?
My point is if Scala's synchronous API was compatible/built on an event based system or green threads, then the far less attractive callback based API would not exist. Just like Go doesn't have callbacks...what would be the point?
> Use the right tool for the job.
Go doesn't claim to be good at everything. If you want great traditional multi-threading then by all means use Java. If you want a highly-scalable network engine with code that doesn't look like spaghetti, use Go.
> The lack of developers which run around and try to tell everyone that they found the silver bullet for solving concurrency in Scala is a sign of a mature community
I don't don't hear many Java programmers running around complaining about hand-editing reams of XML either...I still don't envy them.
> Claiming “A is the best, because B is even worse” just shows a complete lack of knowledge of existing solutions.
I don't think Go is "best", and I already gave you examples of other synchronous, event-based languages (you still haven't shown me an example in Java/Scala). I actually prefer Python/Gevent for most things.
> Do your research instead of claiming blatantly false things.
Well I think the onus is on you to provide some evidence to back up your claims.
https://github.com/jimmc/scoroutine - some of the examples in this are godawful, but you'll find that they still work if you rewrite them to be legible.
// Some example methods which might spend waiting for other
// services (calling the database, sending emails).
def getEMailForUsername(username: String) =
future { DB.Users.find(_.name == username).get.email }
def sendPassordRecoveryMail(email: EMail, message: String) =
future { SMTPService.send(email, message) }
// Future/Promises API:
def resetPassword(username: String) =
for {
email <- getEMailForUsername(username)
result <- sendPassordRecoveryMail(email, "some message")
} yield result
// Async API:
def resetPassword(username: String) =
async {
val email = getEMailForUsername(username)
val result = sendPassordRecoveryMail(email, "some message")
await(result)
}
@smegel:
I don't see any “callback hell” or “code that does look like spaghetti” here.
By the way, I'd love to see a link to “Scala's synchronous API” and that mystical “far less attractive callback based API”. Or are you making things up again?
For me, no exceptions is a show-stopper. I know people sometimes say it's actually advantage, and I believe in some situation, it can be.
In a typical scenario, I might be working on an utility that does some IO. In case of error, I just want to do some cleanup then exit. With exceptions, the control flow will jump right up to the places were cleanup has to be performed. With return values, I need to check return values at each step of the call stack, which is tedious and ugly. Return values have to be modified to account for error code. Admittedly, Go handles this last point well with multiple return values.
Go's version of 'finally' is called 'defer', and it can be used in any situation, not just error management (I often use it for releasing locks in the statement immediately after I've locked them).
>I need to check return values at each step of the call stack, which is tedious and ugly.
I see it more as explicit. You HAVE to decide what you're going to do in case of an error. Usually it's as simple returning that error, but sometimes you want to do more, and having the question constantly being asked means you think about it for all cases.
> I see it more as explicit. You HAVE to decide what you're going to do in case of an error.
I've often read this argument, but i don't see how that is an advantage over using exceptions.
With exceptions you don't have to decide what to do in every function call that might raise an error. You just have to know that they can raise one, and code accordingly. For example:
f = File.open(...)
foo(f)
bar(f)
f.close()
Is buggy if foo or bar could raise exceptions (the file would not be closed if an exception is raised there). Fixing it could imply adding a `finally` block around f.close() so the file always gets closed (same as Go's defer) or using a higher order function that already does that for us:
File.open(...) do |f|
foo(f)
bar(f)
end
The thing is, this code didn't need to worry about which of foo or bar was raising an error; it just needed to worry about closing the file if anything went wrong. And i could add a new baz(f) call in there, or maybe refactor the foo(f) and bar(f) calls into a foobar(f) function that does just that and the code will still be as linear. There'd be no need to add extra `if`s there nor in the refactored foobar(f) function.
> Usually it's as simple returning that error...
I'd say that it's rather "most of the time" than "usually". And exceptions give you exactly that default behaviour of propagating the error up the stack. Without having to obfuscate the logic of some intermediate code (that doesn't care about what error could happen, as long as they are propagated to the caller) with superfluous `if`s and `return`s.
> I've often read this argument, but i don't see how that is an advantage over using exceptions.
Go's way is better than Java style exceptions because Go has exceptions very much like Java's ("panics"), but Go isn't as likely to force you to handle them to address circumstances that aren't exceptional in the context of your use case, because the builtins and standard library don't tend to use them as much, preferring reporting conditions through multiple return values, which allows the decision to panic or not to be made by code written at a level that has some awareness of the use being made of the function so that it knows whether a condition is one which warrants a panic.
Didn't know about defer, cool. You can use Java's finally in a similar manner (although the syntax is worse, and you can't scatter the logic like with defer).
> I see it more as explicit. You HAVE to decide what you're going to do in case of an error. Usually it's as simple returning that error, but sometimes you want to do more, and having the question constantly being asked means you think about it for all cases.
Checked exceptions also do that (you have to add "throws" statements) and it looks much cleaner. I must however say that I'm not a fan of checked exceptions either.
> Checked exceptions also do that (you have to add "throws" statements) and it looks much cleaner. I must however say that I'm not a fan of checked exceptions either.
Indeed. I was going to mention checked exceptions too. I think they are a great alternative, and personally I do not wish to use a programming language with no exceptions.
I must ask, why are you not a fan of checked exceptions?
For the same reason I don't like errors in return values: most of the times you cannot recover from failure, only do some cleanup. Since you should always consider than anything you call may fail and put your cleanup in a finally/defer, you don't really need checked exceptions.
Even in say, a GUI application (which you don't want to crash on failure), you'll usually contain all failures at some level and for instance display a pop-up indicating that something failed.
I suppose checked exceptions are good for recoverable failures, but I encounter preciously few of those.
Go has exceptions, but it differs from, e.g., Java in that:
1. It handles raising and recovering from them, and specifying actions that must happen whether or not an exception occurs, through two built-in functions (panic/recover) and a simple statement (defer) rather than a multi-armed structured syntax (try/catch/finally).
2. The built-in functions and standard library use panics (Go's exceptions) with more restraint than is idiomatic in Java.
> In a typical scenario, I might be working on an utility that does some IO. In case of error, I just want to do some cleanup then exit. With exceptions, the control flow will jump right up to the places were cleanup has to be performed.
And this works fine with Go's panics. You'll be more likely to need to decide that something is panic-worthy and explicitly panic in your lowest level call in Go than in Java (just as, conversely, you'll be more likely to need to explicitly catch and handle an exception thrown in Java for something that's not really exceptional in your use case.)
But aside from that, there's not a lot of difference.
> With return values, I need to check return values at each step of the call stack, which is tedious and ugly.
Or check them at the lowest level of user code and, if there is a problem that is truly exception/panic worthy at that level, call panic().
If you are checking errors at each level of the call stack just because the lowest level function uses (as is typical in Go) a secondary return values to report conditions outside the scope of the main return value, you aren't dividing functionality up properly between functions. Generally, I would think, the lowest level of user code that is specific enough to make an intelligent decision should either be collapsing out the unusual-condition secondary return value or initiating a panic, depending on how the condition involved relates to the purpose of the code.
When programming for fun in my spare time, I've noticed that I use Go differently from certain other languages (Haskell, Rust).
When writing Haskell, I spend a lot of time thinking about how I can express the problem using the different language constructs, and how elegant the code is. With Go, because the language is so simple, I just focus on solving the problem; I almost feel that I solve the problem in spite of the language.
Go is interesting and I use it from time to time, but I'd hesitate to call it excellent without runtime constants and generics. Also, the "and not used" errors can get annoying.
* no coding style flamewars
* it has familiar syntax
* builtin concurrency model
* it has gc
* it is fast
* static types that don't get in the way
If you compare this list to popular up-and-coming ecosystems from the 90s (java, python, C, C++, Ruby), okay, concurrency is a big win and everything else, maybe go is a little bit better.
If you compare to other popular up-and-coming ecosystems in 2013 that have a similar sized following as Go (Scala, Clojure, Haskell, Node, Rust)...
Well, at least Go beats Node. and Haskell appeared in 1990.
One thing I'd really like to see for go is a UI toolkit which is native to it. Not Qt, Gtk, Wx ports, but a proper high level UI framework like WPF (but not!). I reckon that would take it into the financial and business sectors in no time at all.
I'm a Java guy and I've been learning Go on the side just for fun and so far I like it. I've found the documentation fairly easy to get through (the fact that there are examples right in the documentation makes me like it better than Java).
I don't think the language is perfect, but I think it's well on it's way to getting there.