Hacker News new | comments | show | ask | jobs | submit login
The language I wish Go was (stuffwithstuff.com)
59 points by dochtman 2585 days ago | hide | past | web | 66 comments | favorite

tl, dr; This post ended up way longer than I expected. Super science summary: I wish Go had tuples, unions, constructors, no Nil, exceptions, generics, some syntax sugar, and ponies that shoot Cheez Whiz out of their noses.

There's always D, if you want more whiz bangs. It's been oft quoted, that good library design involves leaving someone wanting more. I think one form of good language design involves this too. Go is supposed to be minimalist. I think it's a good sign if people keep wishing it were just a little less minimalist.

If you read the post instead of just the funny tldr, you realize that many of the demands would make Go simpler and/or more orthogonal and/or smaller.

Can you flesh that claim out a bit more? I read the post and it was almost all extra stuff that would make the language spec longer, not shorter.

One example was that tuples elegantly handle multiple return values anyways, so you don't need to bother with them if you have tuples (and tuples are good in their own right).

Another is that generics already exist in the language - map works even for user defined types. But since you can't make your own generics, map is all you get. With generics map is just a standard library feature rather than a core language feature, and you could write your own containers too if you wanted.

These might not make the language spec strictly shorter, but definitely more orthogonal.

If you have actually used Go, you would realize that many of his demands display seriously lack of familiarity with the language.

For example, he completely missed defer.

tl;dr? He explicitly mentions "defer"

I'll have to agree with the D comment. As someone who actually writes in D (for fun), I felt that it was more like a checklist for D than a wish list for Go. Though yes, I will concede that Go + those changes != D.

Unfortunately, the Cheez Whiz ponies aren't implemented yet, but if D ever hits 3.0 maybe it can be pushed into the spec.

I got the impression he wanted something close to Ocaml than to D. D is much more complicate than what he is proposing.

I do think D is probably closer to where my heart is. I think my ideal low-level language is a bit like C + some of ML's type system. I think it may be possible to get that without being as heavyweight as D, but I'm not sure.

> Unfortunately, the Cheez Whiz ponies aren't implemented yet, but if D ever hits 3.0 maybe it can be pushed into the spec.

I'd wait first for the any of the existing D2 compiler(s) to at least implement the features that are in D2 'spec', as it currently stands there are no implementations of D2, and that is without going into all the bugs in the most used compilers.

Go actually has a fair amount of syntactical sugar, the language design document has a fair amount of "ooh! And we did this cool thing to save you from typing an extra line!"

I especially like "short variable declarations" (aka type inference, http://golang.org/doc/go_spec.html#Short_variable_declaratio...). Just type one more character than you would in Python for assignments, but you get static typing.

Yes, an other exceptionally new feature invented by the Go masters which nobody had ever thought about before.

Go is supposed to be minimalist.

Every language is designed according to that rule. No language designer thinks, "Let me just slap some extra unneeded stuff in there." They just differ on what they consider necessary.

In Go's case, I think many of the things they've omitted from the language are actually useful features and they've just pushed the complexity onto the users. For example, no exceptions just ends up meaning "if err != nil { return nil; }" everywhere.

Every language is designed according to that rule.

False. Ruby and Perl were both designed (at least for a time) by adding stuff people were asking for or talking about. Not all language designers set a high priority on the thought, "Can we do without it?"

Any complexity not covered over by the language will end up in the user's program.

Any complexity not covered over by the language will end up only in the programs that need it, as opposed to being included in every program. If the complexity is common enough, it's probably worth centralizing (like, say, an object system or type checking), but designing a language so that tricky things that 95% of code doesn't use have special support results in a language that will wow folks on the internet but piss off those using it for real large projects.

Bad culture/developers will screw up a project regardless of what language you use. You cannot prevent this by giving them successively castrated languages. They will still find ways to make things difficult because they don't know what they are doing. I like minimal languages from an aesthetic perspective, but ultimately they're just tools. The second they prevent me from expressing my intent as cleanly as possible, I become much less impressed by any claims they made about purity.

The defer statement is certainly Go-esque, but not as clean as RAII, because I can still forget to write the defer statement after opening a file. This violates DRY when doing file I/O. You can argue that Go never purported to support these concepts in the first place, but, at the end of the day, I'm writing code over and over again needlessly. Not elegant in the slightest.

Disagree and I have a very prominent argument, C.

I mostly program in Clojure now and I have only used Go a little bit. However, I really like Go.

I have a lot of respect for the people who developed Go. I'm sure in their many years of designing operating systems and languages that the features described in these obvious criticisms were considered. That is not to say the language shouldn't be criticised but, in this case, a little research would have made the article a more interesting read.

To take two examples. Constructors are easy to write in GO:


An exception-like mechanism has been proposed:


Constructors are easy to write in GO:

Maybe I didn't clarify it enough, but I tried to explain my problem with initialization functions in the "Future-proofing" section: "If you later need more complex initialization, you’ll have to replace every new(Foo) call with NewFoo()."

An exception-like mechanism has been proposed:

Yes, and implemented and put in the language and referenced in the post. I'm not crazy about panic and defer for reasons spelled out a bit here: http://www.reddit.com/r/programming/comments/du9zu/the_langu...

> An exception-like mechanism has been proposed:

Proposed and implemented almost six months ago.

But that doesn't stop people from writing criticisms of the language and asking for their pet features after a cursory watching of Rob's original (and very superficial) talk. sigh

Panic isn't really exception-like - it's explicitly described as not for transmitting exceptions across module boundaries.

There's more discussion, by me and others, in this past entry: http://news.ycombinator.com/item?id=1576401

A better error handling mechanism may have been a restartable condition system.

Exception-like is not the same as proper exceptions. From what I've read about it (there's the caveat: I haven't used it yet), the error handling model in Go sounds pretty badly broken.

the error handling model in Go sounds pretty badly broken

Broken in what way and what have you read? I'm genuinely interested.

I don't want to end up doing language advocacy for a language I hardly use, but you seem to be confusing error handling with exception handling - something the design of the exception-like mechanism was designed to prevent. As I understand it, in this approach, errors are things you expect to happen (but are errors) and exceptions are things you never anticipated.

Calling it error handling was actually a deliberate choice: since defer/panic/recover isn't an exception mechanism, it wouldn't have made sense to call it one.

What I've read about it so far is the original "Proposal for an exception-like mechanism" thread on the golang-nuts group [1]; and the "Panic, defer and recover" article referenced in the Go FAQ [2]. If any of the stuff below is misinformed, or just plain wrong, I'd love to hear: Go is a language that I want to like.

So now the things I object to:

With Go's model, as I understand it, you can call panic with an arbitrary value. To handle that reliably, your deferred block has to check the type and dispatch to appropriate handler code yourself. That's something you get for free with exceptions, where you can generally provide separate catch blocks for different types of exception.

When you recover from a panic, the execution of your function is over. So if you have two operations which can panic and you want to be sure both are attempted, you have to wrap each one in a separate function. It also means the only way to tell if a function panicked (in case you need to do further cleanup, say) is by checking the return code - and you'd better hope that everyone is following sensible conventions for what they return there.

That leads to another point: Go encourages checking return codes to see if a function failed, whether or not there was a panic. One of the big advances from exceptions, to my mind, was that you no longer had to mingle control flow information with application data. Keeping the two separate makes it easier to write reliable software because it's more self-documenting.

On a more subjective note, I find the syntax really clunky. The examples I've seen all seem to wrap the handler code in an anonymous function: defer func() { ... }() I imagine it would get pretty noisy if you were registering several handlers that way (much like event handlers in Java were).

I don't necessarily think exceptions are perfect either, just that Go's alternative seems like a step backward. But I'd actually quite like to be shown that I'm wrong about this.

[1] http://groups.google.com/group/golang-nuts/browse_thread/thr...; [2] http://blog.golang.org/2010/08/defer-panic-and-recover.html

"the error handling model in Go sounds pretty badly broken."

That is a massive, damning statement. Please don't make such claims without backing them up. Spreading such ignorance is damaging to the project.

Go's error handling model is simple: handle the error immediately, panic, or ignore it. A panic can be recovered, similar to catching an exception.

Just because you heard someone whining that Go is not like their favourite language doesn't mean you should go around claiming it's "pretty badly broken." What an insult!

Given the tone of your post I can't tell whether you're genuine or just trolling, but see my reply to papaf (it was just below, at time of writing) for more details about what I think is broken in it, if you're interested.

Of course if you are trolling, then congratulations on getting a response from me.

Me and many others that have used languages with "proper exceptions" for years are very happy that Go lacks them.

Control flow in code with "proper exceptions" often becomes as convoluted as code using COMEFROM.

While Go's approach to error handling is not perfect, I find it much clearer and manageable than that in any other language I have used.

The discussion over at golang-nuts, the official mailing list - http://groups.google.com/group/golang-nuts/browse_thread/thr...

"Now when you need to read from a file, you just do:

    ReadFile(filename, func(file) {
Now you’re safely guaranteed to close the file when the operation is done. This works because Go has lexical closures, a really nice feature. But the syntax for this is ungainly. Ruby addresses this with block arguments. Translated to Go, they could look something like:

    ReadFile(filename) do(file) {

He does realize, that all he did was replace "func" with "do", and moved some parentheses around, right?

Are there other examples where the difference is more dramatic than this?

Of course he does. And yes, this small syntactic difference does have effects in the real world; rename 'func' to 'delegate', and with the addition of a semicolon (at the end of the println() call) this is valid C# code today - but almost noone writes their resource protection using this idiom because it's ugly.

> this is valid C# code today - but almost noone writes their resource protection using this idiom because it's ugly.

Incorrect (and you don't need delegate, C# has lambdas now). It's because C# doesn't use that for its stdlib and instead has a (redundant) special form and defined protocols.

It is correct to say that replacing the func with delegate and adding the semicolon makes it valid C# syntax. The correctness of that statement is completely independent of C# lambdas, which are neither a complete subset nor a complete superset of C# anonymous delegates.

And it is ugly. It's ugly because the argument list visually expands across more than one line, including whole statements. The "redundant" forms of "using" and "lock" are both more limited than the closure-passing approach, but their limitations haven't been pressing enough to overcome much of tthe ugliness of the closure-as-parameter approach for this specific case of passing a statement block.

Totally agree about Unions, and I think that one of the reasons that nil is there might be because there aren't any yet.

I'm not sure I agree about overloading, though. Maybe something like Haskell's infix notation (1 `add` 2) would be good enough.

"Once you have constructors and you can statically ensure that every variable has a chance at initialization, you can start to escape one of the most unfortunate language misfeatures in wide use today: null"

Question, can anyone think of a case where the lack of pointers being able to point to null will make it more difficult to implement something? That is will you lose expressivity if this becomes impossible?

The only case is when you want to express data is missing (not that a variable is unitialized, but that there's nothing there, think ternary boolean) and that is very neatly taken care of by option types (ML's `option`, Haskell's `Maybe`) which you can easily replicate via union types (create a type Nothing with a single value `nothing` and replace what was formerly a nullable type `T` by the union type `Nothing | T`; depending on whether you want these to be easy to use or not, you may or may not want to add the ability to lift computations into the option type)

Right. The language makes you check for nulls at the few places where the result can go null, and all other tedious null-checking goes away forever.

All the advanced type system jiggery-pokery aside -- that's probably the best "normal programmer" example of why ML & Haskell do static typing better. The ALGOL family of languages should have done that years (decades!) ago.

So many I wouldn't know where to start. Databases have had not-null constraints for years, but an accurate model often means having null values.

A better improvement would be to handle null references better - Rails' whiny_nil support is great for example.

> So many I wouldn't know where to start. Databases have had not-null constraints for years, but an accurate model often means having null values.

These nulls are a different issue than the initialization-originated nulls (that is in fact an other issue with null: they can mean both "I have nothing to put there" and "I forgot to put something there").

> A better improvement would be to handle null references better


> Rails' whiny_nil support is great for example.

whiny_nil exists because you can't do much more in a dynamically typed language, apart from making nil a message sink. Go is not a dynamically typed language, and statically typed language communities have found much better ways to handle most cases where you would use null references decades ago.

Circular data structures where you need to simultaneously initialize two values to each other are the common case where this is problematic. To me, that's a fairly rare edge case and you can easily handle it using mutable variables and option or maybe types.

He mentions BitC. Any comments about this language as I have never heard about it before?

I don't have a lot to say about it personally but it came up fairly often on LtU http://lambda-the-ultimate.org/search/node/bitc

In general I think he is right but he has to start use D not Go.

Sorry to nitpick, but one should say

"The language I wish Go were"

The fact that the subjunctive mood really only matters for one verb to me is an equally compelling argument that a) it's not a big deal that people forget and b) people should just remember it.

The link says:

'As a result, appeals to "preserving distinctions" that are "important for communication" and to "avoiding ambiguity" are baseless and indefensible in this case.'

"no matter which form you use, people will understand what you are trying to say."

That is simply not true. Case in point: when I saw the title "The language I wish go was" I expected to find an article about google cancelling the go language.

That would have been written "The langauge I wish Go had been"

> Case in point: when I saw the title "The language I wish go was" I expected to find an article about google cancelling the go language.

I'm not a native English speaker, but I do think my English is pretty good. Care to explain why the plain past form made you expect that?

In any case, an article titled "Go language cancelled" would have shown up much before anything else. That's what's generally meant by "context".

Give ooc-lang.org a try ;)

You know what I wish Go had? Compiler-enforced immutable state. It is inexcusable that a language designed for concurrent programming in 2010 doesn't support immutability [1].

[1] http://groups.google.com/group/golang-nuts/browse_thread/thr...

Rob Pike answers this question directly in the last couple minutes of his Go talk at the Emerging Languages Camp, earlier this summer. I think it's pretty illuminating as to the rationale behind why Go does it the way it does.


Check out the video, but here's a rough transcription of part of it:

"This is a systems language, which means you should have the choice to do efficient things, if you know what you're doing. You shouldn't be told "you can't do that because you might get it wrong." Let me explain. The model for sharing and locking all that stuff is like having a big piece of paper and we all gather the paper and we make notes on it about who owns what, when -- and we scribble and erase and rewrite and work on it. This model is: you don't have one piece of paper, you have lots of pieces of paper. And you write a message on a piece of paper, and you give him that piece, and he goes away, and you don't have it anymore. He'll bring it back to you if it's relevant to you; he won't if it's not. And because the individual pieces are broken up, there's no concern or worry about who owns what at any one time -- it's whoever's got ahold of it at the moment. And once you understand that, it just doesn't come up as an issue, that for efficiency reasons, I want to pass a pointer on a channel, because once I've passed it I forget it. The only person who has it is the person whose business it is to deal with it now. If you're worried about it and you really care: don't pass a pointer, pass a value. Make it a channel of struct, rather than a channel of pointer to struct. It will be a copy, and there's no way you can share it. But that's your decision, not the language's."

The rationale "You shouldn't be told 'you can't do that because you might get it wrong.'" applied to language design is always suspect. Almost all features added to languages above raw assembler are there to prevent you from doing things that you might get wrong. The flipside of that is having particular syntax that lets you opt in to doing the potentially dangerous thing.

Safe by default, dangerous by responsible choice.

More to the point, a languaage that doesn't provide the tools to manage when, where, and what "dangerous choices" you make is much less worthwhile than a language that does.

C#, for example, doesn't let you modify objects passed in as parameters, unless you highlight that behavior on the method signature and the call itself with the appropriate keywords. There are many other examples in many other languages. "Do what you want, use convention to keep safe" is a recipe for trouble that has caused innumerable very serious defects in software (buffer overflows, security vulnerabilities, etc.) "Do what you want, and you have all these tools to keep track of it" is a much better choice, even if in the end the code does the same exact thing. Banking on humans not making mistakes is not robust.

Banking on humans not making mistakes is not robust.

This should be expressed as a bumper sticker. How about: Human Error: Bet on it!

This is essentially relying on convention rather than guarantees. Convention is fine for smaller programs, but it falls down when you're dealing with large, complex code bases worked on by many diverse programmers.

If you're debugging a sharing problem, and relying on convention, you don't even know where to start looking for it in a large codebase.

Yeah, at the time, I found his explanation uncompelling:

which means you should have the choice to do efficient things, if you know what you're doing.

That directly clashes with other design choices in Go. For example, taking the address of a variable on the stack implicitly puts it on the heap (at performance cost) to protect you from accidentally using the pointer after that function returns.

If you're worried about it and you really care: don't pass a pointer, pass a value.

...and pay the performance penalty of doing not just a copy, but a deep copy, simply because the language doesn't have const.

Wonder how the first sentence goes with mandatory garbage collection.

I think CSP is often good but in a system language you need to share stat and with passign pointer you can only share state unsave. So you copy or you share unsave.

> This is a systems language, which means you should have the choice to do efficient things, if you know what you're doing. You shouldn't be told "you can't do that because you might get it wrong.

I don't have the time to watch the video right now, so maybe I'm reading this out of context, but how does compiler-enforced immutability change this? I'm not asking for everything to be immutable (not even in Haskell is that true) -- I'm just asking for the ability to mark an object as immutable and have the compiler make sure that it isn't modified. I'd prefer that non-local objects be immutable by default, but I'm willing to live with mutable by default.

Really, immutable state gives you the efficiency of pointers coupled with the nice semantics of copy-by-value. It's the best of both worlds -- not having it is denying the programmer "the choice to do efficient things".

Someone on the reddit thread argued that because it is CSP you do not need this. The idea being when you send your object down the channel you lose ownership of it. I don't know enough about the Go implementation to know if this is true or not.

Normaly thats right but you can pass pointers on a channel you share mutible state.

Listen to the go talk at Emergin Languages Camp. Rich Hickey asks about that in the end.

Is there any point where you would want to pass a pointer but NOT because you want mutable state, simply because it's more efficient or something?

Well, pointers have fixed size, random structures may be arbitrarily big and involve a crapload of copying.

I agree completely. I had a note to discuss that (and globals, eww!) but I cut it because honestly that post was too long already.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact