I Declare

Separate syntax for declaration and assignment is a Good ThingTM.

Unified Syntax

Some languages have a single syntax to either (a) define a variable for the first time or (b) assign a new value to an existing variable.

This “unified syntax” generally looks like this:

x = 2  # define a new variable with '='
x = 3  # re-assign an existing variable with '='

The above example uses Python, but many other languages also use a single notation to convey two subtly different things:

  • create a new variable
  • modify an existing variable

There are reasons to prefer a single notation for both of these concepts, such as:

  • less syntax to learn
  • to make these concepts “feel” more like one concept

When Separate Syntax Helps

On the other hand, requiring separate syntax for declaration and assignment can be helpful. For example, in Python, the following is not an error, even though I think it should be caught as an error before the program is allowed to run:

color = 'blue'
if True:
  colour = 'red' # oops, used British spelling (added 'u')

print(color)
blue

Python does not consider this a syntax error (even though a missspellled word is a syntactic mistake), or even a runtime error. If you’re lucky, this error will be caught by your tests. If you’re unlucky, it will be caught by your audience during a high-pressure demonstration.

This would be caught as an error if Python had separate syntax for declaring versus assigning variables. We can work around this in Python, if we pay the price:

def define(name, val):
  g = locals()
  if name in g:
    raise NameError
  else:
    g[name] = val

def assign(name, val):
  g = locals()
  if name in g:
    g[name] = val
  else:
    raise NameError

define('color', 'blue')
if True:
  assign('colour', 'red')
print(color)
Traceback (most recent call last):
  File "<stdin>", line 17, in <module>
  File "<stdin>", line 13, in assign
NameError

Of course, it’s much more typing to write define and assign than it is to write =, and the whole point of the exercise was to avoid simple mistakes. But if it takes 20x more work to avoid the mistake, no one will want to use these safety features.

Some Mistakes Not Worth Repeating

Statically typed languages like C and Java differentiate between declaration and assignment by the presence or absence of type annotations.

int x; /* declaration */
int y = 0; /* combined declaration and assignment, or a "definition" */
y = 3; /* assignment only */
z = 10; /* error: z must be declared before it can be assigned */

This notation is economically terse in one sense (no extra word in addition to the type) but ultimately is verbose because it requires type annotation for each variable declaration (and makes type inference difficult or impossible to add to the language).

Languages with type inference, such as Rust, have keyword syntax for declaring a variable without a type annotation:

let mut x = 3; // type of x inferred, omitting 'mut' makes x immutable

Recent C++ has limited forms of type inference, which reduces a lot of the clutter, but still requires a keyword (auto):

for (auto i : v) {...}

Type inference is great, and C++ and Rust are moving in the right direction, but they would be even better if let or auto was not required.

Better Alternatives

The language BQN has a nice solution to this problem. It is a dynamically typed language and has no type annotation syntax, so it could borrow from JavaScript’s var syntax to distinguish definition from modification. Instead, it uses two different glyphs:

x ← 1 # define
x ↩ 3 # change

I find the ↩ glyph evokes its meaning (“change”), since it reminds me of a browser’s refresh icon.

For a language whose variables don’t have special attributes like type or storage class, I think this 2-glyph solution is optimal. On the other hand, if a language supports other variable attributes, such as mutable/immutable or whether the variable should outlive the current scope, then I think additional syntax becomes necessary.

Typed, ASCII Version

My knowledge of the (experimental) Jai language may be out of date, but at one point I think it used what I would call a “separated” syntax for declaration and assignment, which looked like this:

w : int;      /* declare w, explicitly annotate type */
x : int = 10; /* declare x, explicitly annotate type, and assign at the same time */
y := 42;      /* declare+assign y at the same time, but this time infer the type */
z = 10;       /* assign only, an error unless z was declared previously */

I think this offers the programmer a good spectrum of choices between type inference and explicit type annotations. I also appreciate the visual distinctiveness that separates declaration from assignment, and the symmetry between name : type = value and name := value.

You can imagine extensions to this that provide the compiler more information or give the programmer more flexibility:

a : const int = 10; /* add "const" keyword */
b : arc int = 10; /* add keyword to denote the variable will be automatically reference counted */

For a language with optional type annotations, this is a nice solution.

As an aside, Haskell lets you write type annotations on a separate line from the function you’re annotating, which can sometimes really help with readability. You can often omit the type annotation entirely, which of course is as minimal as you can get in terms of type annotations. It looks like this:

foo :: Int -> Int
foo x = x + 1

But for the purposes of discussing mutable variable syntax, Haskell is no help, because its values are all immutable.

Summary

I think new languages should use separate syntax to distinguish between creating a new variable and updating an existing one. It transforms a very common class of bugs (typos) into syntax errors, rather than silent runtime errors waiting to ruin your day.