LanguagesArchitecture

We're going to commit a cardinal sin today and talk about syntax design! 0

There are three main alternatives for variable declaration:

  • Specifying whether the variable can change, such as:
    • Javascript's let x = 4 vs. const x = 4
    • Rust's let x = 4 vs. let mut x = 4
    • Java's int x = 4 vs. final int x = 4
    • Swift's let x = 4 vs. var x = 4
  • Not specifying whether the variable can change, such as:
    • Old Javascript's var x = 4
    • Go's x := 4 (though it's not really a keyword per se)
  • Leaving off keywords completely, such as:
    • Python's x = 4

The previous version of Vale used the first option, let and let mut. 1

However, it recently changed to a fourth option, which was so nice that it became the default behavior in version 0.2.

Side Notes
(interesting tangential thoughts)
0

It's such a travesty to be talking about something as mundane as syntax during the week we're prototyping deterministic replayability!

1

Vale is a language that aims to be fast and memory-safe, while still being easy to learn. This syntax change helps with that!

The Fourth Option

Most languages can't just use x = 4, because that already meant something. That's the assignment statement, which modifies an existing variable that we already declared.

And alas, Python tried combining those two statements. It didn't go well. 2

However, there's another option here: let's change the assignment statement!

Instead of this:

func main() {
  let a = 3;
  let b = 3;
  let c = 3;
  let mut d = 3;
  d = 7;
  println(d);
}
stdout
7

We can have this:

vale
func main() {
a = 3;
b = 3;
c = 3;
d = 3;
set d = 7;
println(d);

}
stdout
7

In other words, x = 3 declares a variable, and set x = 42 assigns it.

It was odd at first, but after using it for a few weeks, we think this is a huge improvement.

Syntax design can be a tricky endeavor. When exploring new syntax, we had to suppress the knee-jerk unfamiliarity avoidance, and actually experiment. Without experimenting, it's easy to get stuck with what's familiar, even if there are better options.

2

This leads to problems; if you rename your x = 1 declaration to y = 1, but forget to modify x = 42 assignment below, you now accidentally have two variables!

Why We Like It

To our great surprise, we've found that our codebases have a lot more declarations than assignments, so it makes sense to require the extra keyword on assignments because they're rarer.

We sampled three Vale projects. One had 111 declarations, and only 35 assignments. That's only 21% assignments! The other two were even lower, at 20% and 6%.

This isn't just Vale either. A randomly chosen Rust library, Rocket, had about 8%. 3

When did this happen? We used to assign variables all the time!

3

This is approximate; I used let(\s+mut)? (4437 results) and ^\s*[\w\[\]\.]+\s*[\+\-\*\/]?=\s+ (351 results) and which may have missed some corners, such as lambdas and mutating call returned values.

Parsing with regular expressions is fun!

We suspect moving towards more declarative patterns has contributed to this shift. Let's see some examples!

First, we no longer need assignment to return a value from an if-statement. Compare these two snippets in Scala:

scala
var weight = 0
if (i_am_a_potato) {
  weight = 42
} else {
  weight = 73
}

scala
val weight =
  if (i_am_a_potato) {
    42
  } else {
    73
  }

Second, our for-loops became foreach loops, removing that pesky i++. Compare these two snippets in Java:

java
for (int i = 0; i < ships.size(); i++) {
  ships[i].launch();
}

java
for (Spaceship ship : ships) {
  ship.launch();
}

Third, we loop over collections less, and now use specialized methods like find a lot more. Compare these two snippets in Javascript:

js
let foundIndex = -1;
for (let i in ships) {
  if (ships[i].name == "Firefly") {
    foundIndex = i;
  }
}

js
let foundIndex =
    ships.findIndex(
        x => (x.name == "Firefly"));

Nevertheless, we seem to have a lot less assignments nowadays, so it makes sense to have the extra keyword on the rarer statement, not the more common one.

Another Factor

Note how the declaration doesn't specify whether we can change the variable, like let vs. let mut.

One of the benefits of that distinction was that we could easily know whether the variable could change in the future.

We actually kept that distinction for a while; we used the ! symbol, such as d! = 3.

However, we decided to not require it for local variables, because the set keyword makes assignment more noticeable than it was before.

func main() {
  a = 3;
  b = 3;
  c = 3;
  d! = 3;
  set d = 7;
  println(d);
}
stdout
7

For example, if we want to know whether the variable d = 3 can change, we just need to look for a set d = 7 keyword somewhere in the function, which is much more noticeable now than the previous assignment syntax was.

However, that reasoning doesn't apply to structs. A struct's members might be modified from various far-flung files in our codebase.

vale
struct MyStruct {
x! int;
}

For that reason, we kept the ! on struct members. Other languages do this as well, such as OCaml, and it seems to be a pretty good balance.

Conclusion

Of course, we can't generalize too much. Every language is different, so we can't say that every new language should use this new scheme. Still, newer languages should give it some thought!

Thanks for visiting, and we hope you enjoyed this article!

In the coming weeks, we'll be writing more about our "region borrow checker" which helps eliminate Vale's memory safety overhead, so subscribe to our RSS feed, twitter, or the r/Vale subreddit, and come hang out in the Vale discord!

If you found this interesting, please consider sponsoring us:

With your help, we can write nonsense like this more often!

- Evan Ovadia

We're looking for sponsors!

With your help, we can launch a language with speed, safety, flexibility, and ease of use.

We’re a very small team of passionate individuals, working on this on our own and not backed by any corporation.

If you want to support our work, please consider sponsoring us on GitHub!

Those who sponsor us also get extra benefits, including:

  • Early access to all of our articles!
  • A sneak peek at some of our more ambitious designs, such as memory-safe allocators based on algebraic effects, an async/await/goroutine hybrid that works without data coloring or function coloring, and more.
  • Your name on the vale.dev home page!

With enough sponsorship, we can:

  • Start a a 501(c)(3) non-profit organization to hold ownership of Vale. 4
  • Buy the necessary computers to support more architectures.
  • Work on this full-time.
  • Make Vale into a production-ready language, and push it into the mainstream!

We have a strong track record, and during this quest we've discovered and implemented a lot of completely new techniques:

  • The Linear-Aliasing Model that lets us use linear types where we need speed, and generational references where we need the flexibility of shared mutability.
  • Region Borrowing, which makes it easier to write efficient code by composing shared mutability with the ability to temporarily freeze data.
  • Higher RAII, where the language adds logic safety by enforcing that we eventually perform a specific future operation.
  • Perfect Replayability makes debugging race conditions obsolete by recording all inputs and replaying execution exactly.

These have been successfully prototyped. With your sponsorship we can polish them, integrate them, and bring these techniques into the mainstream. 5

Our next steps are focused on making Vale more user-friendly by:

  1. Finalizing the compiler's error messages and improving compile speeds.
  2. Polishing interop with other languages.
  3. Growing the standard library and ecosystem!

We aim to combine and add to the benefits of our favorite languages:

We need your help to make this happen!

If you're impressed by our track record and believe in the direction we're heading, please consider sponsoring us:

If you have any questions, always feel free to reach out via email, twitter, discord, or the subreddit. Cheers!

4

Tentatively named the Vale Software Foundation.

5

Generational references, the linear-aliasing model, and higher RAII are all complete, and region borrowing, fearless FFI, and perfect replayability have been successfully prototyped. Be sure to check out the experimental version of the compiler!