Hacker News new | past | comments | ask | show | jobs | submit login
Analog – An analogous, indented syntax for the Rust programming language (github.com/asvln)
58 points by asvln 35 days ago | hide | past | favorite | 66 comments

I feel a sense of relief when working in a language where you can paste some code, hit autoformat, and not worry if any lines might have been accidentally added or removed from a for, if, or while block.

Looking at python here...

My first impression was "Oh, CoffeeScript for Rust".

What is the goal of this specification? Maybe I missed it in the point. Also the name might be a bit challenging considering the overall footprint and overloaded nature already of the word `analog`.

The goal is experimentation.

I’m curious:

• Why?

• You’ve made two mostly independent major changes in the syntax: to depend on significant indentation, and to switch from keywords to symbols. Why both, rather than just indentation while keeping it otherwise as familiar as possible?

• Why change lifetimes from ' to `? (Nothing obviously collides with keeping it '.)

• Why change closures from || to //? (You’ve then reused | for other things, but why not the other way round?)

• What does the word “analogous” mean here? (I don’t believe it can be the normal English sense I’m used to because that requires something in the syntax to be compared to—I’m not sure what the proper grammatical term is on the adjective, but it’s like the distinction between intransitive and transitive verbs, with the sense I know being strictly transitive (by analogy) but you using it intransitively (as it were).)

Finally, macros are a knell for alternative syntaxes like this: it’s not possible to make them work interoperably, because macros consume Rust token trees, but can interpret the body however they like, and you can’t reliably detect what they’re trying to do, so you can’t compensate. In your println! example, you are apparently saying it must translate the body from Analog to Rust, which would work in that instance because it’s largely consuming Rust language syntax (though named arguments add a little fun); but in your json! example, it needs to not perform any such translations, because json! doesn’t consume Rust syntax. (Perhaps in that specific instance the translation would be harmless, but it’s trivial to show macros where that’s not the case.) Attribute macros will be OK, and you might be able to get most structural macros working, but many structural macros (especially those that deal with token trees) and all non-attribute procedural macros are going to be a dead loss. I’m afraid I see only three solutions for macros in such an alternative syntaxes: (a) give up on interoperability, and maintain a completely distinct macro system; (b) have macro bodies always be in Rust syntax, which would break your very first example’s use of println!; or (c) on macros where there is any uncertainty, require what basically amounts to FFI bindings, which the language could provide for the standard library but it’d mean pain for calling macros from third-party crates, and could basically entail falling back to Rust syntax in complex cases. Hmm… that’s actually interesting to think about, as the uncertain nature of macros already causes some trouble for developer tooling. (rust-analyzer works well with structural macros, but can’t cope very well with most procedural macros, and something along these lines could actually be genuinely useful.)

- The plan was originally to design a syntax for a simple scripting language; but the more I designed it, the more I saw the ability to just bring in the rest of the language.

To represent all of Rust with only only a small set of ASCII characters, while keeping visual noise to a minimum, and still representing what the code was supposed to do was quite a riddle. I enjoy riddles.

- Changing lifetimes was due to the fact that ` does not conflict with any other part of Rust, I felt the concept of lifetimes deserved its own symbol, and it is much more readable when used as a prefix/suffix on a set of letters.

- The // characters stand out in a group of letters and numbers moreso than ||. In this manner, I can add highlight/significance to closure calls which can be deeply nested in function calls which are mainly chars [a-Z]. Also if you imagine you are in a circle: things loop in a straight line ||, but // would be take you on a diverging path.

- "analogous" "strictly transitive (by analogy) but you using it intransitively (as it were)"

This is very much intentional. Nouns do not exist in the real world, everything is verbing all the time. Languages have failings, they depend on the users of the language to use/redefine it as they see applicable.

The initial goal was to design an 'analogical' language. Or in other words: a language in which it's symbolic representation would correlate to the concepts being implemented. The first commit of the repo exemplifies this more.

- The macros can be translated directly into Rust, the first example is valid. Indentation would be substituted for a { on the macro call and the end of indentation would signify the }. As far as I know, leading and trailing whitespace is not a concern with Rust's macro system.

a) Making its own macro system would be futile considering most expressions haven't changed. It just requires more cognitive overhead to remember the ),; where applicable, but at least you only have to think about that stuff in macro calls.

b) If someone had to fallback to Rust when wanting to use this syntax, then the whole thing would be pointless and I would lament my time spent designing it to not learning how to bake crumb cake.

c) Now that's a lot of syntax trees...

I think you’re missing the complexity of macros: they’re not just Rust or not-Rust, they can mix and match, including using Rust-like syntax. Take this macro stub for example (implemented as a procedural macro, if you want to make it harder):

  macro_rules! alpha {
          $(<$($beta:ident),* $(,)?>)?
          for $gamma:ty;
          struct $epsilon:ident default $zeta:expr;
          ## $eta:tt // And I’m not telling you how I’m using this one
      ) => { … };
I can’t see any way to make this work with Analog syntax in such places as $gamma, $zeta and $theta (and who knows about some of the others like $eta?). I’ve made this a fairly extreme example, but simpler examples that are still problematic are widespread.

I think you misunderstood, or I am misunderstanding what you are trying to communicate. There would be no attempt to change any syntax within macro calls. All token trees fed into any macro would be fed directly with the exception of the implied braces when indenting the call.

In your println! example, you’ve written Analog syntax, but it must be being translated into Rust syntax for the macro to consume. How are you going to decide what to translate and what not to?

Perhaps I might put it this way instead: can you write out for me what an invocation of that alpha macro (which is defined in Rust code) from Analog might look like?

You are absolutely correct, apologies I just saw it now. It was a remnant that got left over.

I now see the issue this raises and have discovered a solution through a macro!! call.

I assume by "analogous" they mean "the AST can be translated one-to-one without context or extra steps"

> Why change closures from || to //? (You’ve then reused | for other things, but why not the other way round?)

Just guessing, but I bet it's to create a parallelism between // (closure) and \\ (closure that moves). If closures were ||, there wouldn't be quite as much of a same-but-different feeling as you get with slash & backslash.

As someone who thinks they probably have dyslexia the difference between slash and backslash is very hard for me to tell, but easy to differentiate from pipe. (One is straight the others are slanted, who knows which slant is which)

I'm not sure that's a dyslexia-specific thing. Back when the web started getting mainstream traction and it was notable to see URLs in TV commercials (ca 1994–5), I remember roughly half of the radio ads that mentioned URLs would say, “h-t-t-p-colon-backslash-backslash…”

I think that's just because people couldn't remember which was "slash" and which was "backslash", and in the DOS/Windows world, you were more likely to hear references to "backslash". Browsers quickly started silently fixing inappropriate backslashes, so people never had to learn which was which.

I have a really hard time transcribing phone numbers and entering product serial keys too

Thank you for your input. The syntax should accommodate dyslexic learners as much as possible.

If anything in the syntax has ambiguity in this regard, please do share, as I am not dyslexic.

This is great! Its amazing how much more readable programming languages get when they drop the visual noise created by unnecessary keywords – there's a reason math is so much easier to understand when it's expressed with symbols than with words, and it's very surprising how few programming languages recognize that.

(I notice a few comments describing this syntax as less readable. My first thought was that these comments were sarcastic/trolling, but I've concluded that they're serious – but written by programmers who think very differently than I do!)

> there's a reason math is so much easier to understand when it's expressed with symbols than with words

Had to do a double-take to make sure this wasn't /s

It's my opinion that the use of random symbols is the primary reason math is so inaccessible. It is for me, and anecdotally I've heard dozens of others say the same thing. The only imaginable benefit I can see to using symbols is that they're easier to write quickly on a chalkboard.

As an educator, I think about this a lot. One approach I'm prototyping for a (hopefully freely available) course on reinforcement learning is to translate dense math expressions into English sentences, and then link each piece of text<=>math with a corresponding color. (Quick example I just uploaded: https://gfycat.com/favorablemintyharrier ).

By doing this consistently, I can highlight the link between math symbol and corresponding concept, and help the student learn how to read and interpret dense constructions as we go.

(Note that this approach is intended to be used as part of a larger explanatory strategy, so please assume the example is embedded in an appropriate context.)

Amazing! I had a similar idea a long time ago, but never put effort into it. Tools like this help fill the gap between the formality and "humanity" when expressing and presenting such concepts to receptors (students).

Is this coded? Do you have it open source?

This is so beautiful.

I completely agree. For me it's less readable. I also think math is less accessible due to its use of random symbols that are context-dependent.

"Readability" is a highly, highly subjective matter, and depends on one's education, background, knowledge, experience etc. People seem to use it as a shorthand for "math-like shorthand", but that's not what I mean when I say readable, it's like we speak in a different language.

Personally I find the adjective "readable" a major trigger, it seems to be thrown around by people who are either trolling or who seem to live in a kind of a social (online) bubble and don't know anyone else with a even a slightly different background. This is always slightly shocking to me.

I remember sitting in the hallway of the building with the math department waiting for a class to start and picking up a booklet on a table with a style guide for writing mathematics and it specifically advised using words in place of symbols, e.g., preferring “for all x in the integers” over “∀x ∊ ℝ.”

D'oh, not integers, reals. Sigh.

Does this mean you prefer Ada or COBOL to C or JavaScript? All symbols (and words!) are “random” if you haven’t learned what they mean. I remember when I transitioned from BASIC to C (this was a while ago) it seemed ridiculously terse and full of crazy punctuation, but C-like syntax is now considered “accessible”.

I am a crazy one, I like both Ada and C!

The other huge benefit to symbols is that they are compact, and recognising patterns or performing symbolic manipulation is much easier when everything is shorter and closer together. Mathematics also uses a two-dimensional layout in some places (integrals, fractions), with very well-chosen and suggestive notation, which would be lost completely going to words.

Right. It's not just that they are random symbols. They are also inherently obscure symbols.

But just bad as or maybe worse are the variable names which are random, short, and completely non-descriptive. If you compare it to source-code, it's like the worst code ever written.

Math is easier to understand with symbols if you've been trained in what all the symbols and syntax means, and virtually impossible to understand if you haven't. This is easy to miss because for the most part we have one unified syntax taught in most the world.

Words may not be the optimal format to express equations, but are the lowest denominator that would allow everyone to understand if they knew the concepts.

That should illuminate why your opinion and other people's opinion of this differ. You likely each have different relative opinions and skills reading syntaxes like this.

> Math is easier to understand with symbols if you've been trained in what all the symbols and syntax means, and virtually impossible to understand if you haven't.

I haven't and it continues to be a massive source of shame. Maybe today's the day I do something about it.

I think you're too categorical. We don't only use words in written communication - there are pictures and symbols too, so restricting ourselves to spoken words in largely symbolic activity seems a disadvantage. For both math and programming it makes sense to invest into training of symbols and then get to the matter with expanded toolset, rather than keep using something more similar to COBOL. Tools are important for the progress.

It's not a random event that digits - essentially symbols for numbers - were invented millennia ago.

I wasn't making a case that words are better than symbols, only that there's always a trade off between representing something using existing known syntax and a somewhat less known but more concise and exact syntax, and that tradeoff is largely defined by how familiar your target audience is with that syntax.

C-like syntaxes are more popular than lisp syntax(for example) not because it's inherently better but because most people have some exposure to it at this point through other languages they have been exposed to. Interestingly, that actually does make it inherently better in some aspects when choosing a syntax to adopt, such as learnability. For the person that only knows C and the person that only knows Lisp, which might have an easier time with the average rust program? What about someone that only knows Prolog?

The issue is not about only using words, the issue is about using things your audience already knows. People use numbers in letters not just because words are less convenient, but because words are less convenient and they know the people on the other end know Arabic numerals so that's a valid medium to rely on.

For programming languages it's less that words are better and more that there are either different or conflicting meanings for many symbols leading to confusion for those that deal with multiple languages or are new to the current language, so time needs to be taken to internalize what they mean in this new context. That doesn't make them bad or worse than words, but often they necessarily are less obvious in meaning to those new to the language to benefit those that are more familiar or experts.

> (I notice a few comments describing this syntax as less readable. My first thought was that these comments were sarcastic/trolling, but I've concluded that they're serious – but written by programmers who think very differently than I do!)

Don't worry, I assumed the exact same about your comment. I still have to remind myself sometimes that people use Python because they think it's easier to reason about.

I'm not a Rust programmer, but I can read and mostly understand the Rust code I've seen. This by comparison is incomprehensible to me. Your analogy to maths doesn't seem to hold up, because I am a mathematician, and still find this syntax unreadable. To me it is almost the definition of "visual noise", as it doesn't use words where maths would, and contains many extra redundant-seeming symbols maths would omit. I also really like Python's indentation syntax, so I have no objection to that in principle.

Take these three lines from the README example:

    |?| ^door <= 100
        ^door_open[door - 1] !^door_open[door - 1]
        ^door += pass;
Without the docs I don't have any idea what this does. Even with the docs it makes very little sense to me. |?| seems to start a loop, but it doesn't seem to be in the documentation (the closest thing is |??| for "while"). ^ means mutability, but is needed in the first line for some reason even though no mutation is happening? (Or is mutation happening? <= doesn't seem to have changed meaning though.) But then ^ is not needed on the mutable "door" in the second line? What does the second line do? It's not assignment, according to the docs, because that is a very un-mathematical prefix thing (but everything else, including other assignments like +=, is infix?). It seems to be just two expressions next to each other, and the docs don't seem to say that's meaningful. Why does the third line have a ;, but not the second? Is the second line somehow part of a statement finished on the third (which isn't indented)? Is the second line a ?-less condition affecting the third?

I am not being sarcastic or trolling. I genuinely don't understand this. The assumptions I'm aware of making are (1) the documentation covers everything new in the new syntax, and (2) the example in the README is valid code in the new syntax.

Serious question: do you understand these "much more readable" three lines?

> (I notice a few comments describing this syntax as less readable. My first thought was that these comments were sarcastic/trolling, but I've concluded that they're serious – but written by programmers who think very differently than I do!)

I think you have to take into account familiarity here. Rust uses "fn" for functions, which is really short (the same as := really), but it's easy to see how it's the short version of "function". On the other hand, := is assignment in some languages (usually Pascal descendants), and is used for "definition" in BDNF. That makes it hard to map it to "function".

This is a neat experiment, kind of making Rust look more like f# or haskel. I admit that Rust can get a little verbose, so I'm happy to see folks taking a fresh perspective towards it.

While I like `^` for marking mutability, other things might not need to be so abstract (`=:` instead of `fn`) or re-purposed, like using `|i|` for loops instead of a Rust closure.

> I admit that Rust can get a little verbose

Heh. As a self-taught developer with a dumb ape brain, if anything I wish it was more consistently verbose. I've read through the Rust book and tutorials multiple times, and I've still not been able to wrap my brain around how lifetimes work. Personally I think the incomprehensible <a'> syntax has a lot to do with that. I'd love to see a kind of pseudocode that compiles to rust simply for the purpose of making it easier to learn the real language.

Maybe you're thinking "wait, lifetimes are easy, you just..." but I think the reality is probably that most people who want to learn programming on their own, don't have a job in the field, and have never had a compsci class are going to have trouble wrapping their heads around the concepts. I can write good Python code. I can write safe C code (because it's easy to understand explicit allocation and freeing of memory), but I've never been able to make lifetimes work in my head. The complexity of rustc's error messages doesn't help much either, last time I tried it.

I don't see how different syntax can have any impact on the understandability of lifetime. Imagine every usage of `'` will be replaced with a `lifetime` keyword, `<'a>` becomes `<lifetime a>`, `&'a b` becomes `&lifetime a b`, I'd argue it would make the situation even worse.

Lifetime in Rust is actually an explicit generic type system over lifetime is a new concept (you would not see it anywhere else) so it will indeed feel alien as you can't map it to other construct that you have already learned in other language.

So to learn it you must treat it as such: a new concept, learn it by remember how you first learned recursive (yes, Rust's lifetime is not harder than recursive), pointers, ... Don't expect it to be easy or familiar because you already know how to code in other languages. I see many people struggle with learning Rust just because of this weird expectation that new language should be *very* similar and they do not have to spend much effort to understand.

Part of the issue is just that Rust is actually very good at working out lifetimes in certain situations, so they become implicit. This makes it even harder for the inexperienced to know when and why one has to specify a lifetime, as well as what the lifetime ought to be.

The issue isn't so much that you can't map lifetimes onto a concept from another language so much as it is that you can't map the concept onto anything you already know at all. If you know what a function is (i.e. you've taken an algebra class required of all U.S. students), you can work out for yourself in 10 minutes how a recursive function works. It may be surprising at first that a recursive function can return a value (instead of just calling itself forever), but by examining a simple one (like nth Fibonacci) any student with an understanding of algebra can determine that it does.

Incidentally, most high school students who progress as far as calculus meet with recursive functions like the following (though usually not expressed with this notation):

    f(x) = x/2 + f(x/2)
I'd say the concept of a function is itself something new and extremely difficult (I remember finding the syntactic ambiguity between function notation and multiplication very confusing), but fortunately most students encounter it well before they ever try programming.

> Part of the issue is just that Rust is actually very good at working out lifetimes in certain situations, so they become implicit. This makes it even harder for the inexperienced to know when and why one has to specify a lifetime, as well as what the lifetime ought to be.

What you refer to called lifetime elision, and there's an exact rule set [1] for that, so you will know exactly when lifetime are required, but you can always explicitly specify lifetime for every reference. About the why: lifetime is a tool for you to give a constraint to your function regarding lifetime of reference (and reference-based struct) of your input and output. My favorite example for introducing lifetime is:

    fn take_until<'a>(source: &'a str, token: &'_ str) -> &'a str {
        // take str from source until hit token
means that the output can not live longer than the first input, and it doesn't have to care about the lifetime of the second input.

My mention regarding recursive is more about programming and related concepts (terminal conditions, tail recursion, avoid infinite recursion...) than math recursive. For math, I was so proud of myself when I first understood and solved a recursive induction proof.

[1] https://doc.rust-lang.org/nomicon/lifetime-elision.html#life...

If anything, lifetimes are one key part of Rust where I don't really see how a different syntax would help. The <'a> and &'a syntax is nicely self-contained, but the semantics can be obscure to novices.

Rust does get a little verbose, but this syntax proposal isn't doing much to simplify it either. It's mostly a lexical choice of symbols over reserved words, but the amount of separate symbols is IMHO a bit too high for comfort. (The choice of semantic indentation leads to additional drawbacks when copy/pasting code.)

Reserved words may be inelegant in principle (since they intrude on the namespace for identifiers), but at least they're easier to type on most keyboards. And Rust seems to be going for a rather minimalistic syntax already. It's certainly not as verbose as languages in the BASIC, Pascal or Ada lineage, let alone Cobol.

There was a pre-proccessor for Rust without curly brackets that I can't seem to find now... It actually looked very nice. The two biggest changes I would probably make are square brackets for generics, and in-band lifetimes everywhere.

If you remember the name of this I would love to see it.

The src directory is empty. Is there no implementation, this is just a specification?

Correct. I'm not even sure if it is worth implementing, but the idea is there.

What's sorely lacking is a comparison of identical Rust and Analog pieces of code, side by side

It is so frustrating seeing someone work so cleverly to create a new syntax but restricting themselves to ASCII; I know the reasons why of course.

The semantic meaning of the 32 ASCII non-whitespace punctuation characters are so tragically overloaded between languages (with subtle, major, or even contradicting meanings).

The difference in meanings of ASCII characters usually varies within a language or requires “digraphs” like := or “trigraphs” like <=> and I laugh at our ancestors* using 6-bit* trigraphs (then I feel shamed that we have only progressed to 7 bit trigraphs).

* poetic facts + punctuation.

PPS: Any recommendations for a reliable swype keyboard for iPad that also accesses common Unicode characters such as dagger? I find Gboard on Android to be far superior to the Apple virtual keyboard.

Where is the source code or release?

For those reading, this appears to be vaporware. The idea is in the readme, but it's not been built yet.

Also, OP, you cannot post your own stuff to HN without "Show HN:" at the beginning of your title.

Show HN has its own rules and you don't have to use it to show hn your own work.


I don't believe this is correct

> Off topic: blog posts, sign-up pages, newsletters, lists, and other reading material. Those can't be tried out, so can't be Show HNs. Make a regular submission instead.


Judging by your first sentence it is correct for it to not be a show hn.

See? Rust's original syntax isn't that bad!

Code is read far more often than it is written. Optimize for readability.

Oh cool, let's make a (mostly) ergonomic language and throw it away for the sake of a few keystrokes.

Scope is a lie anyway, right!?

I'm glad to see they didn't start implementing anything.

"Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something."

"Don't be snarky."


Honestly, where should I post this?

This is gross

"Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something."


Readability note: 0

"Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something."


Dangling parens? This is terrible.

"Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something."


You should try working with APL family of languages, dangling parens likely won't bother you after that.

The biggest downside to Python's popularity is the uber-fans' tendency to think it's a Good Idea to wreck other languages with significant whitespace. Like CoffeeScript.

So far I believe every such attempt has ultimately failed. Like CoffeeScript.

This seems to be a particularly execrable example of that phenomenon.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact