Why is it that in nearly all modern programming languages (Go, Rust, Kotlin, Swift, Scala, Nim, even Python last version) types always come after the variable name in the variable declaration, and not before?
Why x: int = 42
and not int x = 42
?
Is the latter not more readable than the former?
Is it just a trend or are there any really meaningful reasons behind this solution?
6
All of the languages you mentioned support type inference, which means the type is an optional part of the declaration in those languages because they’re smart enough to fill it in themselves when you provide an initialization expression that has an easily-determined type.
That matters because putting the optional parts of an expression farther to the right reduces parsing ambiguity, and increases consistency between the expressions that do use that part and the ones that don’t. It’s just simpler to parse a declaration when you know the var
keyword and the variable name are both mandatory before you get to the optional stuff. In theory, all of those things which make it easier to parse for computers should improve overall readability for humans too, but that’s a lot more debatable.
This argument gets especially strong when you consider all the optional type modifiers that a “non-modern” language like C++ has, such as *
for pointers, &
for references, const
, volatile
and so on. Once you throw in commas for multiple declarations, you start to get some really strange ambiguities like int* a, b;
not making b
a pointer.
Even C++ now supports “type on the right” declarations in the form of auto x = int{ 4 };
, and it does have some advantages.
5
Why in nearly all modern programming languages (Go, Rust, Kotlin, Swift, Scala, Nim, even Python last version) types always come after the variable declaration, and not before?
Your premise is flawed on two fronts:
- There are new-ish programming languages that have the type before the identifier. C♯, D, or Ceylon, for example.
- Having the type after the identifier is not a new phenomenon, it goes back to at least Pascal (designed 1968–1969, released in 1970), but actually was used in mathematical type theory, which starts ~1902. It was also used in ML (1973), CLU (1974), Hope (1970s), Modula-2 (1977–1985), Ada (1980), Miranda (1985), Caml (1985), Eiffel (1985), Oberon (1986), Modula-3 (1986–1988), and Haskell (1989).
Pascal, ML, CLU, Modula-2, and Miranda all were very influential languages, so it is not surprising that this style of type declarations stayed popular.
Why
x: int = 42
and notint x = 42
? Is the latter not more readable than the former?
Readability is a matter of familiarity. Personally, I find Chinese to be unreadable, but apparently, Chinese people don’t. Having learned Pascal in school, dabbled with Eiffel, F♯, Haskell, and Scala, and having looked at TypeScript, Fortress, Go, Rust, Kotlin, Idris, Frege, Agda, ML, Ocaml, …, it looks completely natural to me.
Is it just a trend or are there any really meaningful reasons behind such a solution?
If it’s a trend, it’s pretty persistent: as I mentioned, it goes back a hundred years in mathematics.
One of the main advantages of having the type after the identifier, is that it is easy to leave the type out if you want it to be inferred. If your declarations look like this:
val i: Int = 10
Then it is trivial to leave out the type and have it inferred like this:
val i = 10
Whereas if the type comes before the identifier like this:
Int i = 10
Then it starts to get hard for the parser to distinguish an expression from a declaration:
i = 10
The solution that language designers then usually come up with is to introduce a “I don’t want to write a type” keyword that has to be written instead of the type:
var i = 10; // C♯
auto i = 10; // C++
But this doesn’t actually make much sense: you basically have to explicitly write a type that says that you don’t write a type. Huh? It would be much easier and more sensible to just leave it out, but that makes the grammar much more complex.
(And let’s not even talk about function pointer types in C.)
The designers of several of the afore-mentioned languages have weighed in on this subject:
-
Go FAQ (see also: Go’s Declaration Syntax):
Why are declarations backwards?
They’re only backwards if you’re used to C. In C, the notion is that a variable is declared like an expression denoting its type, which is a nice idea, but the type and expression grammars don’t mix very well and the results can be confusing; consider function pointers. Go mostly separates expression and type syntax and that simplifies things (using prefix
*
for pointers is an exception that proves the rule). In C, the declarationint* a, b;
declares
a
to be a pointer but notb
; in Govar a, b *int
declares both to be pointers. This is clearer and more regular. Also, the
:=
short declaration form argues that a full variable declaration should present the same order as:=
sovar a uint64 = 1
has the same effect as
a := uint64(1)
Parsing is also simplified by having a distinct grammar for types that is not just the expression grammar; keywords such as
func
andchan
keep things clear. -
Kotlin FAQ:
Why have type declarations on the right?
We believe it makes the code more readable. Besides, it enables some nice syntactic features. For instance, it is easy to leave type annotations out. Scala has also proven pretty well this is not a problem.
-
Programming in Scala:
The major deviation from Java concerns the syntax for type annotations—it’s “
variable: Type
” instead of “Type variable
” in Java. Scala’s postfix type syntax resembles Pascal, Modula-2, or Eiffel. The main reason for this deviation has to do with type inference, which often lets you omit the type of a variable or the return type of a method. Using the “variable: Type
” syntax this is easy—just leave out the colon and the type. But in C-style “Type variable
” syntax you cannot simply leave off the type—there would be no marker to start the definition anymore. You’d need some alternative keyword to be a placeholder for a missing type (C♯ 3.0, which does some type inference, usesvar
for this purpose). Such an alternative keyword feels more ad-hoc and less regular than Scala’s approach.
Note: the designers of Ceylon also documented why they use prefix type syntax:
Prefix instead of postfix type annotations
Why do you follow C and Java in putting type annotations first, instead of Pascal and ML in putting them after the declaration name?
Because we think this:
shared Float e = .... shared Float log(Float b, Float x) { ... }
Is simply much easier to read than this:
shared value e: Float = .... shared function log(b: Float, x: Float): Float { ... }
And we simply don’t understand how anyone could possibly think otherwise!
Personally, I find their “argument” a lot less convincing than the others.
5
Pascal does it too by the way, and isn’t a new language. It was an academic language though, designed from scratch.
I would say it is semantically clearer to start with the variable name. The type is just a technical detail. If you want to read your class like the model of reality that it is, it makes sense to put the names of your entities first and their technical implementation last.
C# and java stem from C so they had to respect the “prior art”, so not to confuse the experienced programmer.
6
Why
x: int = 42
and notint x = 42
? Is the latter not more readable than the former?
In such a simple example, there’s not much difference, but let’s make it a bit more complex: int* a, b;
This is an actual, valid declaration in C, but it doesn’t do what it intuitively looks like it should do. It looks like we’re declaring two variables of type int*
, but actually we’re declaring one int*
and one int
.
If your language puts the type after the variable name(s) in its declarations, that problem is impossible to run into.
7
I imagine, var x : Type = value
is easier for automatic refactoring than Type x = value
. The former just needs to remove or add : Type
between var <identifier>
and =
. The latter must be able to parse the entire Type
expression (or semantics in the worst case) to find it.
But otherwise I don’t see much of a benefit of adding a redundant var
in front of declarations.
Maybe when you have large complicated or templated types I could understand that people prefer “postfix notation”:
ThisIsMyLargeType[HereICome[MyType],PolicyStrategyManager]
algorithmSettings = ...;
algorithmSettings : ThisIsMyLargeType[HereICome[MyType],PolicyStrategyManager]
= ...
The long type (particularly without good Type naming conventions) still takes some time to be parsed by a human as a “type” in order to figure out it is a declaration. It won’t matter for a compiler but if a human wants to skim code and skip declarations, it could take a moment to realize that it is a declaration after all.
I guess, a short explicit keyword just improves the ability to skim because for skipping declarations, only a fixed small keyword var
needs to be read. Trained people can see faster if it is only a variable declaration and can ignore the rest.
In this light, I find Martin Maat’s answer very helpful because when reading over code, one probably doesn’t need to read all the type details. One can skip implementation details like type and assigned expression better when one can see the var
(or val
) part.
With the type after the variable name, the semantic information monotoneously transitions from unspecific to specific.
Particularly in C++, variable definitions can become streneous.
I’m pretty much used to C-like declarations which is why this is easier to read for me at the moment.
Only slightly related: the colon syntax.
Knowing Python, Nim confused me with a 2nd meaning of the colon. IMO their syntax addition of writing var x : Type
does not mix well with Python syntax which already uses :
as syntax delimiter. The non-Pythonic syntax changes and recommendations appear to me like an arbitrary eclecticism of different things.
The big plus of “prefix notation” is, it’s much more natural to talk this way in English and related human languages. “This is my Dog Woofy.” On the other hand, you could hear people say: “Do you know Johnny, the Superstar?”. But for me it sounds less natural.
Could be an interesting language experiment to replace the type annoation :
with “the”.
Let woofy the Dog be my pet. # var woofy : Dog = this.pet;
1
FORTRAN used prefix notation: variables starting with I,J,K were integers. BASIC used Suffix notation: variables ending in $ were strings. Like the idea of string objects and automatic memory management, what comes around goes around: suffix notation long predates Pascal.