Alan Donovan and Brian Kernighan Answer Questions on Go
features.slashdot.org
features.slashdot.org
I, for one, don't like abstraction and power just because I'm a PL geek. I admit those are also reasons, but the truth is I use them to make my codebase smaller, simpler, and easier to reason about.
For some, achieving that goal means writing for loops. For me, it's never having to write explicit loops.
The problems tend to crop up for other people trying to decipher and read it. Sometimes also for the machine trying to execute it.
> Go has everything you mention in both of your lists of desirable attributes (depending perhaps on what you mean by "extreme abstraction")
"perhaps"? By whose definition would it ever even come close to "extreme abstraction"? All of us assembly programmers, maybe?
Don't get me wrong, I use Go where it makes sense. It has pros and cons. I'm literally programming Go right now. But "extreme abstraction"... you need more than "perhaps" to qualify that.
Or was that his point and did I just completely miss the joke? Actually, that's probably it.
Yet still, Kernighan said what he said.
The Linux kernel is still written in C.
Ben Klemens, author of 21st Century C, leads the statistical computing group for the research arm of the U.S. Census Bureau. He models complex systems and computations in C.
The entire Python scientific computing stack is resting on C.
A huge portion of the GHC compiler is written in C.
Many AAA game developer studios are writing their engines in C (with vendor supplied C++ compilers, but C none-the-less).
And do people really write C89 for greenfield projects today? C99 is pretty amazing and has added some great features to the language. C11 might be the trivial change having only added some atomic operators (depending on whether you think this is trivial) and rescinding VLAs (though most compilers will probably continue to support them anyway).
I think Go is a perfectly fine language but it seems like perhaps the group is a little out of touch with the rest of the world judging by replies to questions like this.
As for C99, I would love to see some hard stats about rate of adoption. Personally I can't imagine starting out on a new project and not use C99, but then the hard-core C contingency is a pretty conservative bunch. (Are there still systems where you can't get a C99 compiler?)
The rate of adoption question is interesting. If only there were a foundation or group out there shepherding the community and keeping tabs on such developments. If there is I'd appreciate being pointed to the right address.
In windows it's pretty annoying to get a C99 compiler. The "standard" compiler to do Windows development is Microsoft's, and it doesn't support C99.
Of course you can install MinGW (gcc); most open source software on Windows uses it, but it's very unusual for the average Windows developer to even have gcc, and completely unheard of in the commercial space.
I will also note that installing MinGW in Windows is a pretty painful experience as well, which doesn't help.
For most Windows developers, if it doesn't ship in Visual Studio, it doesn't exist.
There are options.
Also note that Microsoft has finally started improving C99 support with Visual Studio 2013 and 2015.
Only as far as C++ standard requires C compatibility.
For Microsoft the future for native programming on Windows are C++ and .NET Native.
For compatibility with open source world and ISV that still care about C, the answer is the integration of Clang frontend with Visual C++'s backend, that is coming with Visual Studio 2015 Update 1.
Nevertheless, I stand by my comments re VS2013+. If you're accustomed to working on projects that support varied platform/compiler/libc combinations, but you've still specifically found VS2010 (and earlier) particularly difficult to work with due to the insistence on C89, you'll probably find VS2013 (and later) substantially more to your taste, and by quite some margin.
(That's certainly been my experience anyway. After some initial effort to work around library differences, my C99 code builds with gcc/clang/VS2013 - and does so, ongoing, with very little effort.)
- Commercial compilers for embedded space.
- Security areas where compilers are certified.
Aren't a lot of NumPy modules reliant on fortran libraries?
Is it? I thought it was written in Haskell. Or are you referring to the GHC runtime?
Also, at the beginning the Go team wasn't all that enthusiastic about an IDE, but its great to see that Go will get an IntelliJ grade IDE soon, so that works for me :)
With Go's removal of version numbers, they haven't removed any of the complexity of resolving incompatibilities, they've just hidden the fact that there is one. I would have much preferred a strategy of maintaining version numbers, and resolving them via "either you specify what version you want pulled in, or we'll build with the most recent and throw a warning".
It's fine to not ship a solution in version one. But to use it as a reason to not have version numbers at all seems like heading in the wrong direction (backwards).
1. Treat each version of a package as a separate package. If there are two versions required, they are both included.
2. At compile time transform package names to include the desired version number.
I may be completely misunderstanding or over-simplifying this problem.
Imagine a queueing library, for instance. You push something onto a queue created with library A, and then you want to read from library B (A and B are different version of the same library). Just about anything could happen (depending on how the library/language are implemented). You might get an error due to library B reading a data structure formatted for library A. Or you might get nothing back, because library B is checking for the queue in its own registry, while it exists in library A's. Etc.
If there is no library-dependent state between calls, or the calls are not being mixed between libraries, you can totally do that, though. A JSON serialization library can totally be supported in that manner, for instance. The queueing library example, if it's only being used to support other libraries or separate functionalities, such that you never have to push onto a queue in library A, and expect to be able to pop it off from a queue in library B, would work, too. But in that latter case, that's not something the library writer can guarantee, it's all about your own usage. So there's a lot of potential issues.
// package a
lib-v16.Queue(int32) // Stores in queue for version 16 of lib
// package b
int32 = lib-v18.DeQueue() // Loads from queue of version 18 of lib, which is empty
And even if we accept that you shouldn't have state in the package and the queue should be passed around, now we just have two packages (a & b) that use incompatible versions of lib for potentially no reason.To expand on (b), suppose that packages A and B depend on a shared queue managed by package C, as in your example. Because A and B have such tightly coupled logic, chances are that some package--either one of A and B, or some other package that depends on A and B--will have code that expects a type from "A's C" to be equivalent to a type from "B's C". At that point the code will fail to compile, and the programmer will fix the problem before it ever hits production. (Fixing this is easy because you can just inspect the lockfile to find all the crate versions in use.) Basically, static typing makes it such that, while the problem exists in theory, in practice its probability is so low as to not be a concern.
This won't work if the dependencies are truly depending on incompatible-at-the-API-level versions of the same library, of course. But there's no magic bullet solution to that—not handling versioning in the language/package manager doesn't make that problem any easier.
But that requires a monorepo that scales and people to test and check in the upgrades, so it doesn't work well for open source development, which is why people are working on alternatives.
But this can all be done using a separate system. The build system itself doesn't need to know anything about versions. If you look at Bazel, it doesn't let you specify versions for target deps. Only the developer actually upgrading a third-party library needs to deal with open source version numbers, and everyone else can build on their work.
Of course it could be done in a separate system. It could be done and tracked in a separate system even with version numbers. Go has just chosen to -require- you running your own package manager to make things stable, whereas for most languages it's optional and you can create a repeatable, portable build without it.
https://nodejs.org/api/modules.html
// Package A:
import B from "B"; // A/node_modules/B/1.0.0
import C from "C"; // A/node_modules/C/1.0.0
// Package B:
import D from "D"; // A/node_modules/B/1.0.0/node_modules/D/1.0.0
// Package C:
import D from "D"; // A/node_modules/C/1.0.0/node_modules/D/2.0.0Now let's say that module A is depending on version 1.0.0 of module C, and module B is depending on version 1.5.2 of module C.
Does B.bar(A.foo()) still work? Is type t the same in C version 1.0.0 and 1.5.2?
B = import("b", ">1.0")
C = import("c", ">1.1 && <2.0")
etc.The diamond dependency problem only happens when two indirect dependencies are identical but on different versions and that these two versions are incompatible. That's two big if's. 99% of the time, it's not a problem.
Notice first that this is a pretty rare problem since libraries tend to be backward compatible these days. And if you happen to come across a bad behaved one, you simply exclude the one you don't want from the graph, problem solved.
This problem has been fixed in Maven for almost ten years now, the only reason why you would not implement this crucial feature in a version manager is laziness.
For better or worse, a sizable proportion of developers choose IDEs on the basis of looks.
https://github.com/go-lang-plugin-org/go-lang-idea-plugin
So far the best "Go IDE", I've used.
The same scheme could be used to resolve diamond dependencies in multiple inheritance as well.
I'm certain I'm biased, but in my experience it's been the most robust way to handle versioning that I've dealt with.
Imports naming specific versions would also allow better integration of code/build into a version control system. Actually, the entire OS should be built with version control in mind from the ground up.
Oh really? That's great news! I remember having that problem earlier this year and thinking that it wasn't the greatest from a usability standpoint.
https://channel9.msdn.com/Events/Visual-Studio/Connect-event...
I am loving it, honestly. Microsoft has a great product here.
"The languages... are either long gone (PL/1) "
http://www-03.ibm.com/software/products/en/plicompfami
http://www.fujitsu.com/fts/products/computing/servers/mainfr...
http://www.iron-spring.com/about.html
Next he's going to be telling you COBOL is gone, too. Big time writers' definition of dead/gone in IT has always seemed different than general usage. ;)
That's completely incorrect: it's trivial (and common) to just ignore errors in Go:
ok, _ := Foo()
Checked exceptions don't let you get away with this kind of sloppy programming. try {
ok = foo()
} catch (Exception e) {
// Do nothing
}
Checked exceptions don't save you from "this kind of sloppy programming".As an aside, I once tried using a crypto library to do something fairly simple (encrypt a file I'm writing to disk), and I had to handle a whole stack of exceptions that I had no clue how to handle. So what can you do? You should be able to write code that provides sensible default behavior, but checked exceptions make you work to just get that behavior, which is not how default behavior is supposed to work.
Return value errors like Go implements forces everyone to care about all errors, at all times, even those they can't handle, which is why you see the pattern
ok, err:= Foo()
if err {
every ten lines in Go sources.If those were runtime exceptions, my program would just crash on error, which is the default I wanted. I didn't want to pollute my code with error handling stuff just to try out some simple functionality. If they were runtime exceptions, I could learn to use the code from the ground up, not by having to understand the whole thing at once.
That's exactly the point: not everyone along the method stack is forced to deal with it, only the one caller that knows how to handle it.
> The problem is that I don't know where it should be handled
Then don't handle it at all and let it crash the program. But at least you didn't add boiler plate simply bubbling up an Err at every level of the stack frames.
> If those were runtime exceptions, my program would just crash on error, which is the default I wanted
Sometimes it is, sometimes it isn't. Some exceptions should crash your program (runtime exceptions), others should be handled (checked exceptions). Languages that take correctness seriously should offer you both options.
My goal wasn't to write correct code, it was to test something out. To get comfortable with the library and the task. Checked exceptions get in the way of that.
>Languages that take correctness seriously should offer you both options.
Rust and Haskell take correctness seriously and offer neither. Checked exceptions make perfect sense in terms of ensuring correctness, but they are awful for usability and they are not the only way to achieve correctness.
>Some exceptions should crash your program (runtime exceptions), others should be handled (checked exceptions).
Shouldn't the user determine that, not the implementor? Why should any exceptions not be checked?
Because there are exceptions you can't do anything about (e.g. OutOfMemoryException) and exceptions that you don't know what to do with (e.g. an NPE where you didn't expect it).
NPE is the poster child for an unchecked exception: if you know your code is throwing an NPE here, just fix it instead of catching the NPE.
Now if you choose to handle this stupidly, that's entirely your fault, but at least the compiler did its jobs by forcing you to think about the error case.
In Go, the compiler doesn't enforce anything.
In Go, as in Java with checked exceptions, the compiler forces the programmer to handle the error.
Using _ in Go is not idiomatic. It's the exact equivalent of using a try/catch block with nothing in the catch block in Java.
They sure do. Just write your Java code without a try/catch block anywhere, or just have your catch do absolutely nothing. You can do it, trust me. Exceptions don't stop programmers from doing anything.
The point of checked exceptions is that you can’t do that. It is a compile-time error to not either catch the exception or explicitly indicate that you will propagate it.
Unfortunately, at least in Java, that style proved too onerous for a lot of programmers and motivated the catch-all, do-nothing wrapper idiom that is completely unhelpful as far as safety goes.
Exceptions don't stop programmers from doing anything.
At least in principle, you can statically detect any failures to handle possible exceptions if you have a suitable type system. Of course, if you just hack around those warnings, as we’ve seen Java programmers do with checked exceptions and catch-alls, then you’re no better off than if you ignored a relevant return code in the first place (aside, perhaps, from making it much more obvious to a static analyser or during a code review that you are doing so).
> The point of checked exceptions is that you can’t do that.
Sure you can. You just have to write "throws Exception" after all your function signatures.
You can't. I don't think you understand how checked exceptions work.
try {
foo();
} catch (Exception e) {
;
}
?Also, you don't need the semicolon.
var foo *bar
foo.Baz()
Accidentally dereferencing a null pointer in Go is dangerously easyhttps://play.golang.org/p/cjmflMBhF7
Why not learn the language before criticizing it?
if b == nil {
fmt.Println(":)")
} else {
fmt.Println(b.emote)
}
In this case, that's useless, but that's an artifact of the chosen example. I use it every so often in places where it happens to have meaning.Go often treats nil as a legal value, which means that some of the things you might expect to crash don't, and when used idiomatically can sometimes make for shorter code. For instance, the "length" call on slices will happily take a nil and return 0, you can "append" to a nil slice and get a slice back, etc. Ultimately it's still a language with a null in it, though. There's no non-nil pointer type.
1. Signal to the calling programmer that some error condition can occur. For example having to catch SomethingNotFound tells them that it's possible that Something might not be found.
2. You just put in your coding standards that you must do something sensible inside of a catch block, and get a code checker to break the build.
I don't think anybody is trying to say that checked exceptions have "solved" the problem of people ignoring error conditions. It does at least give you an easily visible thing to point at and say "that's wrong" though.
By that standard, exceptions actually fit in between C-style obliviousness and explicit error returns. They do allow a certain amount of implicitness (does this code have no try/catch exception handlers because the programmer is deliberately invoking the default exception behavior, or is it because they didn't think about errors at all?), but you still can't ignore errors the way you can in C. And then with explicit error returns, you can't ignore them at all without leaving a trail of your decision to do so right there in the source code.
Checked exceptions tried to straddle the boundary, but I think I have to agree with the general consensus that they are a failed experiment. One of my "cut through the noise" metrics for language design decisions is "do any subsequent languages pick up the feature?". If a language as dominant as Java has a feature, but after 10-15 years no new languages are picking up the feature, that Means Something.
do {
try error-return-statement
statement
statement
try error-return-statement
} catch ErrorType1 {
...
} catch ErrorType2 {
...
}
It looks like exceptions, but under the hood error objects are being returned by error-return-statements, thus the explicit try keywords before them. It retains go error object simplicity, but keeps things readable and tidy. err := f1()
if err != nil {..} // have to do this or compiler error
err = f2()
// oops, forgot to actually do anything about it!
Now, granted, `go vet` helps with this, but.. these kinds of things can be solved by the language proper in much better ways, and they should be type errors. Like rust's `Result` or Haskell's `Either`.Edit: rust's result is especially nice with #[must_use]. This has saved my team from mistakes relatively frequently.