There are several technical advantages to the types-to-the-right style. For one, it's often easier to write a parser for: having an explicit let or val keyword makes it immediately obvious to a parser without lookahead that the statement in question is a declaration, and not an expression. This is less of a problem in languages like Java, where the grammar of types is simpler, but in C and C++, if you begin a line with
foo * bar
then it's not yet clear to the parser (and won't be until after more tokens are observed) whether this is a variable declaration for bar or a statement multiplying foo and bar. This isn't a problem for name-then-type syntaxes.
On a related note, it's also often advantageous for functions to indicate their return type last, as well, especially when the return value of the thing might be a function of an earlier argument. There are plenty of examples of this in functional or dependently-typed languages, but even C++ (which historically has listed the return type of a function first) has added an alternate (slightly clunky) syntax for function types where you can specify the return type after the arguments for this reason:
template<typename Container, typename Index>
auto
foo(Container& c, Index i)
-> decltype(c[i])
{ ... }
The parsing problems have long been solved. The `foo * bar;` issue is solved in D by the observation that the * operator returns a value, and no use of the value is made here, so it must be a declaration. No symbol table lookup is necessary. (D explicitly does not rely on a symbol table to parse.)
It is possible that the * operator is overloaded and the overload has side effects that are relied upon here, but D discourages overloading arithmetic operators for non-arithmetic uses, and considers the side-effect only case as particularly wretched and so has no problem not supporting it.
Lookahead issues are also trivial to solve, so shouldn't be a factor in modern language design.
That's why C is usually parsed with a symbol table, which is the way it was meant to be parsed. When parsing "foo * bar;", then it should be clear after parsing "foo" whether the statement is a variable declaration or an expression syntax. (It's a variable declaration if "foo" is registered as a type in the symbol table).
The advantage of this style is that types align nicely to the left and no additional keywords (like let) are required. The disadvantage is that parsing becomes highly contextual. We can't start with parsing somewhere in the middle of a file.
Surely you can have such a keyword in either case. Both of these are certainly possible:
var int x = 1
var x int = 1
I don't see that as an advantage of types to the right so much as an advantage of choosing to define the grammar to include a terminal symbol that makes parsing easier.
It is true that newer languages seem to be more likely have a keyword like 'var', but that has less to do with types left or right and more to do with type inference. If types are optional, then there needs to be something that remains which identifies it as a variable declaration.
Not at all. C/C++ grammar is explicitly contextual. `foo * bar` is a declaration if-and-only-if `foo` was previously declared as a type. Otherwise it is an expression. The subsequent tokens have zero bearing on this.
Nor is this a good argument for types-to-the-right. If C named pointer types like `⋆foo` instead of `foo⋆` [stars used to avoid formatting glitches], then putting the type to the right as `bar * foo` would be equally ambiguous as your example.
I recently updated my editor (https://github.com/DigitalMars/med) to highlight in yellow all search matches on the screen. It works so damn well I am disgusted with myself for not thinking of this decades ago.
It neatly finds the first use, etc. It also does a word search so `it` does not highlight when searching for `i`.
Easier for the machine means easier for people to write tooling for the language. Imagine if you could ast.parse() C++ code the same way you could for Python code.
Oh, but you do, because the parser will invariably leak into the way you write code. If your code takes twelve times longer to compile [1], or you need to add a space because the parser thinks that ">>" at the end of a nested template is ambiguous [2], or any number of "hacks" we have internalized as idioms because they make the parser happy, it's not really a problem with your machine anymore.
I don't think it has much to do with type inference. It's more that type systems became more complicated, and so did type names. And with a long composite type name, the name of the variable gets pushed too far out and obscured. It worked great in Algol, and still works pretty well in C (although that is partly because it splits the declarator to keep array and function syntax to the right of the variable name), but in C++ with templates it's already hard to read.
There are also a variety of issues with parsing it that way, most of which go away entirely if the name is first.
"One merit of this left-to-right style is how well it works as the types become more complex." ... "Overall, though, we believe Go's type syntax is easier to understand than C's, especially when things get complicated." ... "Go's declarations read left to right. It's been pointed out that C's read in a spiral! See The "Clockwise/Spiral Rule" by David Anderson."
I am still of the opinion that not putting some sort of visual break between a variable name and the type is somewhat unpleasant. Almost every other language uses a colon to make these easier to visually parse.
I think that's a strange rationale and it doesn't fit the philosophy promoted by golang otherwise, which is that we should optimize for the 80% or 90% cases.
Well, the parsing issues go away mostly because the actual pattern is "keyword on the left, type on the right". `let a: My_Type` is a whole lot easier to parse than `My_Type a`, and this goes even further when instead of My_Type you have something more complex, due to the C-family syntax trick of having "declaration mimic use".
`a: My_Type` or even `a My_Type` (assuming that juxtaposition is not used for something else) is easier to parse, since you only need one token of lookahead here to know that it's a declaration, and that the rest is a type constructor - and this is true regardless of how complicated that type constructor is.
For an extreme example on the other end of the spectrum, in C++, for something like this:
a<>::b<c>d;
It's impossible to even say whether it's an expression statement or a variable declaration, because it depends on what exactly b is. In fact, it might be impossible to determine even if you have the definition of b in pure C++. Consider:
template<size_t N = sizeof(void*)> struct a;
template<> struct a<4> {
enum { b };
};
template<> struct a<8> {
template<int> struct b {};
};
enum { c, d };
int main() {
a<>::b<c>d;
}
Now whether it's a declaration or an expression depends on pointer size used by your compiler.
Needless to say, this is all very fun for tools that have to make sense of code, like IDEs. And I think that's another vector for a different syntax - as "smart" tooling (code completion etc) became more common, PL designers have to accommodate that, as well. C++ helped by first becoming popular, and then teaching several painful lessons in that department.
Looks to me like the world had mostly settled on types-on-the-right already in the 1970s, except C was an anomaly and languages which imitated its syntax in other ways often imitated that too.
Types are moving to the right because having them on the left makes the grammar undecidable. Languages like C or C++ can only be parsed when semantic information is passed back into the parser.
For example, consider the following C++ statement:
a b(c);
This is a declaration of `b`. If `c` is a variable, this declares `b` of type `a` and calls its constructor with the argument `c`. If `c` is a type, it declares `b` to be a function that takes a `c` and returns an `a`.
> Types are moving to the right because having them on the left makes the grammar undecidable.
This is just an artifact of how they designed the syntax in some languages like C++ or C#. You can easily put types on the left in a way that makes this false.
I disagree with the fact that types are moving over to the right side for readability's sake or anything like that. Modern theorem proving languages explicitly specify that type declarations are simply equivalent to set inclusion, which makes the type specification operator (usually :) equivalent to set inclusion (∈). This influence was what rubbed off onto modern MLs, and by extension, inspired many of the ML inspired/type safe languages that have come out recently.
Type annotations on the right reads way more naturally to me e.g. "customerNameToIdMap is hash map of strings to UUIDs" over "there's a hash map of strings to UUIDs called customerNameToIdMap".
int num_entries = ..
float div = ..
float result = num_entries // div
vs
num_entries: int = ..
div: float = ..
result: float = num_entries // div
And that's an extremely simple example. I tried out Nim but dropped it because while writing some code with list of lists of different types etc it became completely impossible to parse it quickly.
I don't see much difference here. It's probably not a big deal when the type annotation is short but if it's something longer reading the short variable name first helps give some context first. When scanning code I think I read the variable name first as well.
Type inference should be able to deal with the simple cases anyway.
It's from Pascal; at least I can't think of any earlier language that had it. It's very noticeable when you compare it to ALGOL W, which Wirth did just before Pascal.
On a related note, it's also often advantageous for functions to indicate their return type last, as well, especially when the return value of the thing might be a function of an earlier argument. There are plenty of examples of this in functional or dependently-typed languages, but even C++ (which historically has listed the return type of a function first) has added an alternate (slightly clunky) syntax for function types where you can specify the return type after the arguments for this reason:
It is possible that the * operator is overloaded and the overload has side effects that are relied upon here, but D discourages overloading arithmetic operators for non-arithmetic uses, and considers the side-effect only case as particularly wretched and so has no problem not supporting it.
Lookahead issues are also trivial to solve, so shouldn't be a factor in modern language design.
The advantage of this style is that types align nicely to the left and no additional keywords (like let) are required. The disadvantage is that parsing becomes highly contextual. We can't start with parsing somewhere in the middle of a file.
Surely you can have such a keyword in either case. Both of these are certainly possible:
I don't see that as an advantage of types to the right so much as an advantage of choosing to define the grammar to include a terminal symbol that makes parsing easier.It is true that newer languages seem to be more likely have a keyword like 'var', but that has less to do with types left or right and more to do with type inference. If types are optional, then there needs to be something that remains which identifies it as a variable declaration.
Not at all. C/C++ grammar is explicitly contextual. `foo * bar` is a declaration if-and-only-if `foo` was previously declared as a type. Otherwise it is an expression. The subsequent tokens have zero bearing on this.
Nor is this a good argument for types-to-the-right. If C named pointer types like `⋆foo` instead of `foo⋆` [stars used to avoid formatting glitches], then putting the type to the right as `bar * foo` would be equally ambiguous as your example.
It neatly finds the first use, etc. It also does a word search so `it` does not highlight when searching for `i`.
[1]: https://stackoverflow.com/questions/29707622/swift-compiler-...
[2]: https://en.wikipedia.org/wiki/C%2B%2B11#Right_angle_bracket
Dead Comment
There are also a variety of issues with parsing it that way, most of which go away entirely if the name is first.
"One merit of this left-to-right style is how well it works as the types become more complex." ... "Overall, though, we believe Go's type syntax is easier to understand than C's, especially when things get complicated." ... "Go's declarations read left to right. It's been pointed out that C's read in a spiral! See The "Clockwise/Spiral Rule" by David Anderson."
Apparently the Spiral Rule is not entirely correct.
I think that's a strange rationale and it doesn't fit the philosophy promoted by golang otherwise, which is that we should optimize for the 80% or 90% cases.
For an extreme example on the other end of the spectrum, in C++, for something like this:
It's impossible to even say whether it's an expression statement or a variable declaration, because it depends on what exactly b is. In fact, it might be impossible to determine even if you have the definition of b in pure C++. Consider: Now whether it's a declaration or an expression depends on pointer size used by your compiler.Needless to say, this is all very fun for tools that have to make sense of code, like IDEs. And I think that's another vector for a different syntax - as "smart" tooling (code completion etc) became more common, PL designers have to accommodate that, as well. C++ helped by first becoming popular, and then teaching several painful lessons in that department.
For example, consider the following C++ statement:
This is a declaration of `b`. If `c` is a variable, this declares `b` of type `a` and calls its constructor with the argument `c`. If `c` is a type, it declares `b` to be a function that takes a `c` and returns an `a`.This is just an artifact of how they designed the syntax in some languages like C++ or C#. You can easily put types on the left in a way that makes this false.
Type inference should be able to deal with the simple cases anyway.