Stroustrup's Rule and Layering Over Time in Rust
thefeedbackloop.xyz
thefeedbackloop.xyz
The big problem, of course, is that code hangs around. Can you evolve your syntax as people get used to it? Not only are you adding work to your parser, but people who use your language also have to deal with the multiple iterations of the syntax that linger in various codebases. This is one reason I detest dealing with Perl codebases.
Anyhow, as an exercise of evolving syntax while keeping the underlying semantics, I find Reason [1], a new syntactic layer over OCaml, particularly interesting.
Clippy[1] helps a lot in this case, it helps people writting idiomatic Rust and it's updated when new syntaxes introduces new ways to do stuff, so the old one is deprecated _de facto_.
If your company decides to not use try!, then you can set that up (and have an opt-out for when you "really" know what you're doing)
Our civilization is going to end up with countless dead languages -- dead programming languages, that is -- because future generations won't know how to read the increasingly terse and arcane syntax that is all the rage these days. Either that, or the investment necessary to read these crazy languages will be considered an ill advised investment in resources. We'll end up recreating the wheel indefinitely, but in different languages.
I would like to see much, much simpler programming languages, even if they're more verbose, as long as the result is improved readability and longevity of the code written in said languages. I think some of the older languages are far superior in many ways, if only because, with some of them, you can actually read and understand the code without even any formal training in the language.
I'm really sad to see this misconseption over and over on HN. Verbosity is so much worst than syntax complexity because you ends up having dozens of different patterns that do the same thing overall but with subtile differences on edge cases.
If you look at JavaScript pre-ES6, you had at least 10 ways of doing OOP with really different behaviors when it came to inheritence or encapsulation. This makes the code so hard to understand.
ES6 formalizing a specific semantic for OOP was godsend.
As long as you introduce syntax to help with what people are actually doing (and not juste because it looks cool) syntax addition enhance the code readability.
Ada and Dylan are good arguments against the notion that verbosity is harmful.
Please don't be rude.
Verbosity is so much worst than syntax complexity because you ends up having dozens of different patterns that do the same thing overall but with subtile differences on edge cases.
There's a level of verbosity/terseness that's ideal for (your average) human. I don't know precisely what that level is, but I think many programming languages, such as C++ and Rust, have stepped way past that line.
If you look at JavaScript pre-ES6, you had at least 10 ways of doing OOP with really different behaviors when it came to inheritence or encapsulation. This makes the code so hard to understand.
The problem with JavaScript OO was, in my opinion, the use of prototypes.
As long as you introduce syntax to help with what people are actually doing (and not juste because it looks cool) syntax addition enhance the code readability.
This is simply an unsupported claim.
There are no languages in the same domain that don't have a comparable level of syntax and semantic complexity given the intrinsic nature of the information they want to encode.
And I would make rather take sigils than using a bunch of different keywords every time I need to talk about lifetimes and pointers.
To date I have not seen languages with comparable complexity expressed with more elegant syntax. Some domains just have irreducible complexity.
So vague.
> As long as you introduce syntax to help with what people are actually doing (and not juste because it looks cool) syntax addition enhance the code readability.
So you are saying Rust's syntax is doing just right. But this has nothing to do with other language design. Just open your mind.
It's not obvious from the simple example given the original article, but as the release notes mention it makes things like the following:
try!(try!(try!(foo()).bar()).baz())
much more readable:
foo()?.bar()?.baz()?
If the main concern is readability of code, the latter wins hand down.
Programming languages can make things more complicated and having worked in C++14, python and VBA i can tell you that VBA does not come out on top even though it is seemingly simple. Compared to python VBA lacks so much ecosystem and language features that you will have a hard time to even parse XML, process loosely structured data and serve it via HTTP.
Looking at python vs. VBA and C++11 vs. C++03, I'd say that programming languages have generally been moving in the right direction.
When I was young, it was all about the "next guy." You iterate on it to make it clean, make it simple, make the docs right, etc because you wanted to set up the next guy for success debugging your shit. It was a code of honor, we are all the next guy in a way. You don't hear about it as much any more. Working and getting it done quick seem to outweigh real craftsmanship.
There are general purpose languages that enable people to author complex programs because they have terse syntax and are highly expressive. In such cases the programs are complex but the languages are often semantically simple and consistent (e.g. Haskell).
I certainly do not agree in languages like C++, where for example they are attempting to combine the semantics of runtime memory management with the lambda calculus.
Forgive my youth, which languages are these?
Of course reading any program still requires a mind accustomed to logical thought. A subsistence farmer has a different sort of "no formal training" than an 18 year old taking Chemisty courses.
In any case there is no need to worry...the important thing is the ideas not the syntax. And the useful ideas tend to be conveyed through time in multiple languages.
BASIC is even worse in many respects. It starts being deceptively simple, but do you think that someone looking at these two lines:
PRINT a, b;
PRINT a; b
would be able to tell the difference? Or, say, what does this do? LINE (0, 0) - (100, 100),, BF
(no, it doesn't draw a line)And then if we're talking about classic BASIC, you have to remember that A% is integer while A$ is string etc. None of that is at all obvious.
Or, say, you see this:
NAME x AS y
A reasonable guess would be that it renames a variable, or maybe creates an alias, right? But no - it actually renames a file with a name corresponding to a value in variable x, to a new name corresponding to a value in variable y. And many BASIC dialects will even helpfully stringize it for you, if the variables were, say, integers.OTOH the usual, pre-Java-8 way of allocating anonymous subclasses when you mean to pass a function is at best obscuring the meaning of your code...
Again, this is just a difference in attitude, but I find verbosity in general just cognitive noise that would be better spent with precise, higher-level constructs.
class Something {
typedef std::unique_ptr<Something> unique;
typedef std::shared_ptr<Something> shared;
typedef std::weak_ptr<Something> weak;
};
// ...
// later in the code
Something::unique a;
// instead of std::unique_ptr<Something> a;This is a start, then I usually define specific types from there.
Likewise the evolution from list comprehensions to generators left us with some terribly overlapping syntax.
Going with let's have "explicit syntax" now until the users get familiar with it and we will make it more terse later, is not a good idea IHMO.
But I do understand and appreciate that sometimes evolutionary changes will bring in duplication, so I am in no way attacking Rust and the specific example of the evolution of the error handling syntax.
> Going with let's have "explicit syntax" now until the
> users get familiar with it and we will make it more
> terse later is not a good idea IHMO.
I don't think this is a conscious decision. IMO, when you're first designing a language, you don't necessarily know what idioms are going to be most common, so you use special syntax sparingly, and force users to be explicit everywhere else. Then, once you have experience with how people use the language and can empirically observe what idioms are popular, you favor those idioms with shorthand syntax.Getting back to the specific example in the OP, for a long time there was a discussion on whether Rust should use the question mark for some dedicated syntax (having removed the C-style ternary operator in 2012 as redundant with `if`). But people disagreed on what to use it for: some wanted to use it to designate functions that return booleans (like Ruby does); others wanted to use it to designate functions that return Option (my original stance, way back when); others wanted it as sugar for constructing and typing Options (as Swift eventually did); some wanted a C#-style coalescing operator. But after years of experience it turns out that none of the above are especially prevalent in Rust, especially once the community embraced the Result type (which matured relatively late in Rust's development) for most of what Option had originally been used for. In retrospect having a terse way to handle Results is an obvious win and I absolutely adore the new `?` operator, but it takes time to produce the evidence that such things are truly worth their weight.
Maybe I misunderstood the article then! When I read:
For new features, people insist on LOUD explicit syntax.
For established features, people want terse notation.
I understood, keep it explicit to begin with, and make it terse when users get familiar. Probably I got it wrong.Using those examples, let X as Y = Z is just one more letter but eliminates any confusion for someone unfamiliar with the syntax, since its fairly distinct from other C like languages.
Its also not particularly cumbersome for a language to support a ! operator and the keyword "not" for logical negation.
Same with : and "in" in for loops.
That and syntax features like whitespace significance as a basis are IMO great for good style.
For a language like Rust where it is now, adding these optionally would greatly increase my enjoyment of the language, because I generally like programming that I can read like Python, despite having a half decade of C++ experience so its not for a lack of benefit from the terseness.
Rust would do well to avoid weird glyphs, IMO.
'Only', 'if', 'you', 'like', 'quoting', 'every', 'single', 'word', 'or manual splitting and'.split(), variable, 'interspersing'. Or writing your own shell syntax.
>There should be one-- and preferably only one --obvious way to do it.
The zen of python isn't saying "only have one way to do anything" it says "have at least one obvious way, and preferably only one obvious way to do this".
There can be lots of ways to iterate over a list:
[x for x in l]
for x in l:
x
while (x for x in [1,2,3]).next():
...
Only one is obvious for a given situation.I love Rust language and feel sorry for this criticism. But small things are important.
The language has actually thought of this case -- it turns out you can map lower-level errors into your higher-level error type by implementing the `From<E>` trait. E.g.:
use std::io;
pub enum MyErr { Io(io::Error), Custom(String) }
impl From<io::Error> for MyErr {
fn from(err: io::Error) -> MyErr { MyErr::Io(err) }
}
fn do_stuff() -> Result<(), MyErr> {
let f = try!(File::open(...));
// ...
}I still sometimes make mistakes reading rust code with the ? at the end of the line, but it's just a question of what type is it and does the line return. Both of which are handled by the compiler in the end, so you get a little bit of extra knowledge that even if you misread the line of code, there's often limits to how much damage you can cause.
Maybe you already know this, but if not (and for others that don't), try it immediately. You'll be glad you did.
And error-chain exists if you want a more fluid system of errors.
Or you can implement a conversion from the underlying error to your custom error type[1], and 'try'/'?' will just work.
[1]https://doc.rust-lang.org/book/error-handling.html#composing...
Rust's problem is the same that any language in its domain encounters: it must encode a lot more information than similar scripting languages in order to describe the semantics of this program.
It most certainly does not read like Perl. The sigils are weird but there's few of them and they're used consistently and without overloaded meaning. And unless you want to make a horrendously verbose language by assigning keywords to stuff related to lifetimes, pointers and references, it attempts to strike the balance between readability and compactness for the experienced developer.
Rust used to have a lot more sigils. Its syntax and semantics are nowadays a lot simpler than languages in the same domain (C++ I'm looking at you) and a lot of the complexity has been pushed into the traits system, IMO for the better.