Frankly, I can't help being disappointed by how bland this language is, and I have zero interest in using it. Maybe because I like coding, and the IMHO using Go or Java would totally kill the fun of it.
Frankly, I can't help being disappointed by how bland this language is, and I have zero interest in using it. Maybe because I like coding, and the IMHO using Go or Java would totally kill the fun of it.
Also, I assume the 'no type safety' statement is just hyperbole?
Even the stdlib uses interface{} everywhere now: https://tip.golang.org/pkg/sort/#Slice
Google has a ton of developers working on a giant codebase. If Google were written in something interesting and complex (eg scala or ocaml) then some parts of the codebase would be remarkably complex while others would be simply a ton of library imports and then something procedural. Whether you are in the former or the later would be developer dependent.
Now, say you're a Google exec, you know you're losing a ton of developer hours as people ramp up to different parts of the codebase and that some parts are so complicated that only a few (expensive) engineers could ever work on them. The more you can remove differentiation between engineers and commoditize programming the less expensive specialists you'll have to deal with and (hopefully) things will get cheaper. There's a size at which the investment in creating a new, bland, language will pay off for you - hence golang.
Now, say you're a Google exec, you know you're losing a ton of developer hours as people ramp up
Why wouldn't ramp-up time be a big company's most significant cost?
- Should I try to catch the exception, or just let it
bubble up and edit my interface to include it?
- Should I create a new exception type or reuse an existing one?
- Should I throw a checked or unchecked exception?
- Am I exposing implementation details via my interface?
(eg I don't want to throw an SQLException from GenericDataSourceWidget.connect())
In golang, I know there's really just the one pattern: check if err != nil, prepend a descriptive message, and return it.That'd be the RuntimeException equivalent, sure. But what do you do for the equivalent of checked exceptions? Errors are frequently recoverable, "err != nil" alone does nothing to help you there, and string manipulation is a horrific alternative to types.
- Should I try to catch the exception, or just let it
bubble up and edit my interface to include it?
- Should I create a new exception type or reuse an existing one?
- Am I exposing implementation details via my interface?
(eg I don't want to throw an SQLException from GenericDataSourceWidget.connect())
(except for "- Should I throw a checked or unchecked exception?" since that's fairly Java-specific)To me, those questions seem unavoidable, and sweeping them under the rug is a false simplicity. You're exposing things - what do you expose? How should the caller deal with it? Is it the same as [other thing]? I'd much rather have the type system involved, since error handling is pretty critical to correctness/stability. If go's giving up the safety, what does it get in return?
1) "if err != nil" after every statement
2) give some serious thought to whether the previous statement could panic or not
Good luck if it's a library call that may get updated or call other libraries !
3) Think about the non-error error cases that can't be abstracted out. Go is like C, in the sense that there is an ERETRY "error" (unsurprisingly, you should simply try again, you should NOT fail)
And there are cases where there can be an error and yet error is nil. Easy example of this would be sscanf.
And we now see practical Go code published online : how these problems are dealt with, real world edition:
1) either mindlessly putting "if err != nil { return err }", which is a very bad information-erasing exception system, or just outright ignoring errors. Don't you know you can also use "_" as the error variable ? Maybe they should make that implicit like in perl. Of course perl is likely to tell you this happened ... unlike C and Go.
(really brings back the C days doesn't it ?)
2) most people either don't know or just deny this. Thankfully panics at least do list where they occur. They also kill your program and print stacktraces. Pages and pages and pages of stacktraces.
3) very few people even know about these problems ... so they're ignored, and the standard Go tools themselves don't behave according to unix specifications.
You should really use a library like github.com/pkg/errors so you get to wrap the error you return with additional information. Errors are just a worse Either monad after allm they're much more pleasant to use than exceptions.
That's it. I think OP just assumed panics in Go are what exceptions are in other languages.
And there’s nothing like checked Panics so you’d know if one will happen or not.
And those panics do give you a helpful stack trace, complete with source code line numbers, so it's easy to find the culprit (as opposed to "bubbling up" exceptions).
The canonical use of checked exceptions in Java is for unpredictable events - almost always related to interaction with the outside world, like IO, networking, parsing, etc. These are things the programmer can't prevent, and must defend against, so the type system allows, and in fact forces, the programmer to explicitly address them.
This is all explained beautifully, and at length, in this monograph:
Well, imagine a game letting the user roll a dice. "Chose a number of sides for your dice".
In such cases, handling such a panic might be useful. And knowing that it exists might be useful, too.
If the language does not allow specifying a range of a type (for example, requiring all numbers passed into the RNG to be positive), then it should be specified in the API in other ways programmatically, so it can be statically analyzed.
In languages with dependent types, for example, it’s common to represent a Stack in a way that number and type of elements are stored in the type (so you can’t even pull from an empty stack – that’d be a compile time error).
In the same way, the random number generator should either return an error, or use a number type that can only encode positive numbers as input.
Especially if combined with the interface{} everywhere across the new stdlib functions this all smells very much like C's problems.
Calling a number generator with a negative limit is also a programmer fault, so no reason to provide an error here.
X includes
the language authors (who are using this more and more in the stdlib as pointed out elsewhere)
the authors of any library you use ... but
transitively, so this includes the authors of any library you use indirectly as well
Hmmmm ... what was the problem with (unchecked, or python's) exceptions again ?
How are errors in Go at all monadic? They just have a convention where you return a tuple and manually check if something isn't nil. Either type will inhabit one variant or another, not both with one having a value of nil.
The 'monadic' part of Either (or Result in something like Rust, where try! is sort of like >>= if you squint) is the ability to chain Either types together and have the boilerplate abstracted, that feature is completely absent from Go. Errors in Go are neither the Either type nor monad, IMO.
This is like saying "I'm happy to avoid the cognitive load of having to specify how my code should behave in case of an error".
Sure, your code is simpler as a result. It's also more buggy.
Personally I've had a lot of fun using it, but prior to starting with it I'd mostly done Java and Node, so that may have something to do with it.
So let's special case `make()`, slices, channels, etc. so that some productivity is possible.
Then as they add library features they violate their own tenants as they find utility in these verboten constructs. Exceptions are bad...But we have panics which are in no way the same thing renamed. Never expose them outside a package...But closing an already closed channel panics. Random number generator functions panic on mundane and expected things, just like Java checked exceptions.
Example:
https://golang.org/pkg/math/rand/
``` func (Rand) Int31n
func (r Rand) Int31n(n int32) int32 Int31n returns, as an int32, a non-negative pseudo-random number in [0,n). It panics if n <= 0. ```
Standard behavior would be returning an error, not panicking,
See this:
https://blog.golang.org/defer-panic-and-recover
``` The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values. ```
It's just an inconsistent and, quite frankly, disappointing language outside of goroutines and its interfaces. Those two make it possible to be productive, but with generics for instance a whole slew of new possibilities will emerge.
The simplicity is nice, but it's too simple and too inconsistent. All the verbosity of Java combined with the impenetrable inconsistency and abbreviations of C.
So better spend that time contributing to other parts of the Go ecosystem, or another programming language project.
Snarky, but its such a tiresome argument. If you need something Go does not supply, use a language that fits your needs. There are a lot of programmers happy in Go, and thus happy without generics.
There are programmers who are happy using generics, thus write in Rust, Java, etc.
There is no language to bind them all.
I don't know how maintainable 10-year old Ruby code is though, on the other hand.
This isn't to say Go doesn't belong in that list, but simply to reinforce my point that what startups are using today shouldn't be an indicator of high quality technology that can (or should) gain more traction and use in the future.
90% type safety (probably even more) is more than no type safety. It's not a holy grail that all programming must strive for.
[1] JS Typescript and flow: https://www.typescriptlang.org/, https://flowtype.org/
[2] Python with mypy: http://mypy-lang.org/
[3] PHP with Hack: http://hacklang.org/
This only shows that the sweet spot is probably not in the extremes, but somewhere in between the dynamic type system - paranoid type system spectrum.
I've actually been very surprised at the near-ubiquity of Go in the modern infrastructure/tools space though. Seems like each new OSS product I evaluate is written in Go. See companies like Cloudflare, Hashicorp, InfluxData, CoreOS, and, obviously, big projects like Kubernetes.
I can't actually think of a single other language that matches that. Rust might get there one day but cross-compiling still requires a C cross-compiler (ugh) and C dependencies (e.g. OpenSSL) are often dynamically linked.
Most wrappers should at least provide an option to statically link the C; I know the OpenSSL ones do.
And yeah most wrappers provide a static linking option but there's not much consistency which is rather annoying.
The thing about Go is that its opinions on concurrency are ones I share, to the point where I was practically waiting for something like Go to be invented. Other than say, Erlang, I'm not aware of many alternatives which provide a) ultra-cheap coroutines (10,000 coroutines? fine!), b) an I/O system which is seamlessly integrated with that concurrency system (and in a totalitarian manner at that; if you're using Go, you're using its event-based I/O scheduler, no exceptions), and c) a rock-solid runtime.
[IO SYSTEM.] The imposition of Go's I/O system is important, because it means all Go code is written using the same I/O system, which makes code reuse much more feasible. The chances of you being able to integrate a random OSS library that you discover in say, C++ into your C++ project is much lower:
I call design decisions that pervade every line of code in a project "cross-cutting considerations" (CCCs). These are design decisions where changing your mind means rewriting every line of code, or at least reviewing every line to see if it needs rewriting. Your ability to consume a library depends on where your project and the library sit in CCC-space, an n-dimensional space. If your project is written using an asynchronous I/O reactor, and the library uses a traditional synchronous, sockets-based programming model, you're screwed. You can't use that code, unless you maintain a fork (and in that case you'd have to transform the library into the continuation-passing style, etc.). If the I/O is pluggable, you have to go through the effort of plumbing it into the reactor library you've chosen to use, just to be able to consume that library.
Not only does "using Go" imply the I/O system that goes with it, Go's tightness in language design means you don't see the feature rejection that you see in a language like C++. C++ isn't a language, it's a family of languages; everyone chooses their own subset of C++ to code in. Some people think exceptions are bad and avoid them, and some people think templates are bad and avoid them, etc.
What this means is that the statement "this library is written in Go" is a hell of a lot more meaningful than the statement "this library is written in C++". It's not just C++ either; Python for example now offers a wide variety of choices for I/O, which inflates the CCC-space across which the language's ecosystem of libraries are distributed. One library could use asyncio, one something Twisted-like, one synchronous calls, one threads, etc.
[COROUTINES.] It's the right way to do concurrency. Not the continuation passing style; it's truly preposterous that programmers have been made to write in a format originally intended to be implemented as a compiler transformation. Only recently are we seeing languages augmented with async/await keywords to allow this transformation to be performed behind the scenes (JavaScript, Python 3, C#). Erlang has been around a long time making the CPS look ridiculous, and later there was Stackless Python, an ignored gift horse to the Python community. Stackless Python failed to be a real alternative to Erlang, Go, etc. because it never managed to get a thriving ecosystem or IIRC, a standard I/O system around it.
I also perceive that Go has almost completely accidentially obtained some additional fondness for the fact that it produces statically linked, portable binaries. If you're shipping only Go code, you may often be able to get away without using containers when they'd otherwise be essential. You see Go binaries for Linux being distributed officially by OSS projects when normally for Linux that's very rare; it's left to package managers, and you have distro differences making compatibility potentially tricky.
The fact that Go shipped with a standard, configuration-free build system also makes creating new libraries, or bringing in existing ones almost completely frictionless. Even if you think Go is boring as a language, what really makes it stand out is its execution. Just look at how they're improving the GC with every release.
(This turned into an essay... I guess my ultimate point is that getting a coroutine-based highly-scalable I/O programming environment to work as an ecosystem requires you to standardize on one runtime completely and utterly, and be able to trust that runtime with production workloads. The only such systems I can think of which are stack based and which formed successful ecosystems are Go and Erlang; though now we're seeing a lot more CPS-based systems using async/await annotation, which are probably more than good enough for the same applications. Although I would point out that neither Python 3 nor JavaScript are trying to occupy the multi-thread m:n scheduling space in the same way that Go and (I think?) Erlang are. They're constrained to essentially single-thread operation.)
GHC Haskell.
The C++ Actor Framework is awesome
What language is pure anything? Even Smalltalk wasn't pure Objects. Programming languages are pretty complex. If you look deep enough, you'll find the leak in the abstractions.
Example of lack of purity?