"The Language I Wish Go Was"

458 views
Skip to first unread message

MCM112

unread,
Oct 21, 2010, 8:46:26 AM10/21/10
to golang-nuts
I recently stumbled across this article at:
http://journal.stuffwithstuff.com/2010/10/21/the-language-i-wish-go-was/

I thought it might be prudent to post it here to hear the views of the
go community on some of the points raised by the author.

Cheers,

Ben

Corey Thomasson

unread,
Oct 21, 2010, 9:13:09 AM10/21/10
to MCM112, golang-nuts
Some of those points are invalid (block arguments for example, are
just another form of closures, which go has). The rest have been
discussed, and discussed, and discussed on the mailing list.

Andrew Gerrand

unread,
Oct 21, 2010, 9:15:48 AM10/21/10
to MCM112, golang-nuts

It's a long post that proposes adding a lot of things to the language.
I think the author picked a good title; he wants a language that is
not Go.

Go is a (very) small language, and IMO that is one of its major
strengths. Features are only introduced after they are deemed
essential.

Andrew

peterGo

unread,
Oct 21, 2010, 11:53:28 AM10/21/10
to golang-nuts
Ben,

On Oct 21, 8:46 am, MCM112 <m...@stickyeyes.com> wrote:
> I recently stumbled across this article at:http://journal.stuffwithstuff.com/2010/10/21/the-language-i-wish-go-was/

The author probably likes stuff like this too.

Giant Knife 16999 - Wenger Swiss Army Knife
http://www.wengerna.com/giant-knife-16999

Peter

nsf

unread,
Oct 21, 2010, 11:54:22 AM10/21/10
to golan...@googlegroups.com

Yeah, like "I want to stick in a bunch of features to the Go", but as
Andrew has pointed out, that won't be the Go anymore. IMHO the author
of that article doesn't really realizes that if he will stick in
templates _and_ operator overloading in any language it will become
a mess. It is inevitable. I mean really, we have a language that has
(almost) all these "nice" features and it is called D. And no one uses
D, because it is overcomplicated and bloated.. But you know, it
compiles faster than Go, but that's no the only reason why we love Go.

Being simple is a very hard property to achieve, and Go is the closest
thing we have today.

Of course Go has its own issues, like recent proposal of a new built-in
"append" function showed us, which btw removes the need for a "vector"
package mentioned in article. But let's be honest. Go is good enough and
adding new features now should be a strictly "de facto" thing. I mean
when we want to add something we must have at the same time enough
cases which this new feature solves and extra steps should be
considered to make sure that the solution is generic enough to be
included in the language. Let's take me for example. Currently I have
one major Go app (> 5k loc) - gocode, and many small ones. And the
only case where I had to use very evil copy&paste technique was exactly
the vector-like append functions.

Experience in other programming languages tells me that all you need
most of the time is a vector and a hash table, because these two has
the most interesting properties (vector is a contiguous chunk of memory
and linear/streaming memory access patterns are preferred on modern
CPUs, and map has insertion/lookup complexity close to O(1)). Take a
look at most of the scripting languages, almost all of them has two
types of built-in containers, it is a vector and a hash table. What I
mean here is that maybe (really, maybe) we don't need generics at all.

On the other hand there are few cases where having an optimal sort
function is a big win. And current "sort.Interface" is not a solution
to that, it will always be slower than C's qsort which is slower than
C++'s std::sort. Also we don't have any kind of generic binary search,
which is sad. I don't think that adding built-in function for each of
those cases are a good idea, but for some.. maybe.

That's what I think regarding most painful topic of the Go - generics..
Let's see what else in that article:

- Named/keyword args - I think author proposes some kind of a name
mangling and compile-time code generation features. Crap.

- Block arguments - they're nice. But it's pure syntax sugar for
calling functions where the last argument has a function type. Syntax
sugar needs a lot of arguments to be considered for adding to the
language, IMHO.

- Operator overloading - makes language unreadable, especially if mixed
with templates. Also requires other considerations like "reference"
types (see C++). In theory is simple, in practice complicates a
language a _lot_.

- Tuples - maybe, but python experience tells me that they are a very
bad duplication for structs. Once you want to have a name for each of
the tuple's components, you're screwed. Better leave them out.

- Unions - I miss them, at least for my go lemon parser generator port.
C's and D's versions have no problems with lemon, because they both
have unions. In Go I have to use interface{} magic, which has bad
performance properties.

- Constructors - my experience shows me that they are useless. First of
all they don't work if you don't have function overloading, because
obviously for some types there is more than one way to initialize
them. And function overloading adds another layer of complexity and
ambiguity. Secondly, the problem with error handling. People will
want to write myFunc(MyObject(1, 2, 3)) and then they will complain
that it's not possible, because constructor returns multiple values
(we need to handle errors, right.. ugh.. exceptions I forgot). Crappy
features, please don't even consider it.

- Eliminating nil - I'm a C guy, nulls are ok for me. Go solves the
most nasty problem btw, it doesn't allow programmers to use 0 as nil.
The problem is addressed in C++0x as well, but it's too late for that
language.

- Exceptions - some people keep telling me that they are a superior way
of handling errors. Other people (including few very famous
programmers like John Carmack) tend to avoid them. I don't understand
them as well. Having panic/recover is fine for me as long as they
don't introduce additional language statements (hello to
try/catch/finally, the most horrible thing I've ever knew about
programming languages) and as long as Go's standard library doesn't
force me to use them.

- Generics - I've already said enough about them in the beginning.

- Uniform Access - who? Bertrand Meyer? OOP guy? I think he is nuts. In
fact every person who seriously considers OOP as a valid term is
nuts.

Summary: I dunno... A long answer to the long article.

Russ Cox

unread,
Oct 21, 2010, 12:55:19 PM10/21/10
to m...@stickyeyes.com, golan...@googlegroups.com
> Eliminating nil

There was a very long discussion about this last year
on this mailing list. No one had any proposals that
worked and left the language intact.

> Exceptions

http://blogs.msdn.com/b/oldnewthing/archive/2004/04/22/118161.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx

Having to write and then maintain codereview.py
has only made me more aware of how right he is.

Russ

Namegduf

unread,
Oct 21, 2010, 1:27:36 PM10/21/10
to golan...@googlegroups.com
On Thu, 21 Oct 2010 05:46:26 -0700 (PDT)
MCM112 <m...@stickyeyes.com> wrote:

It's nice that they've tried the language and seen what they thought,
but I can't say I found the post particularly illuminating. The central
problem that, in my view, makes it an unsatisfactory review is that a
number of the issues they raise are handled in Go by common idioms, and
they haven't reviewed their suggestions against these idioms, at least
not as explictly and in as much detail as I think would be required to
be able to tell if their ideas actually provide benefit.

The alternative for "scoped behaviour" in their example is defer, which
is not discussed (well), and the provision of New* functions replaces
constructors with a more explicit form; their request for constructors
needs to be directly compared against this. Their section on error
handling is very sparse on discussion of whether the explicitness of
the handling is good or bad, which is the primary change exceptions
make, and also lacks a discussion of the value or lack thereof of
banning, by convention, panic()/exception-like behaviour across package
boundaries, another important idiom.

This might just be my opinion, but I don't think future-proofing, as
they describe it, is an idiom in Go, either. They should directly
compare the uniform access feature against a lack of any such
future-proofing, not against speculatively wrapping everything. I feel
this would be hard to prove reliably either way. My personal opinion is
one of dubiousness that the cost of modifying every call site in the
event changes happen in one place beat the costs of mitigating it
*everywhere* pre-emptively.

Another of their points seem not quite thought through; their point
about "type safety without coupling" would only apply if the error from
their function was the only error the function it was passed to
returned, and their function only ever returned one type of error.
It seems unlikely to be valid in real code, where os.Error is common.

Finally, their suggestion for multiple types for a return value is just
bad; it appears to create both additional syntax and be longer, and they
suggest it would be more efficient for "avoiding creating a variable"
without actually suggesting an implementation so the other performance
effects could be examined; I'd think this would require returning
something like an interface to make the type switch possible, which
seems like it would have significant effects of its own.

All in all... I think it would be a better review if the author had
spent more time with the language, possibly working with other people's
code, before judging the language, and perhaps thought a little harder
in some cases. It is hard to begrudge them this, although I would have
suggested not writing the review if they weren't willing to seriously
consider what is idiomatically used as alternatives to the desired
features, and be quiet about performance when they haven't analysed
what their idea would cost to implement. Perhaps my standards for blogs
are too high, though.

It does seem, though, that the language they want is not Go.

unread,
Oct 21, 2010, 1:50:23 PM10/21/10
to golang-nuts
On Oct 21, 5:54 pm, nsf <no.smile.f...@gmail.com> wrote:
> - Block arguments - they're nice. But it's pure syntax sugar for
>   calling functions where the last argument has a function type. Syntax
>   sugar needs a lot of arguments to be considered for adding to the
>   language, IMHO.

I don't think the mentioned "block arguments" are syntactic sugar,
because they are able to ensure certain semantic properties (e.g:
always closing a file no matter what). Pure syntactic sugar looks
somewhat differently. For example, overloading the operator "+" in C++
is pure syntactic sugar (I'm not saying it is useless), because it is
just a fancy way of defining a function. Another example, multiple
return values in Go are pure syntactic sugar, because you could do the
same thing by defining a new struct type and returning it instead.

> - Constructors - my experience shows me that they are useless. First of
>   all they don't work if you don't have function overloading, because
>   obviously for some types there is more than one way to initialize
>   them. And function overloading adds another layer of complexity and
>   ambiguity. Secondly, the problem with error handling. People will
>   want to write myFunc(MyObject(1, 2, 3)) and then they will complain
>   that it's not possible, because constructor returns multiple values
>   (we need to handle errors, right.. ugh.. exceptions I forgot). Crappy
>   features, please don't even consider it.

I agree. You are better of if you define and then call a function to
create the object, instead of introducing constructors into the
language.

Although I do *not* think this applies to copy-constructors and move-
constructors.

> - Eliminating nil - I'm a C guy, nulls are ok for me. Go solves the
>   most nasty problem btw, it doesn't allow programmers to use 0 as nil.
>   The problem is addressed in C++0x as well, but it's too late for that
>   language.

I don't agree. The fact that *any* pointer can be nil is a bad thing.

bflm

unread,
Oct 21, 2010, 4:39:30 PM10/21/10
to golang-nuts
On Oct 21, 7:50 pm, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:
> > - Eliminating nil - I'm a C guy, nulls are ok for me. Go solves the
> >   most nasty problem btw, it doesn't allow programmers to use 0 as nil.
> >   The problem is addressed in C++0x as well, but it's too late for that
> >   language.
>
> I don't agree. The fact that *any* pointer can be nil is a bad thing.
It's not a bad thing, it's something quite different - it's your
opinion. Let's imagine e.g. a single linked list in a language without
nil pointers. So you have to use for the nil's purpose a specially
instantiated non nil sentinel with exactly the same semantics as a
trivial, no constructing necessary, nil would have already. Would you
not check for the next pointer being equal to the sentinel (vs.
checking it against nil), you're gonna process garbage instead of
getting a segfault. The later could often be even a lot more safe and
preferred bug behaviour.

IMO demanding a guarantee of any pointer being non nil in a
programming language model is just like asking for a zero being model-
wide forbidden as an integer value only because it can crash some
process when blindly used as a divisor.

unread,
Oct 21, 2010, 4:59:15 PM10/21/10
to golang-nuts
I guess you need some time before you understand what the article was
saying about nil pointers ...

Eleanor McHugh

unread,
Oct 21, 2010, 5:04:09 PM10/21/10
to golang-nuts
On 21 Oct 2010, at 16:54, nsf wrote:
> - Block arguments - they're nice. But it's pure syntax sugar for
> calling functions where the last argument has a function type. Syntax
> sugar needs a lot of arguments to be considered for adding to the
> language, IMHO.

Personally I think syntactic sugar for this would be a huge win. It certainly is in Ruby, particularly for blocks which make use of yield to iterate generated results in a traditional coroutine style. In Go this could be integrated nicely with range to improve orthogonality between slice types and user-defined types.

> - Unions - I miss them, at least for my go lemon parser generator port.
> C's and D's versions have no problems with lemon, because they both
> have unions. In Go I have to use interface{} magic, which has bad
> performance properties.

Unions are another feature I could leverage a lot if it were added to Go. A lot of the dirty hackery I currently do with unsafe would disappear and the type safety of my code would be improved.

> - Exceptions - some people keep telling me that they are a superior way
> of handling errors. Other people (including few very famous
> programmers like John Carmack) tend to avoid them. I don't understand
> them as well. Having panic/recover is fine for me as long as they
> don't introduce additional language statements (hello to
> try/catch/finally, the most horrible thing I've ever knew about
> programming languages) and as long as Go's standard library doesn't
> force me to use them.

It's trivially easy to roll your own exceptions on to of panic/recover (see the code I posted a couple of months back for an example of the Ruby exception idiom in Go) and whilst I'm quite keen on them I don't see any reason they should become a core language feature.

Overall I understand the perspective the article's coming from because there are all kinds of aspects of Go that I'd like to see tweaked to suit my personal needs or biases, but the point is that Go's a young language that needs to be given space to be itself. Just last night I was being asked in an online interview how I could go from Ruby to this supposed 1970s bare-metal language, but that's not really a good characterisation of Go. Sure it's not the bastard child of C and Simula that's C++ or the half-arsed Smalltalk clone we call Java: I prefer to think of it as the best bits of C + CLU without any of the 90s Enterprise OOP nonsense that's made commercial development such a nightmare.

Would I like to see a few features from Ruby or other elegant languages? Certainly. Does that mean Go isn't fit for purpose? Far from it. It's the most productive systems language I've used in thirty years of coding and I don't think that's accidental...


Ellie

Eleanor McHugh
Games With Brains
http://feyeleanor.tel
----
raise ArgumentError unless @reality.responds_to? :reason


munificent

unread,
Oct 21, 2010, 5:43:53 PM10/21/10
to golang-nuts
Hi, I'm the author of that post.

On Oct 21, 6:13 am, Corey Thomasson <cthom.li...@gmail.com> wrote:
> block arguments for example, are just another form of closures, which go has

I'm aware of that. Wasn't that clear from the article? It was under
the "syntax" section and specifically says that the parser will
desugar it to a regular anonymous function. The goal here isn't to
change semantics, it's to add some syntactic support so that scoped
behavior doesn't look so awkward. I think a little syntax can go a
long way towards encouraging something to be idiomatic.

- bob

bflm

unread,
Oct 21, 2010, 6:01:31 PM10/21/10
to golang-nuts
Let me clarify. As you can see above I quoted and commented only
*your* two sentences about nil pointers. No article involved.

munificent

unread,
Oct 21, 2010, 6:05:33 PM10/21/10
to golang-nuts
Hi, I'm the author. Before I reply, I should probably set some
context.

My expectation with this is not to change Go. I would be honestly
surprised if anything in that post ever made its way into the
language. My goals were:

1. To get a better understanding of programming languages in general
by looking at one with a critical eye. I could write similar and
hopefully equally interesting (or uninteresting) posts about Scheme,
ML, C++, or Java.

2. To try to put down in one place my understanding of some of the
things I hear people asking for in Go and why I think they want it.

I'm interested in pretty much all programming languages, so something
like this is just good recreation for me even if nothing comes of it.
Please don't take it as me saying your baby's ugly or trying to take
something away from you. One of the things that speaks very well of Go
is how strongly some people love it. It's like rye bread versus white
bread. Sure some people hate rye bread, but some people *love* it. No
one has strong feelings at all about white bread.

On Oct 21, 8:54 am, nsf <no.smile.f...@gmail.com> wrote:
> MCM112 <m...@stickyeyes.com> wrote:
> Yeah, like "I want to stick in a bunch of features to the Go", but as
> Andrew has pointed out, that won't be the Go anymore.

That sounds to me like there's some platonic ideal of Go out in the
ether than I'm not aware of. Maybe I have too prosaic a perspective on
languages. I consider them tools for humans to use, and something free
to be shaped or reshaped as needs change.

> IMHO the author
> of that article doesn't really realizes that if he will stick in
> templates _and_ operator overloading in any language it will become
> a mess. It is inevitable.

C# pulled it off. There's nothing magical at all about operator
overloading, with the exception of the assignment operation, which
*is* special. I probably wouldn't encourage the ability to overload
that. Other operators are just functions. They don't cause problems
with templates any more than regular prefix functions do, as far as I
know.

> I mean really, we have a language that has
> (almost) all these "nice" features and it is called D.

Java and C# have most of these features too.

> Being simple is a very hard property to achieve, and Go is the closest
> thing we have today.

Being simple is easy. Forth is simple. R4RS Scheme is simple. What's
very hard is being simple without just pushing the complexity onto the
users. I think simplicity is only one term in the equation and
focusing on it can miss the bigger goal. The goal is to maximize
*power*: what the language can express / its complexity. Reducing
complexity will increase power but only to the degree that it
*doesn't* reduce expressiveness.

> - Named/keyword args - I think author proposes some kind of a name
>   mangling and compile-time code generation features. Crap.

Don't hold back. Tell us how you really feel.

> - Block arguments - they're nice. But it's pure syntax sugar for
>   calling functions where the last argument has a function type.

Yup.

> - Operator overloading - makes language unreadable, especially if mixed
>   with templates. Also requires other considerations like "reference"
>   types (see C++). In theory is simple, in practice complicates a
>   language a _lot_.

Eliminate assignment from your list of operators. Do your complaints
still stand?

> - Tuples - maybe, but python experience tells me that they are a very
>   bad duplication for structs. Once you want to have a name for each of
>   the tuple's components, you're screwed. Better leave them out.

Couldn't you make that same argument about multiple returns? Shouldn't
you be returning a struct instead?

> And function overloading adds another layer of complexity and ambiguity.

Overloading is a separate issue, but Go should be in a much happier
place than C++ if it added it because it already shies away from
implicit conversions.

> - Exceptions - some people keep telling me that they are a superior way
>   of handling errors. Other people (including few very famous
>   programmers like John Carmack) tend to avoid them.

Carmack's perspective is pretty deeply tied to the needs of games.
Things like GC and closures tend to be pretty rare there too.

> - Uniform Access - who? Bertrand Meyer? OOP guy?

Yeah, what does that guy know, right?
http://en.wikipedia.org/wiki/Eiffel_%28programming_language%29
http://www.amazon.com/dp/0136291554

- bob

munificent

unread,
Oct 21, 2010, 6:26:49 PM10/21/10
to golang-nuts
> a number of the issues they raise are handled in Go by common idioms, and
> they haven't reviewed their suggestions against these idioms, at least
> not as explictly and in as much detail as I think would be required to
> be able to tell if their ideas actually provide benefit.

Sorry, I was trying to balance a fully clarifying my points while
trying to be brief enough to keep the post interesting for a more
general audience. It's already painfully long, so I didn't want to go
all language lawyer and make it worse. I can try to clarify some of my
thoughts here where the audience may be more receptive to greater
detail.

> The alternative for "scoped behaviour" in their example is defer, which
> is not discussed (well)

My main complaint with defer is the obvious one: it's not automatic.
If there's something I need to *always* remember to do, to me that
sounds like something I should automate. Scoped behavior is one way to
do that.

> and the provision of New* functions replaces
> constructors with a more explicit form; their request for constructors
> needs to be directly compared against this.

Yeah, those two sections are in rough collision. I'm certain the two
could be brought into a harmonious alignment but that would likely
take more work than is worth doing on a proposal that few would be
interested in to begin with. Overall, my goals for construction are:

1. A uniform syntax between "raw" struct creation and user-defined
initialization functions. That way going from a zero-inited type to
one that later requires initialization doesn't require you to touch
every call site that creates a struct.
2. A way to define initialization functions that get called when a
struct is created.

Almost no languages outside of dynamic ones do 1. Most OOP languages
handle 2 pretty well.

> Their section on error
> handling is very sparse on discussion of whether the explicitness of
> the handling is good or bad

I think explicitness is good where it clarifies and bad where it
obfuscates. If errors are common, then mainline code will need to
address them. In that case, error-handling should be terse and simple.
Return codes (especially with unions) are good for that. I'd use those
for things like parsing and collection lookup.

Cases where errors are rare (stack overflow, out of mem, etc.), the
error handling should not clutter up the main code paths. I like how
exceptions (and panic/recover) handle that.

> discussion of the value or lack thereof of
> banning, by convention, panic()/exception-like behaviour across package
> boundaries, another important idiom.

I don't know enough about how that works to discuss it.

> This might just be my opinion, but I don't think future-proofing, as
> they describe it, is an idiom in Go, either.

Agreed: "Right now, Go avoids this by having a culture of not future-
proofing."

They should directly
> compare the uniform access feature against a lack of any such
> future-proofing, not against speculatively wrapping everything. I feel
> this would be hard to prove reliably either way.

I consider language usability an empirical science, so I'm not sure
what "proof" would mean. There is strong evidence that reasonable
programmers *do* do this exact kind of future-proofing though:
established coding guidelines for lots of languages state things like
"always wrap fields in getters and setters".

> one of dubiousness that the cost of modifying every call site in the
> event changes happen in one place beat the costs of mitigating it
> *everywhere* pre-emptively.

I don't know which cost is worse there, but I do know that languages
that give you uniform access save you from having to pay *either*.
Properties in C# are really nice.

> their point
> about "type safety without coupling" would only apply if the error from
> their function was the only error the function it was passed to
> returned, and their function only ever returned one type of error.

I'm not sure I follow here.

> without actually suggesting an implementation so the other performance
> effects could be examined; I'd think this would require returning
> something like an interface to make the type switch possible, which
> seems like it would have significant effects of its own.

I think union types are fairly well-understood. Many languages have
them and smart implementations can create them without any need for
boxing. It should boil down to a simple struct with a type tag and the
value. In many cases union types should be a net win over multiple
returns: there's fewer values to zero-initialize.

> It is hard to begrudge them this, although I would have
> suggested not writing the review if they weren't willing to seriously
> consider what is idiomatically used as alternatives to the desired
> features,

I did my best to cover "the Go way to do things" whenever I could but
my time is limited.

> and be quiet about performance when they haven't analysed
> what their idea would cost to implement.

I don't recall making any specific performance claims, but I also
haven't described anything here that isn't well-established in other
successful languages.

> Perhaps my standards for blogs are too high, though.

My threshold for posting is:
1. Do I feel I have something interesting to say?
2. Do I feel like there's only a slim chance of putting my foot in my
mouth and looking like an idiot?

This one just squeaked by. ;)

- bob

nsf

unread,
Oct 21, 2010, 6:47:42 PM10/21/10
to golan...@googlegroups.com
On Thu, 21 Oct 2010 15:05:33 -0700 (PDT)
munificent <munifi...@gmail.com> wrote:

> Hi, I'm the author. Before I reply, I should probably set some
> context.

Hi.

Don't get me wrong too, these all are just my opinions, and who am I?
I'm no one. :)

> > IMHO the author
> > of that article doesn't really realizes that if he will stick in
> > templates _and_ operator overloading in any language it will become
> > a mess. It is inevitable.
>
> C# pulled it off. There's nothing magical at all about operator
> overloading, with the exception of the assignment operation, which
> *is* special. I probably wouldn't encourage the ability to overload
> that. Other operators are just functions. They don't cause problems
> with templates any more than regular prefix functions do, as far as I
> know.

I think C# is a mess. I've tried writing code using it's standard
library, it was one of the most horrible experiences in my programming
practice.

>
> > I mean really, we have a language that has
> > (almost) all these "nice" features and it is called D.
>
> Java and C# have most of these features too.

Then why people tend to use C and C++ for most of the systems
programming? On my linux machine I have tons of software and 0 is
written in C#/Java.

>
> > Being simple is a very hard property to achieve, and Go is the closest
> > thing we have today.
>
> Being simple is easy. Forth is simple. R4RS Scheme is simple. What's
> very hard is being simple without just pushing the complexity onto the
> users. I think simplicity is only one term in the equation and
> focusing on it can miss the bigger goal. The goal is to maximize
> *power*: what the language can express / its complexity. Reducing
> complexity will increase power but only to the degree that it
> *doesn't* reduce expressiveness.

I'm still not really sure that expressiveness is a some kind of magical
property that makes people's programs shine. Like Ruby is expressive,
Perl is expressive and I have very hard time reading programs written
in these languages. D goes to that category as well. On the other hand
things written in C and Go are very easy to read and understand.

>
> > - Named/keyword args - I think author proposes some kind of a name
> >   mangling and compile-time code generation features. Crap.
>
> Don't hold back. Tell us how you really feel.

You're proposing some kind of magic transformations behind the scenes
of a call expression. substring(from: start, to: end) becomes
substring__from__to(start, end). That's how languages like C++ and D do
function overloading. They simply encode argument types as a part of a
function name (also the process is knowsn as name mangling). It can't
be good. Have you ever tried reading crappy undocumented C++ code? I
literally spent hours trying to grep things in C++ apps like that.
That's why I call this a "crap". Overloading is a very bad idea and the
fact that some form of overloading is required for generic functions
implementation makes all this stuff even worse.

> > - Operator overloading - makes language unreadable, especially if mixed
> >   with templates. Also requires other considerations like "reference"
> >   types (see C++). In theory is simple, in practice complicates a
> >   language a _lot_.
>
> Eliminate assignment from your list of operators. Do your complaints
> still stand?

Let's be honest. When do you need operator overloading? The most
common area for that is games and 3d/2d graphics in general. These apps
tend to have a lot of math, and operator overloading makes someone's
life simpler. But the thing is, that operator overloading adds a lot of
indirections in a language. It is possible to hide complex operations
under simply looking expressions. It is bad. Don't get me wrong. It's
much better to write code in a language that has operator overloading,
but it's much harder to read it and especially debug it.
Reading/debugging has a higher priority over writing.

>
> > - Tuples - maybe, but python experience tells me that they are a very
> >   bad duplication for structs. Once you want to have a name for each of
> >   the tuple's components, you're screwed. Better leave them out.
>
> Couldn't you make that same argument about multiple returns? Shouldn't
> you be returning a struct instead?

Multiple return values are ok, because they don't introduce persistent
storage type as tuples do. It's like passing multiple arguments to a
function. Function form in general comes from math and in math
functions do return only one value, but computing world is different,
it makes sense to have multiple return values the same way as it makes
sense to be able to pass multiple arguments to a function.

>
> > And function overloading adds another layer of complexity and ambiguity.
>
> Overloading is a separate issue, but Go should be in a much happier
> place than C++ if it added it because it already shies away from
> implicit conversions.

It's not a separate issue, overloading is a very annoying issue,
because in a lot of places it struggles to get in. Want constructors?
But there are sometimes multiple ways to initialize a value, solution -
function overloading. Want generics? But is it possible to have multiple
functions with the same name but with different types as arguments,
solution - function overloading.

Yes, absence of implicit conversions helps us in a way that we don't
need to use function overloading for operator overloading as well. But
is it help, really?

Plus argument above, function overloading makes it harder to read the
code.

>
> > - Exceptions - some people keep telling me that they are a superior way
> >   of handling errors. Other people (including few very famous
> >   programmers like John Carmack) tend to avoid them.
>
> Carmack's perspective is pretty deeply tied to the needs of games.
> Things like GC and closures tend to be pretty rare there too.

Well, exceptions are used in game industry. Of course most people tend
to avoid them, especially if their game is a very computing resources
greedy. But I believe his (John Carmack) opinion has nothing to do with
that. Writing exception safe code is much more painful than good error
checking code. Maybe I'm an idiot, I don't know, but I don't get how
exceptions make my code better.

>
> > - Uniform Access - who? Bertrand Meyer? OOP guy?
>
> Yeah, what does that guy know, right?
> http://en.wikipedia.org/wiki/Eiffel_%28programming_language%29
> http://www.amazon.com/dp/0136291554

I'm sorry, it tells me nothing. I've read a lot of OOP books and I've
used a lot of OOP (I mean especially those, when an author emphasise
that fact) languages, their authors are nuts. And you're pointing
to the guy that started all this OOP mess? That's just my opinion.

>
> - bob

Bob Cunningham

unread,
Oct 21, 2010, 6:52:23 PM10/21/10
to golang-nuts

To beat this to death, let's say the linked list was implemented within
an array, where all 'pointers' were indices into the array. In this
case, 'nil' or zero is clearly a valid value. Which means the end of
the list (EOL) would need to be identified some other way. Would you
choose -1 for this implementation?

Using a 'special' value of the Next field to indicate EOL is encoding
more data into the field than just a 'next' value: It is also encoding
the existence of a next item in the list. This is more popularly known
as 'in-band signaling', where a special value in the domain of a field
is reserved to encode a meaning different from all other values in the
domain. The 'out-of-band' version of the list structure would have a
separate boolean field called EOL, and only when False would the Next
field have any meaning or relevance.

Merging two separate data items into a single field in this manner is
nothing less than a non-type-safe risky hack. This use of nil/null/zero
should be eliminated, if only to greatly reduce the use of problem-prone
ad-hoc in-band signaling. There are times when in-band signaling is
inevitable (such as over bit-serial links), but this specific use of nil
for in-band signaling within a pointer field isn't one of them.

The use of nil pointers in this manner is so entrenched in C and its
progeny that its elimination may seem an impossibility. I don't
recommend eliminating the 'existence' of nil pointers (doing so would
complicate operating system implementations), but their intentional use
should require jumping through some linguistic hurdles to make it
blindly clear what's happening.

Bottom line, the language should make it very difficult (but not quite
impossible) for a user to either intentionally or accidentally create or
dereference a nil pointer. At compile time, not just run time.


-BobC

konrad

unread,
Oct 21, 2010, 7:20:24 PM10/21/10
to golang-nuts
My take on the article:

Tupels: I really don't see how a tuple would fit in a statically typed
language. really if you have to write a type definition you might as
well name the arguments.

Keyword parameters: I believe the conventional wisdom is that
functions with a large argument count are a sign that you are doing
things wrong. Perhaps it would be better to pass a structure instead.

If your reluctant to do this because of the overhead, well guess what,
having the language do it implicitly with keyword arguments also has
overhead.

The Uniform access principle:

I'm a fan of this. The most frequent approach to this is to make
method invocations look like reading and assignment of fields. However
this is not the only way to do it.

Go's primitive for uniform access is the interface, which exposes
methods only. But when you use interfaces you still get uniform
access, and don't have to worry how the information is stored or
calculated.

One think my toy projects in go have taught me is that if in doubt use
an interface. If you don't you will end up re-factoring the code
later, and it will be a painful process.



Gordon Tisher

unread,
Oct 21, 2010, 7:24:14 PM10/21/10
to Bob Cunningham, golang-nuts
On Thu, Oct 21, 2010 at 3:52 PM, Bob Cunningham <Fly...@gmail.com> wrote:
> Merging two separate data items into a single field in this manner is
> nothing less than a non-type-safe risky hack.  This use of nil/null/zero
> should be eliminated, if only to greatly reduce the use of problem-prone
> ad-hoc in-band signaling.  There are times when in-band signaling is
> inevitable (such as over bit-serial links), but this specific use of nil for
> in-band signaling within a pointer field isn't one of them.
>
> The use of nil pointers in this manner is so entrenched in C and its progeny
> that its elimination may seem an impossibility.  I don't recommend
> eliminating the 'existence' of nil pointers (doing so would complicate
> operating system implementations), but their intentional use should require
> jumping through some linguistic hurdles to make it blindly clear what's
> happening.
>
> Bottom line, the language should make it very difficult (but not quite
> impossible) for a user to either intentionally or accidentally create or
> dereference a nil pointer.  At compile time, not just run time.

What value should the Next pointer of the last element in a linked
list have, then?

--
Gordon Tisher

Rob 'Commander' Pike

unread,
Oct 21, 2010, 7:31:10 PM10/21/10
to Gordon Tisher, Bob Cunningham, golang-nuts

17

-rob


munificent

unread,
Oct 21, 2010, 7:33:40 PM10/21/10
to golang-nuts


On Oct 21, 4:20 pm, konrad <kzielin...@gmail.com> wrote:
> Tuples: I really don't see how a tuple would fit in a statically typed
> language. really if you have to write a type definition you might as
> well name the arguments.

Take a look at ML, F#, or Haskell. It works surprisingly well.

> Keyword parameters:
> If your reluctant to do this because of the overhead, well guess what,
> having the language do it implicitly with keyword arguments also has
> overhead.

What I proposed has no overhead. It would be handled purely by the
parser.

> One think my toy projects in go have taught me is that if in doubt use
> an interface. If you don't you will end up re-factoring the code
> later, and it will be a painful process.

That seems like a good best practice, but also the exact of thing
that's annoying about languages like Java. If practices like that
become common where every type definition become a pair of struct
+interface definitions with all of the requisite copy/paste between
them, then it starts to look to me like Go has sadly been dragged back
to the same swamp that Java and C++ are in.

- bob

Alexey Gokhberg

unread,
Oct 21, 2010, 7:35:23 PM10/21/10
to golang-nuts

On 22 Okt., 00:52, Bob Cunningham <FlyM...@gmail.com> wrote:
> On 10/21/2010 03:01 PM, bflm wrote:
>
> Merging two separate data items into a single field in this manner is
> nothing less than a non-type-safe risky hack.  This use of nil/null/zero
> should be eliminated, if only to greatly reduce the use of problem-prone
> ad-hoc in-band signaling.
>

Well, the consequent application of this principle may bring some very
interesting results.

rec type intList is variant(cons: intNode; tip: null)
& intNode is structure(hd: int, tl: intList)

let reverseList = proc(list: intList -> intList)
begin
let temp := intList(tip: nil);
let done := false;
while ~done do
project list as X onto
cons: begin
temp := intList(cons: struct(hd = X(hd); tl :=
temp))
list := X(tl)
end
default: done := true
temp
end

This is how Napier88 handles simple linked lists. The type describing
a list is a union (variant) of a pointer to a list node and a special
data type "null" that has only one value "nil". The "project"
statement inside the loop works like the type switch in Go. The code
is bomb proof, but personally I don't think I would like to code
processing of simple lists this way.

munificent

unread,
Oct 21, 2010, 7:40:17 PM10/21/10
to golang-nuts
> What value should the Next pointer of the last element in a linked
> list have, then?

"Empty". Here's a complete linked list definition in SML:

datatype a' list = Node of a' | Empty

No null in sight, and you're statically prevented from trying to
access the value of a list node without first checking to see that it
isn't Empty.

- bob

Bob Cunningham

unread,
Oct 21, 2010, 7:45:35 PM10/21/10
to Gordon Tisher, golang-nuts

It doesn't matter: Use any value you want!

The 'safe' implementation is to have a boolean 'Last' field, and the
Next field is accessed only when Last is False. It is up to the
programmer to ensure that Next has been correctly set before Last is set
to False.

Ian Lance Taylor

unread,
Oct 21, 2010, 7:59:30 PM10/21/10
to munificent, golang-nuts
munificent <munifi...@gmail.com> writes:

We've fought this particular battle at great length on the mailing list
and wound up right back where we started.

E.g.:

http://groups.google.com/group/golang-nuts/browse_thread/thread/aef193652154f2c6/f498a152f025b147

Ian

Gordon Tisher

unread,
Oct 21, 2010, 8:31:42 PM10/21/10
to Ian Lance Taylor, golang-nuts, munificent

Ya know, the Unreal game engine's scripting language has the best of both worlds -- null pointers and no crashes. The compiler inserts null checks for dereferences that print an error message and then jump to the next source line :-)

On 2010-10-21 4:59 PM, "Ian Lance Taylor" <ia...@google.com> wrote:

munificent <munifi...@gmail.com> writes:

>> What value should the Next pointer of the last elem...

fango

unread,
Oct 21, 2010, 8:54:39 PM10/21/10
to golang-nuts
> 17
>
> -rob

Haha, what's the story here? Thought it was 42 which will had been
tooking mice 17½ million years to put that up and (almost) verified
it.

http://en.wikipedia.org/wiki/Answer_to_the_Ultimate_Question_of_Life,_the_Universe,_and_Everything#Answer_to_the_Ultimate_Question_of_Life.2C_the_Universe_and_Everything_.2842.29

Charles Thompson

unread,
Oct 22, 2010, 12:49:50 AM10/22/10
to golang-nuts
The paradox of choice says less is more. Go validates this premise.

I'm drawn to Go because it's easy to learn and read. Its idioms
embrace common sense. And its concurrency aspects are easy to reason
about. Michael Hoisie's blog post was great anecdotal evidence this.

Fortunately, Go's package system makes it simple to add missing
feature XYZ. Therefore, I humbly beg the creators of Go to resist the
temptations of adding generics, and other new features at the risk of
being closed minded.

Music is composed of seven notes, yet there is limitless expression
and creativity.

Finally, it would be terrible to find something that resembles this
infamous line of Scala in Go's standard lib:

implicit def TraversableBind[M[X] <: Traversable[X]] = new Bind[M] {
def bind[A, B](r: M[A], f: A => M[B])(implicit w: CanBuild[B,
M[B]]): M[B] = r.flatMap(f)(breakOut)
}


On Oct 21, 5:46 am, MCM112 <m...@stickyeyes.com> wrote:
> I recently stumbled across this article at:http://journal.stuffwithstuff.com/2010/10/21/the-language-i-wish-go-was/
>
> I thought it might be prudent to post it here to hear the views of the
> go community on some of the points raised by the author.
>
> Cheers,
>
> Ben

Paulo Pinto

unread,
Oct 22, 2010, 2:38:34 AM10/22/10
to golang-nuts
It was a nice article, and I tend to agree to a great extent to what
you wrote.

I spent some time playing around with Go, but it really does lake some
features
for the time of programming I am used to do.

I can see Go replace C as systems language, because it does offer more
features
(except enums) together with more type safety. Plus it already has a
kind of proven
history. It is really interesting how similar Go is to Alef or Limbo.

But I am not seeing Go being able to replace languages that offer much
better abstractions.
At least not for me.

I only keep coming to the list from time to time, and to the D list as
well I confess, because
I like programming languages. Compiler development was one of my main
subjects at the university.

But this is me. Surely lots of Go users are happy with Go as it is.

Christophe de Dinechin

unread,
Oct 22, 2010, 4:34:19 AM10/22/10
to golang-nuts
> What value should the Next pointer of the last element in a linked
> list have, then?

If you had read it, the original blog post offers a correct answer to
this: real union types.

With it, the pointer type has no nil value. If you need an end-of-list
value, then you create a new type that is "pointer or end-of-list".
This also makes it possible to encode correctly something like a tree
as "leaf or two pointers to tree", no nil ever happening in that tree.
That also means that you won't incorrectly confuse "end-of-list" with,
say, "error-happened", whereas with "nil" you can't make a difference
between the two pointer values, both are nil.

Many languages like Haskell can build linked list without nil very
well. Your question demonstrates that you don't know these languages,
but the original author obviously does.

Benny Siegert

unread,
Oct 22, 2010, 4:42:38 AM10/22/10
to Bob Cunningham, golan...@googlegroups.com
On Fri, Oct 22, 2010 at 01:45, Bob Cunningham <Fly...@gmail.com> wrote:
> It doesn't matter:  Use any value you want!

I choose nil.

> The 'safe' implementation is to have a boolean 'Last' field, and the Next
> field is accessed only when Last is False.  It is up to the programmer to
> ensure that Next has been correctly set before Last is set to False.

The 'safe' implementation is to have a pointer, which can be nil,
which is only _dereferenced_ when it is not nil. It is up to the
programmer to ensure that the pointer is non-nil before using it.

--Benny.

roger peppe

unread,
Oct 22, 2010, 5:10:01 AM10/22/10
to munificent, golang-nuts
On 21 October 2010 23:05, munificent <munifi...@gmail.com> wrote:
> Overloading is a separate issue, but Go should be in a much happier
> place than C++ if it added it because it already shies away from
> implicit conversions.

actually, Go doesn't shy away from implicit conversions entirely.
it has implicit conversions from constant values, implicit
conversions to interface values, and implicit conversions from nil.

any or all of these would make overloading a pain in Go.

roger peppe

unread,
Oct 22, 2010, 5:25:26 AM10/22/10
to konrad, golang-nuts
On 22 October 2010 00:20, konrad <kziel...@gmail.com> wrote:
> Tupels: I really don't see how a tuple would fit in a statically typed
> language. really if you have to write a type definition you might as
> well name the arguments.

tuples can fit just fine in a statically typed language.
Limbo, one of Go's predecessors, has tuples and they work well.
i miss them in Go - it's annoying having to think of a name for
a type when the only reason for the type is to combine two values.
and it's a shame that the return value of a function isn't first class.

but it would be awkward retrofitting them into Go, because
of the way that Go overloads on number of arguments assigned to.

unread,
Oct 22, 2010, 6:28:15 AM10/22/10
to golang-nuts
I agree. It is a very good solution to be able to use 17 in the last
element of a linked list. (I am *not* joking)

If Go would enable me to safely reason - at compile-time - about the
last element of a linked list and the value "17" optionally used
there, then I am all for it. Scrap the current Go, and welcome the new
Go that will enable me to perform this kind of reasoning!

unread,
Oct 22, 2010, 6:56:50 AM10/22/10
to golang-nuts
On Oct 22, 1:59 am, Ian Lance Taylor <i...@google.com> wrote:
> http://groups.google.com/group/golang-nuts/browse_thread/thread/aef19...
>
> Ian

The impression I got from reading (now and back then) the "Repeating
the billion dollar mistake?" discussion is that, in the minds of the
original Go creators, non-nullable pointers have little value. The
generic reason for this is that Go is *not* a language built for the
purpose of fulfilling the needs of those programmers who want to
translate their ideas into code with as much precision as possible or
want the compiler to "understand" what the programmer means.

roger peppe

unread,
Oct 22, 2010, 7:32:09 AM10/22/10
to ⚛, golang-nuts

even in languages with no nil, such as Haskell, you can still get
exceptions from using things that have an unexpected form.

e.g. head []

that's not too different from a nil pointer exception.
so even if you go the non-nil route, you may still end
up paying a good proportion of the "billion dollar"
price.

Carl

unread,
Oct 22, 2010, 8:13:11 AM10/22/10
to golang-nuts
"The Language I Wish Go Was"

<< Did you expect C++ to become such a success? ... Of course not. The
success rate for general-purpose programming languages is vanishingly
small. I knew that, and I knew that the chance of success was affected
by marketing clout, which I did not have. >>

from Bjarne Stroustrup's FAQ.

How does one measure the QUALITY of a general purpose programming
Language?

I think the usual common approach to this is to count to quantity of
features and functions. This will then be the indicator for the
quality of that piece of software. If the number is greater, then this
will indicate higher quality.

Maybe it would be an idea to start a thread about how the QUALITY of a
programming language should be measured.

What would be the positive Key Performance Indicators?

Examples positive KPI's: Compile speed, execution speed and programmer
productivity. Programmer happiness (moral). Number of programmers
using the language; Number of success story's (successful projects
done in the language); Openness and developer support also Dog-
Fooding; number of major players supporting the language. …. and so
on and so forth.

Examples Negative KPI's: bugginess, maintenance issues, security
issues, failures. Insufficient developer support, language not open,
non-availability, proprietary. These would have to be aggregated
AGAINST the positive KPI's.

These are just some ideas for initial metrics to sort of get started,
I am sure that there are many more Metrics and KPI's that would need
to be weighted and possibly measured off against each other. But its
a starting point for collecting something measurable, to get some
serious answers over time.

Actually if you look at the history of how programming languages have
been developed, the process as such if there is even one, it makes
you wonder.... :-)

....................

Florian Weimer

unread,
Oct 22, 2010, 9:01:05 AM10/22/10
to roger peppe, ⚛, golang-nuts
* roger peppe:

> even in languages with no nil, such as Haskell, you can still get
> exceptions from using things that have an unexpected form.
>
> e.g. head []
>
> that's not too different from a nil pointer exception.

(not a Go programmer)

That's just like division by zero. The problem with null pointers is
that they tend to propagate quite far from the place where they were
created, making error handling difficult (and debugging, too). I
suppose that once nullability ends up in the type system, this type of
propagation is restricted. There is a cost of making the type system
more expressive.

Here are a few examples. Go currently accepts this code:

| func print(value string) {
| fmt.Printf("%s\n", value)
| }

| var x string
| print(x)

If you introduce nullable types, that would probably not be allowed
anymore. This might not seem so bad, but you also lose the ability to
write:

| var x string
| if condition {
| x = func1()
| } else {
| x = func2()
| }
| print(x)

This might be fixed by allowing if statements in conditions. However,
this:

| var x T1
| var y T2
| if condition {
| x = func1a()
| y = func1b()
| } else {
| x = func2a()
| y = func2b()
| }
| print(x)

is more readable than the ML-style

| x, y := condition ? (func1a(), func1b()) : (func2a(), func2b())

which tears apart declarations and their values.

But if you want to allow the statement variant in the face of nullable
types, you need to track definite assignment. Even in itself, this is
not a lightweight feature. But it's fairly straightforward (except
for object creation; more on that below). Once you've that, the
following becomes illegal:

| func set(ptr *string) {
| *ptr = "hello, world"
| }

| var x string
| set(&x)
| print(x)

An ordinary pointer makes no guarantuees about write access. For this
reason, many languages with definite assignment throw out pointers
altogether. We could introduce definitely-assigned pointers. These
could only be used in functions arguments, and the compiler enforces
that on every normal execution path, the function returns only after
an assignment to the pointed-to value. And at least prior to that
assignment, the pointed-to value could not be read. This feature
would have additional interactions with function types and general
type compatibility. I fear that making these new pointer types
first-class (that is, usable outside the function on which they are a
parameter) puts us well into the range of researchy-languages.

There's another oddity: non-atomic object construction. If you
construct an object incrementally, without additional, rather complex
machinery, you'll see null values in nullable types. (Java has a
related problem: nominally constant object fields can change their
values during the execution of a constructor, and this is observal by
programs. It is sometimes abused to create cyclic
immutable-after-creation data structures.)

I think the argument above is not a mere eristic device (even though
it's structurally similar to a fake reduction ad absurdum). I'm not
entirely convinced that the bad reputation of null values comes from
their use as an error indiciator (especially in languages which lack
multiple return values). It will be interesting to see how many bug
reports for Go applications will contain hard-to-understand tracebacks
caused by a attempt to derefence a nil value. In this context, it is
a bit odd that a[x] returns a zero value and does not panic (security
concerns aside, the panic could contain a hint regarding the key,
making the error more understandable).

Russ Cox

unread,
Oct 22, 2010, 10:16:13 AM10/22/10
to ⚛, golang-nuts
> The impression I got from reading (now and back then) the "Repeating
> the billion dollar mistake?" discussion is that, in the minds of the
> original Go creators, non-nullable pointers have little value. The
> generic reason for this is that Go is *not* a language built for the
> purpose of fulfilling the needs of those programmers who want to
> translate their ideas into code with as much precision as possible or
> want the compiler to "understand" what the programmer means.

Wow. That's pretty harsh.

I don't think it's fair either. If there were a way for the type
system to help avoid nil pointer dereferences without completely
changing the style of the language, then I at least would be interested.
But as I said yesterday, that thread did not have any proposals
that both (a) worked and (b) left the language intact. It's
certainly true that if we moved to an ML-style model (ignoring
the polymorphism for now) for defining types and constructing
data, that might work (but see roger's reply). But it would
be such an invasive change that I don't think the result
would be recognizable as Go.

Russ

unread,
Oct 22, 2010, 1:21:05 PM10/22/10
to golang-nuts
On Oct 22, 4:16 pm, Russ Cox <r...@golang.org> wrote:
> > The impression I got from reading (now and back then) the "Repeating
> > the billion dollar mistake?" discussion is that, in the minds of the
> > original Go creators, non-nullable pointers have little value. The
> > generic reason for this is that Go is *not* a language built for the
> > purpose of fulfilling the needs of those programmers who want to
> > translate their ideas into code with as much precision as possible or
> > want the compiler to "understand" what the programmer means.
>
> Wow.  That's pretty harsh.

I am not sure I understand why you think it was harsh. It was a "mere
statement" about what the Go language is. If Go was built around the
idea of "enable the programmers to translate their ideas into code
with as much precision as possible", the Go language would be designed
differently. I do *not* think this is a harsh statement.

> I don't think it's fair either.  If there were a way for the type
> system to help avoid nil pointer dereferences without completely
> changing the style of the language, then I at least would be interested.

Based on previous discussions with you and others here, and based on
what I read on golang-nuts so far, I have doubts whether any proposal
to add something to the language will be accepted. Nevertheless, let
me try (I am not hoping for anything to happen):

I propose to add "#T" to the language. It means "non-null pointer to
an object of type T". The language rules for handling values of type
"#T" are basically the same as the rules for handling "*T", except
that "#T" does not accept nil. A value of "#T" can be assigned to a
variable of type "*T" at any time (see also "postponed initialization"
described below).

Without adding anything else, this already makes "#T" usable in
modelling a lot of situations.

A value of type "*T" can be converted to type "#T" by an if
expression:

| var x *T = fn()
| if x != nil {
| // x has type "#T"
| }
| // x has type "*T"

(In the following text, "invalid" means a compile-time error.)

Contrary to languages such as C++, there is no automatic conversion
from "T" to "#T":

| var x T
| var y #T = x // Invalid
| var y #T = &x // OK

The expression "y := &x" still declares a variable of type "*T". No
legacy Go code is broken by this proposal.

A value of type "#T" has no default initial value:

| var t #T // Invalid
|
| type S struct { x #T; y *T }
| var s S // Invalid
| s1 := S{ y: nil } // Invalid
| s2 := S{ x : &T{} } // OK, assuming T{} is valid

It is prohibited to use type "#T" in data structures where the value
of type "#T" is part of a cycle formed during initialization of the
data structure. In such cases, the programmer has to use "*T".

The initialization of a value of type "#T" can be postponed by using
the following pattern:

| func F(#T) { ... }
| func G(*T) { ... }
|
| type T struct { i int }
| var t #T
| var p *T
|
| t.i = 1 // Invalid
| p = t // OK, equivalent to "p = nil"
|
| if condition {
| F(t) // Invalid
| G(t) // OK, equivalent to G(nil)
| t.i = 1 // Invalid
| t = &T{}
| t.i = 17 // OK
| F(t) // OK
| G(t) // OK
| } else {
| F(t) // Invalid
| G(t) // OK, equivalent to G(nil)
| t.i = 1 // Invalid
| t = &T{}
| t.i = 23 // OK
| F(t) // OK
| G(t) // OK
| }
|
| t.i = 29 // OK
| F(t) // OK
| G(t) // OK

In other words, the compiler keeps track of whether a conditional
block of code has initialized "t" or not. In addition to this, an
*optional* extension to this idea is that the compiler is able to do
it also for functions:

| func Initializer(t *#T) {
| if condition {
| *t = &T{31}
| } else {
| *t = &T{37}
| }
| }
|
| var t #T
| println(t.i) // Invalid
| Initializer(&t)
| println(t.i) // OK

Considering that coding patterns such as this one are *not* common, I
recommend *not* to implement this extension. (It can be implemented in
future if the need arises.)

... have a nice weekend thinking about this ...

unread,
Oct 22, 2010, 1:30:29 PM10/22/10
to golang-nuts
I wans't thinking straight when I wrote "Initializer(t *#T)". Lets
make it "Initializer(t *#T init)", in order to signify that (*t) might
be uninitialized when the function starts executing.

Ian Lance Taylor

unread,
Oct 22, 2010, 2:02:40 PM10/22/10
to ⚛, golang-nuts
⚛ <0xe2.0x...@gmail.com> writes:

> I propose to add "#T" to the language.

This proposal came up before. The main sticking points are the ones you
identified.

* No valid initialization value makes it painful to use this type as a
field in a struct.

* Writing down precisely when a value of *T converts to a value of #T is
hard. It has to be written in the spec in such a way that any Go
language processor can implement it. "if x != nil" is simple enough,
but what about a switch statement, or a for statement, etc.

In general the Go team has been very conservative about language
changes, only making them when it everything is exactly right. Can
those issues be made exactly right? I don't know.

Ian

Russ Cox

unread,
Oct 22, 2010, 2:14:14 PM10/22/10
to ⚛, golang-nuts
My apologies for misjudging the tone of your comment.

Having types that change based on control flow analysis
seems like a pretty big complication. Dynamic interface
checks introduce a new variable instead, for example,
but that's quite a bit less useful in the kinds of examples
you gave. Worse, if the value being analyzed is not a
local variable (or a closure has been created that can
access it), then the analysis gets even more complicated.

Also, there are cases that don't involve local variables
that are worth considering. For example, how do you
add an element to a []#T?

To me it seems like the validity of the zero value is so
ingrained in the rest of Go that a type for which you cannot
declare the zero value would be close to useless (in Go).
All the complication comes from trying to work around that.

Russ

unread,
Oct 23, 2010, 12:32:57 PM10/23/10
to golang-nuts
On Oct 22, 8:02 pm, Ian Lance Taylor <i...@google.com> wrote:
> ⚛ <0xe2.0x9a.0...@gmail.com> writes:
> > I propose to add "#T" to the language.
>
> This proposal came up before.  The main sticking points are the ones you
> identified.
>
> * No valid initialization value makes it painful to use this type as a
>   field in a struct.

I am not sure about that (that it is so painful to initialize #T
fields in a struct). In a "sufficiently large number of cases", using
#T in structs is straightforward. In complex cases (i.e: cycles in
initialization, hard-to-analyze-by-the-compiler control-flow and
concurrency patterns, and transporting type-information across
"abstraction boundaries" (functions, closures, etc)), I agree it is
painful to use #T. In those cases, the programmer should use "*T".
However, using "*T" instead of "#T" does not make the underlying issue
magically disappear in the complex cases - it just makes them
invisible from the viewpoint of the compiler, the programmer *still*
has think about the code and make sure the code works correctly.

I understand that #T brings about some "pain". For example, moving a
piece of code to its own function might cause the compiler to fail to
analyze the initialization pattern of some variable "t" of type "#T" ,
and as a consequence of this, the programmer is forced to change the
type of "t" to "*T". When this happens, the programmer "meets the
compiler's relative stupidity in flesh", and as a consequence, some
programmers may start thinking the compiler is a jerk or may even
begin to think the compiler is preventing them from doing the work
("compiler is getting in my way").

An absolute horror is having a 3rd-party library which defines "struct
S { Field #T ... }" and you want to use "S" in an initialization cycle
or in code the compiler is unable to understand - this case is really
painful. It can be "solved", by forbidding any public fields of a
structure to have type #T.

> * Writing down precisely when a value of *T converts to a value of #T is
>   hard.  It has to be written in the spec in such a way that any Go
>   language processor can implement it.  "if x != nil" is simple enough,
>   but what about a switch statement, or a for statement, etc.

Technically, "switch" and "for" statements are reducible to "if"
statements (+goto), so I don't worry about that. It is also doable to
implement a compiler that is able to understand more general boolean
expressions, such as "if (x != nil) && (i < 100) { ... }".

The code "if x != nil" is quite hard by itself. There are some subtle
problems the compiler would have to able to solve, such as:

| var t *T = fn()
| var p *T = t
| if x != nil {
| var y1 #T = x // OK
| x = p
| var y2 #T = x // Invalid
| }

In other words, in the if-true part of the if statement, assignment "x
= p" is allowed, but it resets the type of "x" from "#T" back to "*T".
Another negative example is when before the "if" statement, you
execute a code like "someFunction(&t)":

| var t *T = fn()
| someFunction(&t)
| if x != nil {
| var y1 #T = x // OK
| anotherFunction()
| var y2 #T = x // Invalid
| }

A type-switch may seem problematic at first, but (maybe) it is not
that hard:

| var t interface{}
| switch t.(type) {
| case #T:
| }

The meaning which I would propose is that "case #T" is (informally
speaking) equivalent to "(case *T) and (t != nil)".

> In general the Go team has been very conservative about language
> changes, only making them when it everything is exactly right.  Can
> those issues be made exactly right?  I don't know.
>
> Ian

Whether the #T-related issues can be made exactly right: I think they
cannot. It seems than in the case of #T (i.e: non-nullable type) there
always exists a line the compiler won't be able to cross. The number
of cases a compiler would fail to understand is infinite.

Ian Lance Taylor

unread,
Oct 23, 2010, 12:47:54 PM10/23/10
to ⚛, golang-nuts
⚛ <0xe2.0x...@gmail.com> writes:

> An absolute horror is having a 3rd-party library which defines "struct
> S { Field #T ... }" and you want to use "S" in an initialization cycle
> or in code the compiler is unable to understand - this case is really
> painful. It can be "solved", by forbidding any public fields of a
> structure to have type #T.

When we get to this kind of compromise, then to me the idea doesn't seem
to fit very well with the general Go language.


>> * Writing down precisely when a value of *T converts to a value of #T is
>>   hard.  It has to be written in the spec in such a way that any Go
>>   language processor can implement it.  "if x != nil" is simple enough,
>>   but what about a switch statement, or a for statement, etc.
>
> Technically, "switch" and "for" statements are reducible to "if"
> statements (+goto), so I don't worry about that. It is also doable to
> implement a compiler that is able to understand more general boolean
> expressions, such as "if (x != nil) && (i < 100) { ... }".

I'm not concerned about the compiler. I'm concerned about the language
spec. Again, it's important that every Go language processor implement
the same rules about converting values of type *T to values of type #T.
It's absolutely not OK if one tool does the conversion in one case when
it can prove that it is OK if another tool can not make the same proof,
because then the Go code becomes mysteriously unportable between
implementations. This rule has to be in the language spec, and it has
to be clear and unambiguous and implementable by all language processors
including interpreters and standalone type checkers.

Ian

unread,
Oct 23, 2010, 1:00:48 PM10/23/10
to golang-nuts
On Oct 22, 8:14 pm, Russ Cox <r...@golang.org> wrote:
> My apologies for misjudging the tone of your comment.
>
> Having types that change based on control flow analysis
> seems like a pretty big complication.  Dynamic interface
> checks introduce a new variable instead, for example,
> but that's quite a bit less useful in the kinds of examples
> you gave.  Worse, if the value being analyzed is not a
> local variable (or a closure has been created that can
> access it), then the analysis gets even more complicated.

Yes, that is true.

> Also, there are cases that don't involve local variables
> that are worth considering.  For example, how do you
> add an element to a []#T?

Obviously, "make([]#T, 10, 100)" is invalid code, because #T has no
initializer. Expression "a := make([]#T, 0, 100)" is valid, but (as
you mentioned) it is useless without further additions to the language
(you cannot do "a = a[0:3]"). However, there might be an option of how
to deal with this:

| a := make([]#T, 0, 100)
| var t #T = fn()
| a = a ++ t // Append 1 element
| ...
| a = a -- 1 // Shorten "a" by 1 element

The expression "a = a ++ t" (or something with a better syntax) is the
type-safe equivalent of "a = a[0:len(a)+1]; a[len(a)-1] = t". An
extension to this concept, enabling to append more than one element,
would be:

| a := make([]#T, 0, 100)
| var b []#T = fn1()
| var t #T = fn2()
| a = a ++ b

An other option is to use generator-expressions (as available in some
functional languages) to do the type-safe initializations and appends,
but it seems to me this approach does not fit Go's programming style.

... I agree with all those people who are saying that introducing "#T"
into the language would be a non-trivial undertaking.

unread,
Oct 23, 2010, 1:18:29 PM10/23/10
to golang-nuts
On Oct 23, 6:47 pm, Ian Lance Taylor <i...@google.com> wrote:
Yes, you are right. I guess the only way of how to approach this is to
write an actual extended-Go compiler and the corresponding language
specification. Then implement new code and also port existing code, in
order to ascertain whether it is a good design or not.

unread,
Oct 24, 2010, 4:22:27 AM10/24/10
to golang-nuts
On Oct 23, 6:47 pm, Ian Lance Taylor <i...@google.com> wrote:
> ⚛ <0xe2.0x9a.0...@gmail.com> writes:
> > An absolute horror is having a 3rd-party library which defines "struct
> > S { Field #T ... }" and you want to use "S" in an initialization cycle
> > or in code the compiler is unable to understand - this case is really
> > painful. It can be "solved", by forbidding any public fields of a
> > structure to have type #T.
>
> When we get to this kind of compromise, then to me the idea doesn't seem
> to fit very well with the general Go language.

After giving it some though, it seems a good option would be to:

- forbid "#T" in case "T" is an interface, and

- make the compiler detect *potential* initialization cycles in
structs by means of static analysis. If such a cycle is discovered,
the compiler reports an error, hinting to the programmer to use "*T"
to break the initialization cycle. The fact that Go packages are
forced to form a tree ensures that it is sufficient to perform the
static analysis within a single compilation unit (i.e: if the command
line is "8g a.go b.go c.go" the compilation unit consists from the 3
files "{a,b,c}.go")

This option fits the following criteria:

- it is simple and clear enough to be in the language specification,

- it can be implemented in an unambiguous manner,

- it does not require global code analysis,

- after somebody writes a library then there will be no #T-related
"surprises" when the library is used

Russ Cox

unread,
Oct 24, 2010, 10:06:44 AM10/24/10
to ⚛, golang-nuts
> - forbid "#T" in case "T" is an interface, and

Why? It seems like that's one place you'd really want it.
Most functions that take interface values crash if you pass
a nil value. For example, io.ReadFull.

Russ

Steven

unread,
Oct 24, 2010, 3:50:44 PM10/24/10
to r...@golang.org, ⚛, golang-nuts
Except that its the interface that's nil, not a pointer to it. A non-nil reference to a nil interface is still non-nil. I think that the scope of this, in order for it to have any meaning, would be "how do I demand that a reference type is non-nil?" Thus, it would have to apply to slices, chans, funcs, maps and interfaces, as well as pointers. I see no reason pointers should get special treatment. I think people just have too much stigma around them.

unread,
Oct 25, 2010, 4:46:50 AM10/25/10
to golang-nuts
A Go interface is a special kind of a pointer (pointer to particular a
set of methods). This pointer can be nil. So, "interface X {...}; var
x X" is conceptually the same thing as "struct S {...}; var x *S".
Therefore, "interface X {...}; var y #X" is conceptually the same
thing as "struct S {...}; var y #*S".

It is impossible to locally statically decide whether a type-graph
involving an interface contains a cycle. Due to the nature of
interfaces, such type-graph is always incomplete.

To enforce the argument of type "io.Reader" passed to "io.ReadFull" is
not nil, you would have to introduce a new syntactic construct
enabling the programmer to define non-nil interface variables. Such as
"func ReadFull(r &Reader, ...)". (An alternative is to change the
meaning of "#X" from [non-nil pointer to interface X] to [non-nil
interface X]. This alternative might work OK, because the former
meaning has very little use in real-world programs. The problem is
that it would introduce non-uniformity into the language.)

Summary:
- #T means: a non-nil pointer to any type T
- &X means: a non-nil interface X

-----

There is another (invasive) alternative: change the meaning of the
code "interface X {...}; var x X" to mean that the variable "x" is a
non-nil interface:

| struct S { ... }
| interface X { ... }
| var x X = &S{...} // OK
| var x X = nil // Invalid
| var x X // Invalid

This change would break existing Go code. There would have to be a new
syntax which would allow such codes to retain their original meaning,
for example:

| interface X { ... }
| struct S {
| x nil X
| }
| var x nil X = nil // OK

-----

If there is a real chance for non-nil pointers to be incorporated into
the Go language, I think it should be performed in two steps, the 2nd
step is optional:

1. Introduce #T

2. Introduce non-nil interfaces, but only after #T is proven to work

gnash

unread,
Dec 12, 2010, 12:02:39 PM12/12/10
to golang-nuts
I agree. Let Go be the first imperative language to make it easier to
avoid null dereferences. At the very least a convention for non-
nullable variables.
Reply all
Reply to author
Forward
0 new messages