2 added 6 characters in body
source | link
let-in-function mutable atomicthread-local dynamic* variable = value;
let-in-function mutable atomic dynamic* variable = value;
let-in-function mutable thread-local dynamic* variable = value;
source | link

What matters about programming languages are the semantics, not the syntax. However, syntax is a vehicle for semantics. It is easy to show that two languages can have incompatible semantics (e.g. unrestricted pointers vs. memory safety, or differences in type systems). Let's focus on the syntax and semantics of variable declarations.

When we declare a variable in some language, this variable has a type, a scope, and a lifetime.

The lifetime of a variable determines how long the value in that variable exists. Afterwards, that value is freed. In many languages, the lifetime is unrestricted: a value may be used as long as it is accessible, the garbage collector takes care of freeing the associated memory. This means we do not statically know when a value is freed. But C++ guarantees deterministic destruction, and it is a frequent idiom to associate sides effects with destruction. This is not possible with garbage collection. This line has wildly different meaning in C++ and Java/C#:

Type variable = calculation();

An universal syntax would need precise lifetime control syntax to be able to express a variety of languages.

Next up: scoping. Scope determines where an identifier is visible and the variable can be accessed. There are two kinds of scoping: lexical/static and dynamic. With dynamic scoping, a variable is accessible until a certain point in time, and it is accessible from any code that is executed during this time. Examples are Perl's package-global variables or Lisp's “special” variables. More common is lexical scoping, where the variable is only visible within a specific region of source code. There are three ways where the scope of a variable can start:

  • in the statement where the variable is declared, which allows the variable to be used within its definition. This is important if we are writing a recursive lambda expression.
  • after the statement where the variable is declared. This prevents recursive definitions.
  • at a different location, e.g. the start of the enclosing function or an enclosing block.

This statement has a different meaning in JavaScript (function scope) and C# (scope begins in statement):

var variable = calculation();

Universal syntax would need to provide precise scope controls for a variety of scoping conventions.

Some languages don't make a syntactic difference between assignment and variable declaration. This is problematic when a language features closures, since this prevents us from assigning to closed-over variables.

Finally, typing. Many languages require explicit static typing, others support some kind of type inference, others have no static types. This line has different semantics in JavaScript and C#:

var variable = calculation();

In JavaScript, variables do not have a fixed type, and we can freely assign a value of a different type to it at a later point. In C#, the var keyword triggers type inference, and the static type is determined by the result type of the assigning calculation. Aside from the scoping differences, the equivalent C# would be:

dynamic variable = calculation ();

And there are various other properties such as reassignability, thread-safety guarantees, or constantness of values that might be reflected in a variable declaration. There is no way a simple statement such as Python's variable = value could be used in an universal syntax. Instead, we might be dealing with

let-in-function mutable atomic dynamic* variable = value;

And at that point, our universal syntax is so cumbersome and annoying that no one would want to use it. The solution cannot be to rule out some combinations in order to simplify the syntax, because some language will have used that combination by default. If our syntax cannot express the rich variety of programming language semantics, it is hardly an universal syntax.