We've been playing around with Go where I work. The concurrency model is great, and the type/object/interface approach is pretty flexible. The language designers also succeeded in eliminating many of the pain points they felt with C/C++/Java: dependency bloat and compilation speed. My workflow with Go has more in common with an interpreted language than anything else.
However, the lack of generics seems like a pretty bad oversight. It's annoying to rewrite common functional operations (e.g. map, reduce) over and over again with slightly different argument types (or sprinkling type casts all over your code).
This is probably one of the most discussed things on the Go mailing list (golang-nuts, if anyone is interested). Unlike some other features (like exceptions, again something oft discussed) there seems to be some openness towards it, but also a desire to not introduce anything that would cause increased complexity without a similar or greater increase in value.
Go's approach to exception handling has a greater impact on the way Go programs are written than the lack of generics. That said, I'm fine with the exception handling because it's obviously a conscious decision by the language designers, and it fits in with the spirit of the language.
The lack of generics doesn't feel like that. It strikes me more as an oversight than anything else. This is especially true because some builtins (i.e. make) are generic functions -- they're just special, and you aren't allowed to make your own.
It's not an oversight. They've said (r and rsc) that they want to wait and make sure they get generics Right in the context of the rest of the language, and make it an easy feature to use in the presence of everything else (should it ever be added.) I'll admit, I've wished for generics occasionally myself, but it's been remarkably rare in the end.
> They've said (r and rsc) that they want to wait and make sure they get generics Right in the context of the rest of the language
In this case, wouldn't it have been better to ship 1.0 with generics?
Retrofitting generics in a language already released with an existing code base sure doesn't seem like the best way to make sure the feature is "done right".
There are some other oversights too: lack of immutability/const qualifier; nil pointers in the language; `defer` working at the function, rather than the scope level, etc. Even for error handling, why not use a sum/option type as several other languages use?
Regarding nil references, I now always think of this Sir Charles Antony Hoare quote when the topic arises:
"I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. In recent years, a number of program analysers like PREfix and PREfast in Microsoft have been used to check references, and give warnings if there is a risk they may be non-null. More recent programming languages like Spec# have introduced declarations for non-null references. This is the solution, which I rejected in 1965."
So he's saying he rejected declarations that allow you to declare either nullable or non-nullable types?
Kotlin and Rust seem to have an interesting approach. In Kotlin, you have to explicitly declare nullable types, and Rust does not allow references/pointers to be null in the first place if I am not mistaken. I have just recently started learning more about Scala, and although it allows nulls to be passed, they are not idiomatic, and I am guessing it would not be too hard to have an automatic style checker that catches uses of null (at least those that do not interface to Java libraries).
No, he's saying he now rejects references being nullable (as the default state of all reference types). A concept of "nothing here" is sometimes necessary so he probably doesn't reject explicit nullability annotations (option types as in Haskell, MLs or Rust for instance; this is not exactly an original approach either).
Scala's approach is sane because you can't dissalow nulls when you're working with libraries that work with null values. On the other hand Scala gives you the tools to deal with nulls very efficiently. You can easily lift nullable values to Options, and because Option is a monadic type, it can be used in for-comprehensions and is very composable.
That's a bit harsh. JetBrains and the developers of the compiler are certainly aware of the Maybe/Option approach, they chose not to use it. Their approach is not as composable as Haskell's but much more practical and more readable in my opinion.
If anything, the fact that Option is still used so rarely in Scala is an indication that maybe, that experiment has failed.
Just out of curiosity, how is Kotlin's approach less practical than Option? (I'm new to this style of programming) Is it because you can use Option types in maps and filters and the like?
Yes, but it goes deeper and I can't give it enough justice by typing this on my iPad.
The thing to understand about Nulls is that most often they denote non-fatal errors that have to be either explicitly handled or otherwise they get ignored. That's why nulls are evil, because NPEs take developers by surprise, at the worst possible time.
Monads are useful for dealing with errors. It's a tool, or a design pattern if you will, that makes the developer either deal with exceptional state or make it somebody else's problem. Developers that are not familiar with monads are doomed to implement it badly [1]
If you're interested for more and are a little familiar with Scala, I've attached an URL for an awesome explanation of this topic [2]
const/immutability at the variable declaration introduces a whole lot of annoyances, see const in c/c++. If you want immutability do it at the type.
For a language that uses multiple return values to indicate error nilable pointers are a requirement. With no constructors a non-nillable pointers would become a strange rarely used feature.
'defer' is currently very useful in loops, open a bunch of files in a loop and schedule them all to be closed at the end of the function. If defer was block scoped this would be impossible.
The error handling with sum/option types is an idea that usually comes from people that haven't used Go or at least haven't thought about how it would work at all. It doesn't work. Languages that use sum/option types for errors are universally functional languages that don't mutate function parameters.
The io.Reader interface is a good example of this problem.
Read(p []byte) (n int, err error)
You could make an option type of 'n' and 'err' but since the caller owns 'p' you can't prevent them from using it without checking for the error.
It's non-trivial to transfer features between languages, they all interact with each other and have dependencies on each other.
Output parameters are indeed a safety problem not solved by sum types.
However, sum types would still give extra safety in most cases.
Also, it is possible to differentiate a type of allocation and a type of a usable buffer, such that "io.Reader" could take an allocation as an argument and return a sum type of an error and a usable buffer. The allocation should be an opaque type not usable as a buffer unless io.Reader converts it to such.
There are all kinds of additional complexity you could add to the type system to get additional safety. Unique pointers and linear typing are a few options, this is the route that Rust is going. But with complex type systems comes a lot of pain and work arounds to get around the types when you need to. Is the benefit of the additional safety worth the complexity?
For some features (your examples of linear types are good ones) the cost-benefit analysis is not clear.
For other features (explicit nullability, sum types, etc) the cost-benefit is very clearly in favor of having those features, at least according to all PL researchers and most users who have experience with languages that have these features.
> For other features (explicit nullability, sum types, etc) the cost-benefit is very clearly in favor of having those features, at least according to all PL researchers and most users who have experience with languages that have these features.
In a vacuum, sure. But sum types don't interact in obvious ways with Go interfaces.
And FYI, I enjoy both Haskell and Go. Each have their own strengths (and weaknesses). Haskell's expressiveness and safety are a pure joy to work with. But the simplicity in the language design of Go make working with the language very productive. (I very rarely find myself in a situation in Go in which I have to think about "how do I express this idea using Go". Unlike Python and Haskell, which are huge languages.)
The cost-benefit analysis of a language feature MUST be performed in the context of a language and its stated goals.
> 'defer' is currently very useful in loops, open a bunch of files in a loop and schedule them all to be closed at the end of the function. If defer was block scoped this would be impossible.
Why couldn't it work at both the function and block level? I too would like to use defer at a block level. I find myself creating functions just to gain usage of defer, or not using it when I'd like to.
I can think of lots of times I use defer in a block where it would be very problematic to have it execute at the end of block rather than end of function. I'm assuming there would be new syntax. That alone I think would be a strong mark against it.
But I didn't mention C++ :) (D and Rust have const too)
To be honest, I haven't looked much at Clojure. Do their containers/data structures use the immutable versions by default (kind of similar to what Scala does?)
The Rust programming language seems to have something going in that area. Immutability is the default, you have to explicitly declare mutable data members. Similar with methods, you have the option of declaring `self` to be mutable (which seems cleaner than C++'s approach).
Clojure only provides immutable data structures. Java, however, provides many mutable data structures which are readily available to Clojure. Clojure also provides mutable reference types that are tuned for particular concurrency patterns. Atoms for synchronous compare-and-swap, agents for asynchronous queues, etc.
There are no annotations to specify immutability, you have to trust whoever implemented the class you're using to provide the guarantees that are in their documentation. To some extent, that makes this a static vs dynamic typing discussion, but my point is that type annotations are realistically not necessary if you chose immutability as the default and provide a limited set of well known mutation operations. These operations are generally annotated with a "@", "!", or "." for "dereference current value", "side effectful", and "java interop" respectively.
Similar feelings here, although I've only been playing with it for a week or so. As someone who grew up (literally) on interpreted languages, Go feels far more pleasant to code in than C/C++.
> It's annoying to rewrite common functional operations (e.g. map, reduce) over and over again with slightly different argument types (or sprinkling type casts all over your code).
Personally I would go with interface{} and type cast.
> Idiomatic Go (currently) is to use a for loop for map and reduce/fold operations.
There is going to be a for loop one way or the other. The question is n occurrences of for loop or one occurrence of for loop in a generic_map func followed by n-1 occurrences of call to generic_map and type casting.
As for performance, function calls themselves incur a performance penalty. That doesn't necessitate putting all code inline. If the profiler says that interface{} and type casting incurs a significant performance penalty(it does incur a penalty but does it matter?), then I would go for inline for.
Congratulations to everyone involved with Go. I've been loving using it and it's nice to see CloudFlare mentioned in that post as we are using Go more and more for our production systems.
From the upvotes, apparently Go has a lot of support on HN, and/or there are a lot of Googlers here. I am particularly curious about Go and Ceylon, 2 recent languages with strong corporate backing. Is anyone using Go in a startup? Congratulations to Go open source for 3rd year. Go Go!
Personally, I've used Go for some prototyping work internally. I thought it was a great language to write in. Sadly I haven't had a chance to do something in production with it myself yet!
I've been keeping a close eye on Revel as well (http://robfig.github.com/revel/), which is a Play-like web framework under active development.
Great link with revel, it looks promising. I've used Go in production but only for internal things like command line utils for wiring things together. Its a great language especially if your coming from c.
up vote for sharing Revel. I added myself to their notification list. Have you compared it against other web framework in Go (I know revel is very young). I also love Go but still searching/waiting for the one web framework that will emerge. I almost wonder if google should put their weight behind one just to reduce chances of further fragmentization.
I work for Braintree -- we've been playing around with Go internally in the last two months, but no production system uses it yet.
The language itself is pretty cool, but most of our work is still web application development (broadly defined). Go isn't particularly suited for that, and even if it were, it doesn't have a mature framework for doing webdev yet.
> The language itself is pretty cool, but most of our work is still web application development (broadly defined). Go isn't particularly suited for that, and even if it were, it doesn't have a mature framework for doing webdev yet.
Really? Web development is one of Go's strengths IMO, and the standard library was clearly designed with web development in mind. i.e., the HTML template library, the net/http library, etc.
There are also a few third party frameworks out there. They may or may not be mature depending upon your definition, but gorilla looks promising.
Its nice to see my name in the Contributors file. I found that submitting patches was easy and said patches were quickly reviewed.
What isn't easy is reading and understanding the documentation. The jump between the Go tour and the Go docs is gaping. I wish some five star technical writers would take a look and fix it since I feel that is Go's biggest problem right now.
Between The Go tour and the Go reference does is the Effective Go guide. It gets you going pretty well with lots of practical examples and patterns of how to do things in Go.
I wish the docs included basic examples of how to use various packages and functions. I end up searching for golang packageX example instead. And often I end up on StackOverflow where someone else is using it wrong and ten others are trying to correct them.
I completely agree, but only after a good read of Effective Go and honestly the language spec. The language spec is documented and completely, completely readable. I have no doubt it's a result of the simplicity driven by the simple/fast compiler. Just seeing the examples get you in the Go states of mind and prepare you for the standard library.
I do think that sometimes the more complex packages are aided by examples and I think the std lib could take on a few more examples. Fortunately, http://gobyexample.com someone is already doing that.
However, the lack of generics seems like a pretty bad oversight. It's annoying to rewrite common functional operations (e.g. map, reduce) over and over again with slightly different argument types (or sprinkling type casts all over your code).