2 ms·
> Should always be able to be read top-to-bottom, left-to-right. No <expression> if <condititonal> like in Ruby. > But it should borrow some similarities from
by chrismorgan 5y ago
> Should always be able to be read top-to-bottom, left-to-right. No <expression> if <condititonal> like in Ruby.
> But it should borrow some similarities from natural language (like its popular Subject-Verb-Object structure) to make adoption easier (more at-hand/intuitive).
(Quibble: natural language isn’t subject-verb-object, English is. Mostly. This is acknowledged later in the article.)
Subject-verb-object and verb-object look good at first (thing.frobnicate(other) and frobnicate(thing)), but actually lead you down a path incompatible with reading top-to-bottom and left-to-right. I’ve been steadily leaning in the direction of thinking that it would be wiser to have reading order follow execution order, which often means object-verb, even though that’s not how English works.
It starts with prefix keywords on statements: `return thing()` looks fine, but is actually deeply misleading: reading left to right, you’d expect it means you’re returning a value, but thing() might crash your program (abort, panic, exception, whatever).
It gets worse: return was a statement or diverging expression, but then you use a prefix keyword on a converging expression, such as the popular `await thing`. (This point is not about await specifically, but that type of keyword, for which await is the most popular example.) Before long, you’ve got `(await (await fetch(url)).json()).field` and you’re wondering whether you should finish your transition to Lisp. And so you give up on your elegant fluent interfaces where this keyword is involved and start putting things in meaningless variables more often, unnecessarily exposing yourself to one of the two hard things in computer science.
Rust went with a suffix keyword for await, thing.await, and it composes much better—`fetch(url).await.json().await.field`.
I’m not sure where exactly the balance lies. Having control flow keywords at the start of the line is definitely good for grasping control flow at a glance; if you switch to suffix for them you do lose something. Suffix if/for/while all have such mild readability issues. Suffix return would, I think, be a good idea. (Rust’s ? error-propagation/early-return operator is suffix—though again, return is always diverging whereas ? is not, and suffix is more important for converging expressions; but also in Rust, its prefix return keyword not all that common due to the language’s expression orientation; if designing a fresh syntax for Rust, suffix break/continue/return would be very tempting.)
Prefix unary operators are also a common source of ordering pain, especially negation in conditionals. `if !some.fluent().interface()` is often painful. Didja know that in Rust you can write `if some.fluent().interface().not()` so long as you import std::ops::Not? I’ve been tempted a couple of times.
Conclusion: as usual, some design goals conflict and finding the right balance is hard.