That used to be in the language but was dropped when the semicolon insertion rules went in. You need the + to be able to span lines, and if you can't span lines operatorless concatenation is close to pointless.
> PROPOSAL 2: Implicit widening within types for assignments
> Experiencing frustrations with strict type equivalence. Appreciate clarity of meaning, simplicity of coercion-free language design, and avoidance of "invisible" type/coercion/operator bugs. Despite these, want at least a single liberty here, but prefer a series of slightly expanded versions as well:
...
The proposed rules are too specialized. If such rules don't work for int, they're not worth having. In any case the clarity of Go's strictness is worth the occasional conversion. A huge class of bugs is simply gone, and a huge piece of tricky language in the specification never needed to be written.
> PROPOSAL 3: Bit slices
I bet very few people would use them. They wouldn't carry their weight.
> PROPOSAL 4: First-class radix 2 constants and formatted output
> The fmt package is fantastic. The "%T" format is clever. Also, the "%#o" and "%#x" print numbers in a designated base that can then be read back in as programs or data ("032" and "0x3e" respectively.) We can also print numbers in binary, but there is no "%#b" to print them with a base specifier ("0b1101") and no support in the Go spec for binary literals. There can be times when this is the clearest way to specify a number and given the role of binary in computing, seems not too much to ask of compiler and library.
Really? Base 2 constants just don't come up much. If anything, a case could be made for ditching octal and going to a more general form like 8r15 (r for radix), but it's not worth the turmoil to switch. We went over this some time ago and decided to leave it as is, familiar and sufficient for almost everything.
> PROPOSAL 5: Operand-derived "integer size" for result.
This is another one where you'd lose too much clarity in expression types. I think Nigel's right that you're using the language for a specialized purpose and want more help, but anyone using any language for a specialized purpose wants their own version of specialization.
> PROPOSAL 6: multiple valued built-in operations
> The multiple return value logic is sublime. I have used it to greatly improve code clarity vs. passing secondary return placeholders by pointer in C. But, there is no flexibility about ignoring secondary result values returned from function calls ("tuple assignment" in the sense of silently ignoring rather than using "_") Setting this aside for a moment, it would be an interesting subtlety to have tuple valued versions of basic arithmetic that work this way:
>
> var a, b int
> sum, carry := a+b
> difference, borrow := a-b
> product, highProduct := a*b
> quotient, remainder := a/b // aka, divMod
This has come up before too, and again although it's cute it doesn't seem worthwhile. Another instance of domain-specific specialization. Although arguably closer to the spirit of the language, I suspect you'd see these uses very rarely.
> PROPOSAL 7: integer exponentiation operator in Go
Ditto. Other than base 2, which we have, I can't think of a single time I've wanted integer exponentiation.
> PROPOSAL 8: Optional UTF-8 source symbols
This has come up several times and has been rejected as leading to too much cuteness, visual ambiguity, and worries about presentation. I still see mangled UTF-8 daily.
> PROPOSAL 9: Swap (actually, an observation more than a suggestion)
> Since you have ":=" I wanted to remind/observe that Dijkstra had used ":=:" in writings to indicate an interchange. Your "a,b = b,a" is his "a :=: b". Just saying, in case it appeals to you. It would work with tuples naturally, "a,b,c :=: x,y,z" and bit slices:
>
> m[0:8] :=: m[8:16] // swap bytes: short, terse, clear.
Cute but unnecessary and also a little misleading, since it has nothing to do with :=.
> PROPOSAL 10: Operator overloading.
A long design discussion about this general topic is on hold until and if the generic types question is resolved.
> PROPOSAL 11: Computable booleans
Back to C's idea of a boolean. Easy to work around with a function or map in the rare cases an if feels wrong.
> PROPOSAL 12: Rename complex number sizes
I fought hard for this at the time but was voted down because these names are well known from existing languages. I'm still not sure it was the right decision but I don't want to revisit it. A decision was made.
Sorry to be so negative. It's interesting how many of your suggestions have been thought through before.
-rob
I understand that scientific programming is a niche market, and not
one that is really targeted by the go language, but it's a pretty
large niche, and I think it'd be worth considering the addition of an
exponentiation operator. You may not need it very often, but when you
need it, it *really* helps to have it. It certainly seems as
generally useful as the bitwise operators that go already has. I'd
hate to see them removed from the language, but I also haven't yet
needed them in go.
--
David Roundy
It's easy enough to write a function to do integer exponentiation.
The only advantage I see to adding it to the language is so that the
compiler can optimize exponentiation when the power (right) operand is a
constant. And that does not seem to me to be a compelling advantage.
It's easy enough to optimize those cases by hand in the very few cases
where they matter. Or just wait until the compiler can inline simple
functions, and then you'll get the same optimization anyhow.
Further, it would be unreasonable to add the exponentiation operator
only for integers. So then we need to ask what about the relationship
of the type of the two operands. Exponentiation is like the shift
operator in that there is no necessary relationship between the two
operands. It is unlike shift in that a floating point operand on the
right makes sense. Adding the operator but only permitting integer
powers would lead directly to a request for supporting floating point
powers. Supporting floating point powers has no compilation advantage
at all--it would be compiled directly into a call to math.Pow, only we
would have to have a different version of math.Pow for each integer,
floating, and complex type.
So I don't see this as being all that simple. We would be introducing
considerable complexity in order to save a function call and some
compiler optimization.
Ian
That used to be in the language but was dropped when the semicolon insertion rules went in. You need the + to be able to span lines, and if you can't span lines operatorless concatenation is close to pointless.
On 13/07/2011, at 5:25 PM, Michael Jones wrote:
>
> PROPOSAL 1: Implicit concatenation
> Combining static strings: merge adjacent strings without "compile time '+' operator"
> v := "aaa" "bbb" means v := "aaabbb", where space between strings may include newline
>
> This can be implemented in the lexer (just saw quoted string, peek ahead to quoted string … wait a minute, that's one big string!)
...
> PROPOSAL 2: Implicit widening within types for assignments
> Experiencing frustrations with strict type equivalence. Appreciate clarity of meaning, simplicity of coercion-free language design, and avoidance of "invisible" type/coercion/operator bugs. Despite these, want at least a single liberty here, but prefer a series of slightly expanded versions as well:
The proposed rules are too specialized. If such rules don't work for int, they're not worth having. In any case the clarity of Go's strictness is worth the occasional conversion. A huge class of bugs is simply gone, and a huge piece of tricky language in the specification never needed to be written.
Assignment between different sizes of the a conceptual type (uint, int, float, complex) is allowed only when universally value preserving, that is when widening the source to match the equal or greater width of the destination. In the case of the ambiguously-sized int and float, the universally value preserving representation is the largest such size.
> PROPOSAL 3: Bit slices
I bet very few people would use them. They wouldn't carry their weight.
Really? Base 2 constants just don't come up much. If anything, a case could be made for ditching octal and going to a more general form like 8r15 (r for radix), but it's not worth the turmoil to switch. We went over this some time ago and decided to leave it as is, familiar and sufficient for almost everything.
> PROPOSAL 4: First-class radix 2 constants and formatted output
> The fmt package is fantastic. The "%T" format is clever. Also, the "%#o" and "%#x" print numbers in a designated base that can then be read back in as programs or data ("032" and "0x3e" respectively.) We can also print numbers in binary, but there is no "%#b" to print them with a base specifier ("0b1101") and no support in the Go spec for binary literals. There can be times when this is the clearest way to specify a number and given the role of binary in computing, seems not too much to ask of compiler and library.
This is another one where you'd lose too much clarity in expression types. I think Nigel's right that you're using the language for a specialized purpose and want more help, but anyone using any language for a specialized purpose wants their own version of specialization.
> PROPOSAL 5: Operand-derived "integer size" for result.
> PROPOSAL 6: multiple valued built-in operationsThis has come up before too, and again although it's cute it doesn't seem worthwhile. Another instance of domain-specific specialization. Although arguably closer to the spirit of the language, I suspect you'd see these uses very rarely.
> The multiple return value logic is sublime. I have used it to greatly improve code clarity vs. passing secondary return placeholders by pointer in C. But, there is no flexibility about ignoring secondary result values returned from function calls ("tuple assignment" in the sense of silently ignoring rather than using "_") Setting this aside for a moment, it would be an interesting subtlety to have tuple valued versions of basic arithmetic that work this way:
>
> var a, b int
> sum, carry := a+b
> difference, borrow := a-b
> product, highProduct := a*b
> quotient, remainder := a/b // aka, divMod
> PROPOSAL 7: integer exponentiation operator in GoDitto. Other than base 2, which we have, I can't think of a single time I've wanted integer exponentiation.
> PROPOSAL 8: Optional UTF-8 source symbolsThis has come up several times and has been rejected as leading to too much cuteness, visual ambiguity, and worries about presentation. I still see mangled UTF-8 daily.
Cute but unnecessary and also a little misleading, since it has nothing to do with :=.
> PROPOSAL 9: Swap (actually, an observation more than a suggestion)
> Since you have ":=" I wanted to remind/observe that Dijkstra had used ":=:" in writings to indicate an interchange. Your "a,b = b,a" is his "a :=: b". Just saying, in case it appeals to you. It would work with tuples naturally, "a,b,c :=: x,y,z" and bit slices:
>
> m[0:8] :=: m[8:16] // swap bytes: short, terse, clear.
> PROPOSAL 10: Operator overloading.
A long design discussion about this general topic is on hold until and if the generic types question is resolved.
> PROPOSAL 11: Computable booleans
Back to C's idea of a boolean. Easy to work around with a function or map in the rare cases an if feels wrong.
I fought hard for this at the time but was voted down because these names are well known from existing languages. I'm still not sure it was the right decision but I don't want to revisit it. A decision was made.
> PROPOSAL 12: Rename complex number sizes
Sorry to be so negative. It's interesting how many of your suggestions have been thought through before.
-rob
Michael T. Jones
Chief Technology Advocate, Google Inc.
1600 Amphitheatre Parkway, Mountain View, California 94043
Email: m...@google.com Mobile: 650-335-5765 Fax: 650-649-1938
Organizing the world's information to make it universally accessible and useful
David
--
David Roundy
Thanks for your thoughts. Almost all of them have come up before,
but we don't yet have an established place to look for design rationales
and removed or declined features. We have been meaning to make one.
In general, we are trying to keep Go small, easy to know in full.
Many of your proposals are things that have already been considered
or done but were deemed not to carry their weight. It is all too easy
to build a language of many individually useful features that ends up
being an unreasonable amount to learn, with the effect that the features
go unused. (Witness C++, where everyone knows and programs in
a different subset.)
Another important point is that what makes sense in a small program
may not make sense in a large one. All of your proposals make
sense in a 1000-line or even 10,000-line program written by a single person
or small group, where everyone involved can keep the whole thing in
his or her head. Haskell and ML seem to me examples of great languages
that target that spot, whether intentionally or not. Python is another
language that targets it.
Go, in contrast, targets much much larger programs, written by many
people, where no one involved has the whole thing in his or her head.
In that situation, it becomes more important to keep the language small,
so that everyone will know all the syntax they see, and to keep the rules
simple, so that everyone can predict what code does and the compiler
can catch mistakes.
Russ
After type T int32, you can't even pass a T where an int32
is expected. Our experience has been that the clarity here is
worthwhile and catches bugs. Being able to pass it where an
int64 is expected would be truly surprising.
> PROPOSAL 5
> Maybe so. What actually motivated me was the assembly output. Not that it
> was lightly optimized, because compilers improve, but because the code
> generator understood that I wanted to do a more expensive division than I
> needed and that was because I was forced to make everything bigger than
> necessary because of the strict type equality rules.
I think the answer here is "compilers improve" or "Go lets you
be clear about which division you want" or both. Note that you
can even ask for a 8-bit division if you really want one, something
that is impossible in C because of the cleverness of the usual
arithmetic conversions.
> PROPOSAL 7
> I want it all the time in Go's constant construction because 2**n is
> mathematics and 1<<n is C. Even more when I want 3**n or 10**n.
You justified PROPOSAL 6 by saying it is how computers work.
But this is not how computers work. x<<y is a hardware instruction.
3**n is not. x**y even less so. It seems strange to single out one
such algorithm for implementation by the compiler.
> PROPOSAL 9
> True, but then Wirth and Dijkstra beat you to ":=" by 30 years so it was not
> misleading when ":=:" was a symmetric version of ":="
Saying that Dijkstra "beat" Go to ":=" makes it sound like
the notations mean the same thing, but they don't.
Dijkstra's ":=" is more like Go's "=".
> PROPOSAL 12
>
> Sorry that this argument was lost. Natural intuitions about element
> capabilities and equivalences will never be clear to the programmer the way
> it is now. Sigh. Octonion2048 here I come.
It will be clear to Fortran, Mathematica, and numpy programmers.
The intuitions don't go as far as you claim.
A float32 can't accurately hold an int32.
Russ
Proposal 1: Even if string concatenation would work (and it doesn't
with the semicolon insertion rules in place), it's an extra rule in
the spec for something that can be done with + (and w/o breaking a leg
in the process). It's in the spirit of Go to leave away redundant
features.
Proposal 3: Translating individual bit slice operations into machine
code generates quite a bit of code _in general_, and even a very good
compiler may not be able to optimize it as well as one can do with
explicit shifts and masks, especially if it's more than one bit slice
operation in an expression. If it's not performance critical but bit
slices are frequent, it's easy to write some functions (and eventually
they may even get inlined). As Russ pointed out, this may be useful
for certain kinds of codes, but it's not carrying it's weight in large
programs. Keepinh the language small is more important.
Proposal 4: 0b1010 constants seem fine, but it's only a question of
time until somebody wants radix 3 or 4 or what have you. Hex values
are the mechanism to get to the bit representation; octal literals are
only there for historic reasons (os.Open). If anything, one would want
a general radix notation. I have proposed in the past:
<radix>x<mantissa>, where radix 0 has the special meaning 16 to be
compatible with the time-honed C notation. Then one could write:
2x1010 (for 0b1010), but also 3x101 (base 3), 10 (or 10x10, base 10),
0xa (or 16xa, base 16); and one could get rid of the octal notation in
turn. One argument against this (and other general radix notations) is
that the octal prefix 0 would not be special anymore (so 010 means 10
not 8), which might become a source of subtle bugs when quickly
porting existing C code.
Proposal 6: Multi-valued operators have been proposed in the past, and
I think it would be very nice to have (and even natural), but
unfortunately it breaks down for division: For a/b one actually wants
an a that is twice the size of b. One could say that in the form q, r
:= a/b, a is of a type with twice the size as b, but it gets
complicated (q may be twice the size, too).
- gri
> PROPOSAL 5
> Maybe so. What actually motivated me was the assembly output. Not that itI think the answer here is "compilers improve" or "Go lets you
> was lightly optimized, because compilers improve, but because the code
> generator understood that I wanted to do a more expensive division than I
> needed and that was because I was forced to make everything bigger than
> necessary because of the strict type equality rules.
be clear about which division you want" or both. Note that you
can even ask for a 8-bit division if you really want one, something
that is impossible in C because of the cleverness of the usual
arithmetic conversions.
> PROPOSAL 7
> I want it all the time in Go's constant construction because 2**n isYou justified PROPOSAL 6 by saying it is how computers work.
> mathematics and 1<<n is C. Even more when I want 3**n or 10**n.
But this is not how computers work. x<<y is a hardware instruction.
3**n is not. x**y even less so. It seems strange to single out one
such algorithm for implementation by the compiler.
> PROPOSAL 9
> True, but then Wirth and Dijkstra beat you to ":=" by 30 years so it was notSaying that Dijkstra "beat" Go to ":=" makes it sound like
> misleading when ":=:" was a symmetric version of ":="
the notations mean the same thing, but they don't.
Dijkstra's ":=" is more like Go's "=".
> PROPOSAL 12
>It will be clear to Fortran, Mathematica, and numpy programmers.
> Sorry that this argument was lost. Natural intuitions about element
> capabilities and equivalences will never be clear to the programmer the way
> it is now. Sigh. Octonion2048 here I come.
The intuitions don't go as far as you claim.
A float32 can't accurately hold an int32.
Russ
> Actually this relates to my motivation for making the list.
>
> :
> var a uint32 = 99999
> var b uint8 = 3
> c := a/b
> :
>
> 6g test.go
> test.go:24: invalid operation: a / b (mismatched types uint32 and uint8)
>
> The language prevents me from saying "I want to divide a 32-bit uint by an
> 8-bit uint" because I must code it as a/uint32(b) so it always sees
> 32-bit/32-bit or in my case 64-bit/64-bit when the divisor was 10.
The language prevents you from saying it explicitly, but nothing
prevents the compiler from optimizing a/uint32(b) into a 32/8 division
instruction, if such an instruction exists.
Ian
The advantage to adding it to the language that I see is that
a*x**3 + b*x**2
is clearer than
a*math.Pow(x, 3) + b*math.Pow(x, 2)
As Michael initially said, "the code above is what compilation is all
about: make the expression clear in source and do the good translation
automatically."
yours,
Bobby
> Hello Everyone,
>
> I'd like to throw some support behind proposal 2, the automatic
> widening of types. I understand that there is a resistance to
> automatic type conversion (which I generally support), but there is
> something kind of ridiculous in the following code:
>
> a := int32(1)
> b := int8(1)
> c := a + int32(b)
It may look ridiculous in this case (I would say it looks clear, but let's take your adjective) but it's not nearly so risible when those assignments are hidden inside function invocations, behind interfaces, inside assignments of composite types, and so on. The rule is the rule because it makes the tricky stuff not tricky any more; the cost is that some simple examples get a little noisier. That's a price worth paying.
-rob
I understand your concern, but I'm having trouble imagining a case
where the resulting code would be incorrect. I would think that a
situation where Go can make programmers' jobs easier without
sacrificing correctness would be a win. I suppose this is what I
meant by pedantic - emphasizing a detail that (in most cases) doesn't
help the programmer evaluate the correctness of the code.
Also, I'm not certain that I follow your concern about the assignments
being hidden. You either have access to the function body (or
interface), and so the behaviour is visible. Otherwise, that piece of
code is a black box, so the details of the cast are hidden whether
explicit or not.
Robert
P.S. In case my original email came across as overly critical, I like
Go very much. I've switched to using it for most of my personal
projects. Thank-you.
Please see my response to proposal 2 from earlier in this thread,
reproduced below. Inserting automatic widening means giving
up the "named types are not the same" rule, which makes it
pretty much a non-starter.
Russ
> PROPOSAL 2
> Hmm... What is tricky about assignments?
After type T int32, you can't even pass a T where an int32
is expected. Our experience has been that the clarity here is
worthwhile and catches bugs. Being able to pass it where an
int64 is expected would be truly surprising.
I understand that there is a resistance to
What you write as
a = b
characterizes this as one case in a compiler: the assignment statement. But there are many more:
a += b
f(b)
T{b}
interfaceVal = b
and so on. And they're not all in code generation either, particularly in Go, which does lots of interesting things in libraries such as JSON, XML, gobs, .... Each one must be thought about, reasoned about, maybe argued about, and then implemented. And that leaves aside the profound concern about how the change affects other parts of the language, parts we often don't realize are affected until implementation of some feature is begun.
Is this feature (any feature) worth the cost of fixing all those places, testing them, documenting them, making them all consistent? On multiple platforms with multiple compilers and interpreters? Worth the cost of maintenance as things develop?
Is it worth changing the simple arithmetic compatibility rule today (exact match or error) into something more subtle? Is it worth taking a step away from exactitude towards more general assignment compatibility rules? What comes next? If we added this feature; from that step it would be only one more step to some new proposal that again seems reasonable in isolation, when reduced to the simplest case.
In short, your characterization of what's required misses much of the deep stuff that factors in. Without paying any attention to the specific proposal, the question is always, "does the benefit of the feature outweigh the cost of implementation, reliability, documentation, and maintenance?" And what precedent does it set? When the topic is arithmetic on basic types, the bar must be set very high.
Now, apply this general discussion to all the different feature requests we get and the complexities multiply. A feature can't just be reasonable, it needs to do more than carry its weight. It needs to make things much better than the cost of implementing and maintaining it, factoring in how frequently it will be used and how it interacts with the rest of the language.
In a language in which mixed-radix arithmetic was the norm, the rule rather than the exception, things might be different. For you it may well be the norm, and for that I apologize for the extra typing you must do. But for most Go programmers it's probably a minor annoyance at most, and always clear in meaning and result.
So the "harm" (your word, not mine) is primarily philosophical. Even if this feature had perfect technical merits on its own, I'd be very reluctant to let it in because of the knock-on effects. One of the many things in Go that I think we got absolutely right was strictness on arithmetic type compatibility and I see no reason to change it. Perhaps I write very different code from you, but the freedom of eliding the occasional cast simply isn't worth the loss of clarity or the precedent that modifying these rules would set.
In short, each feature is reasonable on its own, but that is not sufficient to make it a good idea.
-rob
I imagine a scenario like this... Suppose you have
var (
a uint32
x, y uint8
)
then these two things mean different things:
a = uint32(x) + uint32(y)
a = uint32(x + y)
Which of those should "a = x + y" mean? Presumably the latter.
However, suppose that these were fields in distant types, rather than
local variables declared right next to each other:
foo.U = bar0.V + bar1.V
and originally, both the U and V fields are uint32. Now, a year after
that code was written, a different programmer notices that V values
are always < 256, and changes V's type from uint32 to uint8. In Go,
the assignment to foo.U then becomes a compile-time error. Under your
proposal, it is silently promoted, and means uint32(x + y) instead of
the correct refactoring uint32(x) + uint32(y). This could lead to
subtle bugs.
Sure, comprehensive tests could pick this up, but it's better to catch
this at compilation time. Static typing is there for a reason.
Explicit casts are a feature, not a bug.
> if (a is built in type and b is built in type) && // i.e. not MyIntegerType
Today there is nothing special about the builtin types (except that
untyped constants are converted to int/float64/complex128 if there is no
other type to convert them to). The builtin types are named types like
user defined named types, they just happen to be predefined.
(Admittedly they are predefined in a magic way.)
All language decisions are a tradeoff. Making the language more complex
is a drawback. Adding hidden type conversions, even ones that are safe,
is a drawback. These need a compensating benefit. Most Go code seems
to look OK even though type conversions are required to mix different
types. What is the benefit that significantly outweighs the drawbacks?
Why would much code ever use this feature?
> (if a.familyName == b.familyName) && // i.e., both int or both float
By the way, I assume that int and uint count as different families in
your proposal.
Ian