Go at Google: Language Design in the Service of Software Engineering
talks.golang.org
talks.golang.org
some_expression match { case Some(x) => do_something_with(x) case None => no result (e.g. null in C/Java/etc) case Error(x) => handle error x }
One of the nice things about the monad approach is they can be combined together. Consider the following chain of function calls which may return null at any point:
int val = foo().bar().baz().blah();
To deal with this with if/else statements, you need 3 nested if-statement for a single line of expression. In a language like Scala, you can just write:
for (a <- foo(); b <- a.bar(); c <- b.baz()) c.blah();
I think there is an inherent tension between explicitly detailing everything and boilerplate in terms of readability and writeability. I haven't seen enough Go code or written much of anything to say anything about it, except that it worries me. I do think the multi-return stuff can limit the amount of accident ignoring of error conditions, but the Go language itself doesn't provide any high level syntactic constructs to make dealing with the errors less painful, whereas with the Monad approach (which essentially is multi-return), the pattern matching tends to look cleaner IMHO.
It looks the same to me.
case maybeValue of
Just value -> ...
Nothing -> ...
isn't any different from: if (val != null) {
....
} else {
...
}
http://news.ycombinator.com/item?id=4721748> Consider the following chain of function calls which may return null at any point:
int val = foo().bar().baz().blah();
Like
val = foo.bar.blah.boo rescue nilOr
try {
val = foo.bar().blah().boo()
} catch(NullPointerException ex) {
}
Or try:
val = foo.bar().blah().boo()
except AttributeError:
pass
http://news.ycombinator.com/item?id=4726266Personally, I do see Maybe/Option as cleaner, but it isn't novel as people make it out to be.
The key point is that because Maybe is a monad, you can chain things together and handle failure at the end much like a try/catch:
let result = do
x <- f(t)
y <- g(x)
z <- h(y)
z
in
case result of ...
You are correct that this isn't particularly novel.Also, the semantics of catching a NullPointerException in a chain of method calls are different from chaining Maybe results together. If any of the method executions inside raise an unhandled NPE, you'll catch it outside, which is probably not what you wanted to do.
> The compiler ensures that you don't forget to check the null case when using the Maybe monad, while you can forget to handle the null value and get a NullPointerException later.
Though I use mostly dynamic languages with "everything an object"(or close approximation) where everything is effectively a Maybe, I do see the value in enforcing checks before using Maybe. In practice, I try to minimize the nullable types, as it is (too easy to get lazy | not know about implementation) and forget the check. Sometimes I use exceptions for flow control rather than returning null.
> If any of the method executions inside raise an unhandled NPE, you'll catch it outside, which is probably not what you wanted to do.
Yes. That will be bad. In an ideal world, given foo().bar().baz(), there shouldn't be a NullPointerException inside foo/bar/baz given how I am using them. If there can be one, I shouldn't be chaining them that way. But it's hardly an ideal world.
Exceptions to handle null deferencing have other issues. NullPointerExceptions are not checked exceptions in most languages, and therefore, you do not see people surrounding chained method calls with potentially nullable intermediate results with try/catch blocks, it's exceedingly rare.
Some languages have nullable types which can be checked by the compiler, and indeed, even Java has adopted @Nullable/@NotNull, but the late adoption of this in Java, and non-existence of it in Javascript/Perl/etc all mean that for the most part, chained method calls, which a lot of people have adopted for fluent APIs/'DSL's tend to go unchecked, and any exceptions simply bubble to the top of the program.
In this regard, Rob Pike is right, and a null in the middle of a a().b().c() call likely represents a null that the programmer should have handled, not as an exceptional condition, but as a recoverable one (e.g. findCustomer() didn't find the customer).
In many cases, returning null I think is the wrong design anyway (I see lots of Java code where a search() returns null instead of EMPTY_LIST if it finds nothing), but null or false as a catch-all error code just seems entrenched.
That's why I like the Maybe Monad approach, because Maybe(boolean), Maybe(number), Maybe(Customer) are different types, compared to using integers and booleans as arbitrary error codes.
I won't want checked NullPointerException. That will be so common that people will end up having a "throws NullPointerException" at the top defeating the whole purpose of having it. For many cases, exceptions enforce a cleaner flow.
Connection con = DriverManager.getConnection(...)
If I am trying to obtain a connection, the interface is "returns a connection" or "throws an exception".> In many cases, returning null I think is the wrong design anyway (I see lots of Java code where a search() returns null instead of EMPTY_LIST if it finds nothing),
In Ruby, seq.select {|x| x.some_pred? }.map {|x| x.some_attr }.sort {|a, b| a.some_attr <=> b.some_attr } works with [] because of high level Enumerable interface implemented by [].
A Java Person.findAll() returning an empty array won't be very useful as you won't be able to chain.
"Go is a programming language designed by Google to help solve Google's problems, and Google has big problems. The hardware is big and the software is big. There are many millions of lines of software, with servers mostly in C++ and lots of Java and Python....And of course, all this software runs on zillions of machines, which are treated as a modest number of independent, networked compute clusters"
The author moved effortlessly from "Google's problems" to "servers" that are "networked compute clusters." That's quite a leap, because it's certainly not true that "all this software" runs in that manner. Android does not, Chrome does not, Chrome OS does not, the iOS YouTube app does not, Google Earth does not, etc. And there's plenty of not-big hardware that Google writes for, like laptops and phones.
And the reason I bring this up is because Go was explicitly pitched as a systems programming language - see the PDF http://www.stanford.edu/class/ee380/Abstracts/100428-pike-st.... Programming tools, IDEs, web browsers, operating systems ("maybe") are among the tasks specifically called out as suitable for Go. But now this article tells us that Go was designed for "large server software".
So the question is, what changed? Was Go found to be unsuitable for tasks like web browsers? If so, in what way?
That said, I would guess that most of Google's code is still C++, Python, and Java.
So Go was designed for large software systems, and maybe it's really good at those things. Or maybe not, because nobody's written one yet. The article says so: "We don't have enough experience yet, especially with big programs." So Go is unproven in this way.
Speculatively, my guess is that it won't be able to "at scale" its current state. For example, Go errors out with unused variables, imports, or indeed for all warnings. Imagine trying to make a piece of software like WebKit or LLVM compile on all of the platforms it supports, with all of the compilers they support, without introducing a single warning (or extraneous header or unused variable) on any of them. It would be a maintenance nightmare. You need some flexibility - demanding perfect hygiene for every cell on an NxM matrix of supported platforms is infeasible.
In the other sense of scale - large data sets - Go apparently works well enough to be involved in YouTube. That's cool and is a credit to it.
But Go doesn't most of the design flaws you mentioned. For example, there are no multiple Go compilers wildly diverging in what they understand. That's unique to C++. In Go, like in Java or C#, a compiler either implements the whole spec or it cannot be called a compiler.
Go also doesn't have the portability problems that have accumulated in C/C++ over decades.
Go doesn't have macros (which enable the mess that C/C++ code finds itself in).
Go doesn't have manual memory management, pointer arithmetic, array access is always bounds checked etc. - it's radically safer language than C/C++.
I also fail to see the logic connection between unused variables or unused imports and any of that.
Or how fixing the error caused by unused variable is a "maintenance nightmare". Count the number if #ifdef statements in boost and then let's have a discussion about what is and isn't a maintenance nightmare.
Go doesn't have warnings. The Go compiler errors out on compile errors. Unused variables and imports are two things that people typically assume should be warnings, and are surprised to find them as errors.
So I don't think your fears are well-founded here. Go is a much stricter and clearly defined language than C. There are far fewer edge cases and ambiguities. The spec is much easier to implement, and indeed there are two complete compiler implementations that agree with each other and the spec.
That WebKit or any other large C project could produce no warnings is, of course, hard to imagine. C compilers emit warnings like... well, I can't think of a clever simile. But the point is, it's totally normal for C programs to emit tons of warnings. But ANY Go program (valid or not!) can never produce any warnings. Ever.
- web servers
- web browsers
- web crawlers
- search indexers
- databases
- compilers
- programming tools (debuggers, analyzers, ...)
- IDEs
- operating systems (maybe)
Of that list, "web servers" and "search indexers" are both the kinds of "large server software" mentioned by Rob in this paper. (Most Google programs are effectively web servers of some kind, even if they don't speak HTTP directly.)Sorry if our "positioning" of Go is a little confusing at times. We're programmers, not marketing people. We didn't set out to "market" Go at all, and had we set out with a clear message from the beginning we might have done a better job at it.
This paper best describes the initial motivation for Go's existence. However, like any general purpose language, its uses extend well beyond its design goals.
Go was originally presented as a language for servers, system software, and application programming. The last two were what got me excited, because server-side languages are already well represented, but there's not many languages you could consider writing a web browser or an IDE in.
However, from my perspective, Google refocused Go exclusively on server software. For example, at Google I/O 2011, the talk was "Writing Web Apps in Go." and in 2012, Go was organized under the "Cloud platform," and Rob Pike's talk was about "high performance network services." Support for Go on App Engine appears to be a higher priority than fixing its known problems on 32 bit. Rob has also discussed how he was surprised that Go attracted more interest from Python and Ruby programmers, who mainly work on server software, than from C++ users (and had some not-very-kind words to say about C++ users). These are some of the reasons I'm discouraged about Go's future as a systems and application language.
In the end I guess you give users what they want, and if Go is mainly interesting to people writing servers, so be it. Keep on rockin' (but I reserve the right to be disappointed).
The Go team are not a homogenous group of programmers who can do anything. Each of us has our own particular areas of expertise. That's one of the reasons we work so well, but it also means that prioritizing tasks is complicated. Without going into it too much, the idea that we prioritized App Engine over "the 32-bit issue" is a nonsense.
Incidentally, that issue, to the extent that it was experienced by some users, will be resolved entirely in Go 1.1. This is timely as a lot of interest has developed in the ARM port recently.
Rob Pike's talk at I/O 2012 wasn't about "high performance network services," it was about concurrency. Network servers are a familiar context in which to understand concurrency problems.
There's no "refocusing" going on. It's just that we're putting our best foot forward, and right now that's in server programs and tools. Writing applications (and I'm assuming you mean non-web applications) requires good UI toolkit support. That's not something we're working on, but it is something that others in the community are doing. We all believe Go will make a wonderful language for writing native apps, it's just not there yet. (And I suspect Rob does in particular, having built Inferno with Limbo, which used Go-like concurrency to great effect in its UI APIs.)
It will come.
The loss of these optimizations are compensated by nesting structures instead of just always using pointers to imitiate pass by reference semantics found in other GC'd languages, such as Java or Python. Using them correctly can reduce the number of objects to be collected and analyzed.
"In 1984, a compilation of ps.c, the source to the Unix ps command, was observed to #include <sys/stat.h> 37 times by the time all the preprocessing had been done. Even though the contents are discarded 36 times while doing so, most C implementations would open the file, read it, and scan it all 37 times. Without great cleverness, in fact, that behavior is required by the potentially complex macro semantics of the C preprocessor."
"Great cleverness" apparently means "the ability to detect that a file has a standard 'include once' pattern and only process it once". It hardly seems like rocket science.
#pragma once and Objective-C's #import declaration both do that, though.
"The construction of a single C++ binary at Google can open and read hundreds of individual header files tens of thousands of times."
Some of those could be non issues. There could be other issues I lack the experience to guess. It's no use saying "so don't do that" - not all code is under your control. Honestly if it were simple, it would be solved.
It is somewhat complex to do it correctly, see: http://gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
https://air.mozilla.org/the-future-of-the-firefox-build-syst...
And in 2012 Apple tries to address it: http://news.ycombinator.com/item?id=4832568
The big difference is Go has Google+Pike pushing it and might actually get widely adopted. Getting some improvement is not a bad thing.
Go's search for simplicity is seen as a solution looking for a problem in the language design communities.
But most programmers are not in language design communities. There is a big potential for a 'safer C' or a 'faster Python'. Yes, that's a simplification, but also how people will see Go.
Why throw away Python's capabilities to execute it faster? Just make use of PyPy.
There are several reasons I'm interested in D and more so Rust, but they're very different than Go.
See how this never ends?
And yes, I have. Hence my comment.
Er, you weren't the GP, sorry, but my point remains, though somewhat directed a cjensen.
I don't think it is just that. There is also a difference in culture and tradition. Roughly, Go fits more in the C and Python tradition, D and Rust fit more in the C++ and Haskell tradition. Simplicity versus formal correctness.
I think the Python/C pond is just larger than the C++/Haskell pond.
I'm not sure I completely agree with their reasoning: the example only really got complicated when dealing with function pointers, however that could be simplified with a macro (or a delegate in other languages).
I do agree that C can get complex to read, however I'm not sure switching the type/parameter option is the answer. But then again, I've probably become accustomed to this format. The real test will be using the language for a few weeks... I better start some small hobby projects in Go just to see if I end up liking it!
int *i, j;
is more than a good enough reason for me. Makes interviews interesting, though: "Why are you repeatedly writing your variables and function parameters incorrectly?" var i int*
var j int
My apologies if this seems like a simple question: I haven't played with the language enough to know :)[EDIT: Sorry, I didn't realize how to format code!]
var i *int
Unlike C, values are always initialized in Go.Go has great support for closures, so you tend to use function types more often (or perhaps just more naturally) than in C.
It is just a matter of getting used to it. I switch back and forth between Go and other languages, and it doesn't bother me. (The thing that does get me is forgetting to use semicolons in other languages.)
Re-parsing files multiple times is a solved problem using the standard header guard macro pattern combined with a slightly intelligent C Preprocessor. Yet quotes about the horror of header blow-up pervade this paper as if this is an exciting new problem that has been solved. It isn't; it was solved more than 20 years ago.
I just think the bashing was misguided in an attempt to better justify adding a subset of Ada to C
If you change a header you need to recompile all files that include it, even if the include was conditional. Now your build system starts to look inside files rather than just check timestamps. Similar tool bloat happens over and over again in large codebases.
I'm not convinced Go has the right solution. But it's disingenuous to claim that it's competing with the C preprocessor. It's leagues better.
You should enumerate all the information in the paper you consider to be misleading.
There are many more, but I name these three as they are among most popular.
The linker AFAIK also has bottlenecks, but with today's computers, and thinking about what a C compiler actually does, there's no reason that you shouldn't be download (say) Python, Perl, or Ruby and compile them in 3 or 4 seconds. All of those codebases are 1M lines or less.
On my unremarkable but not slouchy three-year-old workstation that possesses rotational disks and about 6GB of buffer cache. This cache has not been pre-warmed either, but I can't guarantee it is totally cold. There are with compiler optimizations on, too.
$ cd ~/codes/postgres
$ make distclean
$ make -sj10
All of PostgreSQL successfully made. Ready to install.
247.83user 14.22system 0:46.26elapsed 566%CPU
Depending on how you count it, this is 600,000 to 1M LOC.Let's try complete Linux compile without the drivers, via compiling user-mode linux. This is normally sufficient for debugging file systems or the memory management system (it's really a godsend for that), for example:
$ cd ~/codes/linux env ARCH=um time make -sj16
LINK linux
321.04user 23.83system 1:02.49elapsed 551%CPU
All in all, I have always found the complaints about performance and header files in C programs (note: not C++) either outmoded or unconvincing. There are plenty of legitimate problems -- including tricky issues with semantics -- involving the preprocessor, including something as simple as figuring out when it is safe to remove or reorder a header include! There's no need to imagine problems. This meme about how terrible compile times for large C programs is needs to be put out to pasture, or maybe I just need to be exposed to code bases about ten times the size and feel the need to not use incremental builds.It looks like you have 6 CPUs, and say you have 2e9 cycles per second on each of them. 46s * 6 * 2e9 / 1e6 LOC = 552,000 cycles to compile a single line of code on average. That seems 1-2 orders of magnitude off, no? When you had a 10Mhz computer, did you compile at 20 LOC per second? There's a scaling problem here.
(That's ignoring disk access, but if you did it multiple times I doubt it would go more than twice as fast the second time. And to read 1M LOC from disk cold I'd guess should take on the order of 500ms anyway.)
Is what you're saying is that those compilations are fast enough? You wouldn't want it to take 1 or 3 seconds and see no reason why it should?
Also, Postgres and Linux I'm sure have a LOT better physical structure than your typical industry project. Most industrial projects are bound by talent, I would say. If choosing Go gets rid of this problem, then I would say that's a definite advantage on the side of Go, to be considered when choosing a language for a new project.
And it's true that incremental builds are more important, but on projects I've worked on they take 10-60 seconds for a 1 line change in a .cc file and much worse when you're changing headers.
In most practical cases (incremental rebuild) it does take 1-3 seconds. There is just not a huge amount of practical benefit to me. If one has interest in things taking 1-3 seconds for a full rebuild provided one has compiled the code before, there's ccache:
I had to install ccache (reason: this doesn't even cramp my workflow enough to bother until doing this benchmarking) and just do this:
env PATH=/usr/lib/ccache:$PATH time make -sj10
All of PostgreSQL successfully made. Ready to install.
6.05user 1.88system 0:02.48elapsed 319%CPU
If I don't cheat by using ccache, then turning off the optimizer gives me about 50% of my time back: 120.00user 11.72system 0:21.75elapsed 605%CPU
Here's the result of touching one c file in the executor and doing an incremental build (including linking): 0.74user 0.18system 0:00.68elapsed 137%CPU
A randomly chosen header file gives me about two seconds, with --enable-depend on: 3.33user 0.51system 0:01.53elapsed 249%CPU
I like Go, and appreciate that it compiles very quickly in some absolute sense, and for many other reasons, and do not wish for a hideous preprocessor system, but to me claims against the time it takes to compile a reasonably large C program are dubious enough that is can only lead to overzealous suspicion by parties that have to make a quick evaluation on what to spend their time with.> they take 10-60 seconds for a 1 line change in a .cc file....
.cc is another kettle. Just as the disadvantages of .cc should not be lumped with .c, the opposite also has to be taken into careful consideration: some advantages of .cc are not available to .c, and some advantages of both are retained in .go.
Also, ./configure and Postgres's 'initdb'. Now that's slow, in spite of some efforts to speed up the latter.
Try compiling Chrome (or just WebKit) sometime. It can take well over an hour on a beefy workstation. Incremental compiles are, of course, a lot faster than this, but a long way from "interactive" by any reasonable definition.
But wait, there's more! That bizarre crash or link error you're getting on an incremental compile? Yeah, it's the result of some screwy preprocessor problem that no one's taken the time to debug. You could try and get to the bottom of it yourself, but that would take somewhere between an hour and a couple of days. So instead you suck it up, clean the build output, start up a compile, and go grab lunch. This is the day-to-day reality for WebKit developers, and I don't accept that we can't ultimately do a lot better.
Large server-side C++ programs at Google suffer the same problem, because there's an enormous amount of shared code to compile. It's not as much of a dependency and script/preprocessor rat's nest as WebKit, but it can still get really slow, and you still occasionally have to give up and clean/rebuild when things go inscrutably wrong.
Any assertion that C++ header hell is "good enough" and/or "not that bad" flies in the face of this reality.
Because if you have a global.h file that 100 c files include said global.h will be tokenized, parsed and compiled 100 times, even with ifndef guards optimizations working. Along with everything global.h itself may happen to include.
On the HN and reddit threads about the proposed C++ module system a couple weeks ago, everyone said "this needed to happen 20 years ago", and that was absolutely true.
Include guard optimisations do not solve this problem.
It's been enjoyable learning it, though I'm yet to write any code in anger with Go. I find some of the concepts around concurrency and OO design have really made me think about the way I write in other languages.
One thing I cannot quite make sense of though is the treatment of errors/exceptions. This article says: "Exceptions make it too easy to ignore them rather than handle them, passing the buck up the call stack.."
But the example given seems to me to do exactly that:
if err != nil { return err }
Worse still if you forget to do something with err, or don't handle a specific err !=nil situation, it seems easy for control to flow past the error handling and into code that expects no errors.
Are there small, easily understood open source projects written in Go, that would provide tangible examples of the benefit of this error handling approach?
The learning materials thus far have not really convinced me, maybe code in the wild would.
Go's error handling model is simpler than exception handling. There are fewer surprises. That's pretty much it.
Edit - Things like regexp.MustCompile aside.
The Go standard library:
grep -nrHIF 'panic(' /opt/go/src/pkg/* | wc -l
632
Note that this is somewhat inflated with a lot of `panic("unreachable")` calls. They are a symptom of the standard Go Compiler requiring a `return` on every code path of a function that returns at least one value. A `panic(...)` relaxes this requirement. In this case, a panic arising would indicate a bug in the package. Which is another good reason to panic across package boundaries :-)Do you know if they intend to fix the need for phoney code paths? It seems odd that a language with nice support for first class functions and closures can't acknowledge when "if ... else ..." has complete flow coverage.
It's a purposeful restriction imposed by the Go compiler, but not by the Go language specification.
And I think the idea is to encourage explicitness, and force cases like these:
if cond {
return ...
} else {
return ...
}
Into a simpler: if cond {
return ...
}
return ...
Reasonable people can disagree over whether this is a Good Thing.So essentially, use panic/recover as you would a much nicer version of longjmp.
(I believe) the only thing panic/recover lack compared to a more traditional try/catch is typed catching, but idiomatic usage is much different.
Honestly, I really like it. The typed-catching would be nice but the idiomatic usage of exceptions in languages like Java has always rubbed me the wrong way. Exceptions crossing package/api boundaries just seems wrong to me.
You can use reflection with recover to do it, the upside is that it's a more general mechanism the downside is that you need to manually rethrow exceptions you don't handle.
BUT the bottom line is that the standard library doesn't throw exceptions. What you do in your code is really up to you, if you really think panicking across api boundaries is the right thing to do you can, there is no go police coming after you.
There are good reasons to panic across package boundaries. See my other comment. [1]
With Go, although you can pass error codes up the stack if you want, you have to do it explicitly. This forces you to think twice before doing so.
This is a key difference: exception handling subtly encourages ignoring errors and letting outer scopes handle them, while error codes force you to deal with your problems within the immediate scope, and then decide what to do from there explicitly.
Personally, I would be OK with that kind of exception handling. I also enjoy Go's error handling. I think Python's exception handling is abhorrent and it constantly bites me in the ass.
There are obviously still non-trivial differences between checked exceptions and Go's error handling, but they aren't as world shattering as unchecked exceptions.
I really appreciate watching it evolve.
I write OpenGL apps in Go. Surely this is in no way characteristic of Google's internal use-cases.
Today's machines are multi-core and are not as bare-bones as to require manually carefully coded malloc/free calls. That's what's changed since C and that's what Go is (very well) designed for. Then a few syntactic goodies that don't imply performance side-effects or any black magic but that C didn't have: duck typing, struct composition, interfaces, all that.
Obviously, things like the garbage collector and bounds checking make Go slower than C. On the other hand, although the Go spec does not mention the stack or heap, you still have pretty much control over memory layout. E.g. making a slice of structs will be backed by an array of (unboxed) structs, pretty much as you'd expect.
I believe that this part of the speech is wrong, or overstated (and Rob did it on purpose). There ARE actual texts about how Go came to be, and none of it involves Google, as a corporate entity, wanting something particular and asking for some specific set of requirements, the way, say Ada was designed by the defence departments, of Erlang might have been designed by Ericsson. The story, as told also by Rob Pike elsewhere, speaks of some guys getting together t hack on a new language on some pain points THEY had, and mostly as a side project.
And, really, Go was never put front and center by Google, or that Google seems to care a lot about it and advertise. They've done far more for V8 and even Dart, including building a large team of full time paid compiler guys to work on those, whereas the did nothing of the sort for Go.
Not much adoption at Google either: some mysql load balancing service in YouTube, a caching layer for Google downloads, a Google doodle served by Go, and beta support in GAE pretty much sums it. Sure there should be some other small projects, but nothing earth shattering, the way C++/Java are used.
Go is an incredibly young language - it only just received its first official release a few months ago, and yet it already has two complete and independent compiler implementations (both gc and the more optimized[1] gccgo), as well as a staggering number of libraries for most common tasks.
As someone who enjoys toying around with developing languages, I've been blown away by how solid the entire Go ecosystem is at this early stage.
> The story, as told also by Rob Pike elsewhere, speaks of some guys getting together t hack on a new language on some pain points THEY had, and mostly as a side project.
I think every language (or every project) has as many stories of its origin as there are creators - if not more. They're not mutually exclusive, as you seem to make it look.
[1] in that it takes advantage of gcc, which is incredibly well-optimized for non-concurrent applications, that is
Pike, along with other Go team members, have been heavily involved in solving very large-scale problems (e.g., http://static.googleusercontent.com/external_content/untrust...) for a long time now, so they are solving the pain they've personally experienced. But other teams at Google will treat Go just like developers in the world at large do -- with skepticism, until it's proven itself. It's doing so now, but it will take some time before you see heavy adoption, which is the way it should be with any new system of this magnitude.
Well, Dart and V8 are too examples to the contrary.
V8 was simply a drop-in replacement for existing Javascript VM's, so there was no "adoption" curve, per se. It ran something like 40x faster than the one in WebKit when it was released, so everyone just said "Awesome, thumbs up! Stick it in there."
Google is spending some developer relations resources promoting Dart, probably because it's the kind of project that requires more public buy-in (including a VM that ultimately needs WebKit hooks, which is not solely controlled by Google). But it still has to surmount the same hurdles to internal adoption that other tools and languages do. I'm no longer privy to the details, but I'm fairly certain that there are just a few internal teams trying out Dart right now, to help it get its "sea legs".
And that's really the way it should be. These things don't happen overnight, and edicts from on high that a particular team will use a new technology don't usually work out well.
I never said anything about adoption curve. I'm talking about money spend by Google on the project and promotion done by them on it. V8 had a large team assembled, with a star compiler guy, was heavily promoted with marketing material, website and even several videos.
Of course V8 was part of their plan to take over the web browser, whereas Go is just a language for them. Well, my sentiments exactly.