proj-oot-ootSyntaxNotes10

" shadowofneptune 1 day ago

parent prev next [–]

The same information can be communicated in different ways, trading one form of noise for another. I have a personal preference for Pascal-like or PL/I syntax. Instead of int *char x or int&& x, there's x: byte ptr ptr. It's more to type and read, sure, but sometimes having an english-like keyword really helps clarify what's going on.

...

(someone else replying) Ideally, the language should have enough syntax-bending facilities so that you can still simulate what you want, this is mostly just operator overloading and not treating custom types like second class citizens. For example, your example of byte ptr ptr can be easily done in C++ by a bytePtrPtr struct, or even better, a Ptr<Ptr<Byte>> instantiated class from the template Ptr<T> for any T. Overloading the dereference and conversion operators will completely hide any trace of the fact it's not a built in type, and compiler optimization and inlinning will (hopefully, fingers crossed) ensure that no extra overhead is being introduced by the abstraction.

As for the 'byte ptr ptr' syntax specifically, in F# generic instantiation can be done by whitespace concatenation of type names in reversed C++/Java/C# order, so the above C++ type would (if translated to F# somehow) literally be written out as you want it to be, so even what seems like it would require language support (whitespace between related identifiers, generally a tricky thing in PL design) can actually be accomplished with clever and free minded syntax. "

---

hawski 1 day ago

parent prev next [–]

I find that Rust tends to have code that goes sideways more than downward. I prefer the latter and most C code bases, that I find elegant are like that.

It is like that, because of all the chaining that one can do. It is also just a feeling.

reply

est31 1 day ago

root parent next [–]

There are two upcoming features, let chains and let else, to counter the sideways drift.

Sometimes it's also the formatter though that outputs intensely sideways drifting code: https://github.com/rust-lang/rust/blob/1.60.0/compiler/rustc...

reply

kzrdude 1 day ago

root parent prev next [–]

I think it's because of the expression focus. Isn't it easier to make the code flow like a waterfall when it's imperative, but is harder to reason about values and state.

reply

---

... I find that Rust tends to have code that goes sideways more than downward. I prefer the latter and most C code bases, that I find elegant are like that. ... aaron_m04 1 day ago

root parent prev next [–]

I have noticed this tendency as well.

To counteract it, I write exit-early code like this:

    let foo_result = foo();
    if let Err(e) = foo_result {
        return Bar::Fail(e);
    }
    let foo_result = foo_result.unwrap();
    ...

reply

mathstuf 1 day ago

root parent next [–]

Any reason why this wasn't preferred?

    let foo_result = foo()
        .map_err(Bar::Fail)?;

reply

veber-alex 1 day ago

root parent next [–]

Bar::Fail is not wrapped in a Result type, so you can't use '?' with it (on stable at least).

You can write it like this:

  let foo_result = match foo() {
    Ok(v) => v,
    Err(e) => return Bar::Fail(e)
  };

reply

loeg 1 day ago

root parent next [–]

The result type is the return from Foo -- Bar::Fail does not need to wrap Result. Foo is Result<T, E> and map_err() would convert it to Result<T, Bar::Fail>. I think GP's `map_err()?` is the most straightforward way of writing this idea (and it's generally speaking how I would suggest writing Rust code).

reply

veber-alex 1 day ago

root parent next [–]

GP's code will return Result<_, Bar>, the original code we are trying to fix just returns Bar.

reply

loeg 1 day ago

root parent next [–]

If you are writing code to handle Results, it’s going to be a lot less painful to just return Result.

reply

ntoskrnl 1 day ago

root parent prev next [–]

I do this too, with a different spin on it:

  let foo = match foo() {
      Ok(foo) => foo,
      Err(err) => return Err(err),
  };

I was typing it out so often I made editor aliases `lmo` and `lmr` for Option and Result

reply

veber-alex 1 day ago

root parent next [–]

let me introduce you to the famous '?' operator.

The code above can be written as:

  let foo = foo()?;

reply

ntoskrnl 1 day ago

root parent next [–]

LOL you're right! I just pasted the template here, but my defaults are mostly equivalent to plain old `?`. I don't use the match if `?` would work.

reply

gardaani 1 day ago

root parent prev next [–]

Early exit code would be easier to write if Rust supported guard let.

reply

veber-alex 1 day ago

root parent next [–]

its coming soon, already available on nightly.

   let Some(x) = foo() else { return 42 };

reply

inferiorhuman 1 day ago

root parent next [–]

I'd suggest that something like that is already achievable by having foo return an Option and combining it with unwrap_or.

reply

klodolph 1 day ago

root parent prev next [–]

Like this?

    if let Ok(x) = my_func() {
        // ...
    }

Or do you mean something else?

reply

metaltyphoon 1 day ago

root parent prev next [–]

Isn’t that a good general practice todo? Exit early

reply

icedchai 1 day ago

root parent next [–]

You'd be surprised. For every person that things exit early is good, you'll run into another that prefers a single exit. At worked at a C++ shop that preferred "single exit", and some methods with an ungodly amount of conditions just to make this possible. Ugh.

reply

boolemancer 22 hours ago

root parent next [–]

In my experience, a preference for single exit comes from C where you always need to make sure to clean up any resources, and an early exit is a great way to have to duplicate a bunch of cleanup logic or accidentally forget to clean things up.

Of course, that's what goto is actually good for.

reply

---

... Rust is a mostly-expression language (therefore, semicolons have meaning), while JavaScript?/Python/Go aren't.

The conventional example is conditional assignment to variable, which in Rust can be performed via if/else, which in JS/Python/Go can't (and require alternative syntax). ...

kibwen 1 day ago

root parent next [–]

Not the parent, but you can certainly have an expression-oriented language without explicit statement delimiters. In the context of Rust, having explicit delimiters works well. In a language more willing to trade off a little explicitness for a little convenience, some form of ASI would be nice. The lesson is just to not extrapolate Rust's decisions as being the best decision for every domain, while also keeping the inverse in mind. Case in point, I actually quite like exceptions... but in Rust, I prefer its explicit error values.

reply

steveklabnik 1 day ago

root parent next [–]

Ruby is a great example of a language that’s expression oriented, where terminators aren’t the norm, but optionally do exist.

reply

...

Some constructs are incompatible with optional semicolons, as semicolons change the expression semantics (I've given an example); comparison with languages that don't support such constructs is an apple-to-oranges comparison.

An apple-to-apple comparison is probably with Ruby, which does have optional semicolons and is also expression oriented at the same time. In the if/else specific case, it solves the problem by introducing inconsistency, in the empty statement, making it semantically ambiguous.

reply

---

bobbylarrybobby 1 day ago

root parent prev next [–]

In practice though, getting the AST from the text is a computational task in and of itself and the grammar affects the runtime of that. For instance, Rust's "turbofish" syntax, `f::<T>(args)`, is used to specify the generic type for f when calling it; this is instead of the perhaps more obvious `f<T>(args)`, which is what the definition looks like. Why the extra colons? Because parsing `f<T>(args)` in an expression position would require unbounded lookahead to determine the meaning of the left angle bracket -- is it the beginning of generics or less-than? Therefore, even though Rust could be modified to accept`f<T>(args)` as a valid syntax when calling the function, the language team decided to require the colons in order to improve worst case parser performance.

reply

colejohnson66 7 hours ago

root parent next [–]

How does C# manage to handle without the turbo fish syntax? What’s different in Rust?

reply

bobbylarrybobby 5 hours ago

root parent next [–]

It's not impossible to handle the ambiguity, it's just that you may have to look arbitrarily far ahead to resolve it. Perhaps C# simply does this. Or perhaps it limits expressions to 2^(large-ish number) bytes.

reply

xigoi 19 hours ago

root parent prev next [–]

This is why using the same character as a delimiter and operator is bad.

reply

---

tene 1 day ago

root parent prev next [–]

That's really cool that you think Rust syntax could be significantly improved. I'd really love to hear some details.

Here's the example from the post:

  Trying::to_read::<&'a heavy>(syntax, |like| { this. can_be( maddening ) }).map(|_| ())?;

How would you prefer to write this?

reply

chmln 1 day ago

root parent next [–]

That whole example feels like a strawman, from my (maybe limited) experience something that's rather the exception than the norm.

First, lifetimes are elided in most cases.

Second, the curly braces for the closure are not needed and rustfmt gets rid of them.

Finally, the "map" on result can be replaced with a return statement below.

So, in the end we get something like:

  Trying::to_read(syntax, |like| this.can_be(maddening))?; 
  Ok(())

reply

---

codebje 1 day ago

parent prev next [–]

> That said, I've mostly reached the conclusion that much of this is unavoidable. Systems languages need to have lots of detail you just don't need in higher level languages like Haskell or Python, …

I am not convinced that there's so more to Rust than there is to GHC Haskell to justify so much dense syntax.

There's many syntax choices made in Rust based, I assume, on its aim to appeal to C/C++ developers that add a lot of syntactic noise - parentheses and angle brackets for function and type application, double colons for namespace separation, curly braces for block delineation, etc. There are more syntax choices made to avoid being too strange, like the tons of syntax added to avoid higher kinded types in general and monads in particular (Result<> and ()?, async, "builder" APIs, etc).

Rewriting the example with more haskell-like syntax:

    Trying::to_read::<&'a heavy>(syntax, |like| { this. can_be( maddening ) }).map(|_| ())?;
    Trying.to_read @('a heavy) syntax (\like -> can_be this maddening) >> pure ()

It's a tortuous example in either language, but it still serves to show how Rust has made explicit choices that lead to denser syntax.

Making a more Haskell-like syntax perhaps would have hampered adoption of Rust by the C/C++ crowd, though, so maybe not much could have been done about it without costing Rust a lot of adoption by people used to throwing symbols throughout their code.

(And I find it a funny place to be saying _Haskell_ is less dense than another language given how Haskell rapidly turns into operator soup, particularly when using optics).

skavi 1 day ago

root parent next [–]

> In more plain terms, the line above does something like invoke a method called “to_read” on the object (actually `struct`) “Trying”...

In fact, this invokes an associated function, `to_read`, implemented for the `Trying` type. If `Trying` was an instance `Trying.to_read...` would be correct (though instances are typically snake_cased in Rust).

I'll rewrite the line, assuming `syntax` is the self parameter:

    syntax
        .to_read::<&'a heavy>(|like| this.can_be(maddening))
        .map(|_| ())?;

In my opinion, this is honestly not bad.

reply

zozbot234 19 hours ago

root parent prev next [–]

> like the tons of syntax added to avoid higher kinded types in general and monads in particular (Result<> and ()?, async, "builder" APIs, etc).

Rust is not "avoiding" HKT in any real sense. The feature is being worked on, but there are interactions with lifetime checking that might make, e.g. a monad abstraction less generally useful compared to Haskell.

reply

---

flohofwoe 1 day ago

root parent next [–]

This just plasters over the underlying problem, which in case of Rust is IMO that features that should go into the language as syntax sugar instead are implemented as generic types in the standard library (exact same problem of why modern C++ source code looks so messy). This is of course my subjective opinion, but I find Zig's syntax sugar for optional values and error handling a lot nicer than Rust's implementation of the same concepts. The difference is (mostly): language feature versus stdlib feature.

reply

vlovich123 1 day ago

root parent prev next [–]

I’m not familiar with zig. Can you give some examples to illustrate your point?

reply

flohofwoe 1 day ago

root parent next [–]

An optional is just a '?' before the type:

For instance a function which returns an optional pointer to a 'Bla':

    fn make_bla() ?*Bla {
        // this would either return a valid *Bla, or null
    }

A null pointer can't be used accidentally, it must be unwrapped first, and in Zig this is implemented as language syntax, for instance you can unwrap with an if:

    if (make_bla()) |bla| {
        // bla is now the unwrapped valid pointer
    } else {
        // make_bla() returned null
    }

...or with an orelse:

    const bla = make_bla() orelse { return error.InvalidBla };

...or if you know for sure that bla should be valid, and otherwise want a panic:

    const bla = make_bla().?;

...error handling with error unions has similar syntax sugar.

It's probably not perfect, but I feel that for real-world code, working with optionals and errors in Zig leads to more readable code on average than Rust, while providing the same set of features.

reply

veber-alex 1 day ago

root parent next [–]

I don't see how that is all that different from Rust.

The main difference I see is that in Rust it will also work with your own custom types, not just optional.

  fn make_bla() -> Option<Bla> {
    // this either returns a valid Bla, or None
  }
  if let Some(bla) = make_bla() {
    // bla is now the unwrapped valid type
  } else {
    // make_bla() returned None
  }

..or with the '?' operator (early return)

  let bla = make_bla().ok_or(InvalidBla)?;

..or with let_else (nightly only but should be stable Soon(tm))

  let Some(bla) = make_bla() else { return Err(InvalidBla) }

..or panic on None

  let bla = make_bla().unwrap();

reply

wtetzner 1 day ago

root parent prev next [–]

How does Zig represent Option<Option<i32>>? Would it be something like this?

    ??i32

reply

flohofwoe 22 hours ago

root parent next [–]

I had to try it out first, but yep, that's how it works:

    const assert = @import("std").debug.assert;
    fn get12() i32 {
        const opt_opt_val: ??i32 = 12;
        const val = opt_opt_val.?.?;
        comptime assert(val == 12);
        return val;
    }

[1]

reply

---

 >when I revisit old mostly forgoten code, I love that boilerplate. I rarely have to do any puzzling about how to infer what from the current file, it's just all right there for me.

This is going to sound absurd, but the only other language I had this experience with was Objective-C.

Verbosity is super underrated in programming. When I need to come back to something long after the fact, yes, please give me every bit of information necessary to understand it.

-- [2]

---

carlmr 2 days ago

root parent prev next [–]

That's true, I found this writing F# with an IDE vs reading F# in a PR without IDE it really becomes easier to read if you at least have the types on the function boundary.

F# can infer almost everything. It's easier to read when you do document some of the types though.

reply

eropple 1 day ago

root parent next [–]

> F# can infer almost everything. It's easier to read when you do document some of the types though.

F# is also easier to avoid breaking in materially useful ways if (like TypeScript?) you annotate return types even if they can be inferred. You'll get a more useful error message saying "hey stupid, you broke this here" instead of a type error on consumption.

reply

---

bilkow 2 days ago

prev next [–]

It looks like I'm on the minority here, but I generally like Rust's syntax and think it's pretty readable.

Of course, when you use generics, lifetimes, closures, etc, all on the same line it can become hard to read. But on my experience on "high level" application code, it isn't usually like that. The hardest thing to grep at first for me, coming from python, was the :: for navigating namespaces/modules.

I also find functional style a lot easier to read than Python, because of chaining (dot notation) and the closure syntax.

Python:

    array = [1, 0, 2, 3]
    new_array = map(
        lambda x: x * 2,
        filter(
            lambda x: x != 0,
            array
        )
    )

Rust:

    let array = [1, 0, 2, 3];
    let new_vec: Vec<_> = array.into_iter()
        .filter(|&x| x != 0)
        .map(|x| x * 2)
        .collect();

I mean, I kind of agree to the criticism, specially when it comes to macros and lifetimes, but I also feel like that's more applicable for low level code or code that uses lots of features that just aren't available in e.g. C, Python or Go.

Edit: Collected iterator into Vec

reply

klodolph 2 days ago

parent next [–]

There are people who write Python code like that, but it's an extreme minority. Here's the more likely way:

    array = [1, 0, 2, 3]
    new_array = [x * 2 for x in array
                 if x != 0]

Just as a matter of style, few Python programmers will use lambda outside something like this:

    array = [...]
    arry.sort(key=lambda ...)

reply

foolfoolz 1 day ago

root parent next [–]

i have always felt the backwards nature of list comprehensions makes them very hard to read

reply

zanellato19 1 day ago

root parent next [–]

me too. Its one of the things that I kinda dislike in Python.

reply

 bilkow 2 days ago | root | parent | prev | next [–]

I guess you're right, list/generator comprehensions are the idiomatic way to filter and map in python, with the caveat of needing to have it all in a single expression (the same goes for lambda, actually).

I still feel like chained methods are easier to read/understand, but list comprehensions aren't that bad.

reply

dralley 2 days ago

root parent next [–]

Even in Rust I don't like chains that go beyond ~4 operations. At some point it becomes clearer when expressed as a loop.

reply

Iwan-Zotow 2 days ago

root parent prev next [–]

> with the caveat of needing to have it all in a single expression (the same goes for lambda, actually).

one could use multiple expressions in lambda in (modern) Python

reply

vgel 2 days ago

root parent next [–]

Do you mean using the walrus operator? Because unless I missed a recent PEP, I don't know of a way to do this without something hacky like that.

reply

Iwan-Zotow 1 day ago

root parent next [–]

yes

x = 1 y = 2

q = list(map(lambda t: ( tx := tx, ty := ty, tx+ty )[-1], [1, 2, 3]))

print(q)

reply

nemothekid 2 days ago

parent prev next [–]

1. I don't think your Python example if fair. I think

    new_array = [x*2 for x in array if x != 0]

is much more common.

2. In your example, `new_array` is an iterator; if you need to transform that into an actual container, your rust code becomes:

   let new_array = array.into_iter()
        .filter(|&x| x != 0)
        .map(|x| x * 2)
        .collect::<Vec<_>>();

And there your generic types rear their ugly head, compared to the one liner in python.

reply

bilkow 2 days ago

root parent next [–]

Oh, yeah, you're right! If you want to collect into a Vec you may need to specify the type, but usually, you can just call `.collect()` and the compiler will infer the correct type (as I suppose you're collecting it to use or return).

If it can't infer, it's idiomatic to just give it a hint (no need for turbofish):

    let new_vec: Vec<_> = array.into_iter()
        .filter(|&x| x != 0)
        .map(|x| x * 2)
        .collect();

I don't think that's ugly or unreadable.

About the Python list comprehension, I answered your sibling, I think you're both right but it also does have it's limitations and that may be personal, but I find chained methods easier to read/understand.

reply

veber-alex 2 days ago

root parent prev next [–]

They maybe rear their ugly head but they also allow you to collect the iterator into any collection written by you, by the standard library or by any other crate.

While in python you have list/dict/set/generator comprehension and that's it.

reply

nemothekid 2 days ago

root parent next [–]

I don't think it's bad thing. In fact one of my favorite features is that you can do `.collect::<Result<Vec<_>, _>>()` to turn an interators of Results, into a Result of just the Vec if all items succeed or the first error. That is a feature you just can't express in Python.

But you have to admit that is a pretty noisy line that could be difficult to parse.

 rajman187 1 day ago | parent | prev | next [–]

minor point but your python code creates a generator here not an array, you'd have to wrap it in a `list()` to get the same data type and be able to for example assert its length (of course you can just iterate over the generator)

reply

---

 robonerd 2 days ago | root | parent | prev | next [–]

> Back when I wrote C and C++ for a living I'd occasionally meet someone who thought their ability to employ the spiral rule or parse a particularly dense template construct meant they were a genius. I get the same vibe from certain other groups in this industry, most recently from functional programmers and Rust afficionados

Perl one-liner guys used to exemplify this. But I don't really agree that functional programmers do, except for Haskell and people who use lots of the car and cdr compositions, or those who use too much metaprogramming, or... okay maybe you're right. But at least the fundamental premise of functional programming is simple..

reply

---

titzer 2 days ago

parent prev next [–]

I find Rust code hard to read...to the point where I don't feel motivated to learn it anymore. Line noise is confusing and a distraction. Random syntactic "innovations" I find are just friction in picking up a language.

For example, in the first versions of Virgil I introduced new keywords for declaring fields: "field", "method" and then "local". There was a different syntax for switch statements, a slightly different syntax for array accesses. Then I looked at the code I was writing and realized that the different keywords didn't add anything, the array subscripting syntax was just a bother; in fact, all my "innovations" just took things away and made it harder to learn.

For better or for worse, the world is starting to converge on something that looks like an amalgam of Java, JavaScript?, and Scala. At least IMHO; that's kind of what Virgil has started to look like, heh :)

reply

---

    19
    kornel edited 2 days ago | link | flag | 

Because I don’t think that’s the fault of the syntax. Huge part of criticism is expectations/preferences and lack of understanding of the trade-offs that made it the way it is. When Rust is different than whatever other language someone is used to, they compare familiar with unfamiliar (see Stroustrup’s Rule). But it’s like saying the Korean alphabet is unreadable, because you can’t read any of it.

People who don’t like Rust’s syntax usually can’t propose anything better than a bikeshed-level tweak that has other downsides that someone else would equally strongly dislike.

For example, <> for generics is an eyesore. But if Rust used [] for generics, it’d make array syntax either ambiguous (objectively a big problem) or seem pointlessly weird to anyone used to C-family languages. Whatever else you pick is either ambiguous, clashes with meaning in other languages, or isn’t available in all keyboard layouts.

The closure syntax

expr may seem like line noise, but in practice it’s important for closures to be easy to write and make it easy to focus on their body. JS went from function { return expr } to () => expr. Double arrow closures aren’t objectively better, and JS users criticize them too. A real serious failure of Rust regarding closures is that they have lifetime elision rules surprisingly different than standalone functions, and that is a problem deeper than the syntax.

Rust initially didn’t have the ? shortcut for if err != nil { return nil, err } pattern, and it had a problem of a low signal-to-noise ratio. Rust then tried removing boilerplate with a try!() macro, but it worked poorly with chains of fallible function calls (you’d have a line starting with try!(try!(try!(… and then have to figure out where each of them have the other paren). Syntax has lots of trade-offs, and even if the current one isn’t ideal in all aspects, it doesn’t mean alternatives would be better.

And there are lots of things that Rust got right about the syntax. if doesn’t have a “goto fail” problem. Function definitions are greppable. Syntax of nested types is easy to follow, especially compared to C’s “spiral rule” types.

---

" Integer Types and Casting

Because we’re writing a compiler, we deal with a lot of integer math, and we essentially need to use every integer type that Rust provides: both signed and unsigned values, 8, 16, 32, and 64 bits wide. We also frequently need to perform operations involving integers of different types. Unlike C, Rust won’t automatically promote integer types to wider types. It forces you to manually cast any mismatching integer types for every operation. It also forces you to use the usize type (akin to C’s size_t) wherever you need to index into an array or slice.

It seems to me that the way Rust handles integer casting leaves something to be desired, and I know I’m not the only one who feels this way as there have been discussions pertaining to these issues dating up to several years back. It can be frustrating for programmers, because a priori, there's no reason why you couldn’t safely promote a u8, u16, or u32 into a usize, and asking programmers to manually cast integers everywhere makes the code more noisy and verbose.

In my opinion, Rust’s insistence on manual casting everywhere encourages people to write inefficient code, because writing verbose code feels uncomfortable and adds friction. You can make your code less verbose by reducing the number of integer casts, and you can reduce the number of casts by using the widest integer types possible everywhere. If you do that, your code will superficially look nicer, but it will also be less efficient.

In many cases, you can probably afford to use 64-bit integers instead of 8-bit integers. We’re talking about mere bytes of space savings, right? Well, maybe not. We care because JIT compilers can allocate tens or even hundreds of millions of objects, and the smaller these objects are, the better they fit into the data cache. Compactness matters for performance, and it will still matter for as long as processors have caches and limited memory bandwidth. By reducing the friction around integer casts, Rust could actually help programmers write more efficient code. " [3]

---

https://www.swiftbysundell.com/articles/swifts-new-shorthand-optional-unwrapping-syntax/

" if let animation { Perform updates ... }

"

---

Good languages with simple grammar

---

" We can get rid of the semicolons the same way Lua does. We can write function calls the ML/Haskell way, like f a b instead of f(a, b). (But then we need the semicolons back, or significant whitespace like Python. We can make the brackets of control structures mandatory, and the parentheses optional. That way we can write if cond { … } else { …}. We can make sure curly brackets are mostly indentation punctuation like in idiomatic C, or get rid of them almost entirely if we go the significant whitespace route. " -- https://lobste.rs/s/ay2bww/why_not_oberon#c_0s1fgp

---

" mananaysiempre 3 days ago

... In any case, there’s something to be said for defining a language in a series of layers: tokens, then trees, then perhaps elaboration / desugaring, then one or more layers of semantics (typing, binding, execution). In an actual frontend, you probably even want a looser tree syntax followed by a layer of “semantic” checks that could technically be folded into the syntax, in order to be able to tell the user things like “you can’t use an array type like that” rather than “unexpected bracket”. Compare Lisp s-expressions, the Dylan idea of skeleton syntax trees, and the class of visibly pushdown languages as used in one recent structural editor[1]. Contrast the refusal of the Glasgow Haskell Compiler to desugar before typechecking[2].
root parent next [–]

[1] https://news.ycombinator.com/item?id=31384528

[2] https://www.aosabook.org/en/ghc.html -- https://news.ycombinator.com/item?id=32823870

---

https://user-images.githubusercontent.com/1801526/189503047-0b0a4f0f-c5e7-42b2-a17d-37d80bef3970.png

from https://github.com/hsutter/cppfront

---

0z____ for hex constants in 'little endian' order, with most-significant-digits on the right, eg 0zA0 is decimal 10, not 160, and is the same as 0zA00. Decimal 160 is 0z0A (which is the same as 0z0A0).

So 0xA0 = 0z0A

---

kouteiheika 1 day ago

next [–]

I don't necessarily agree with the step of putting the code in a separate function; that often works, but just as often makes it so that the code can't be read top-to-bottom anymore which hurts readability.

In this case there's, I think, a better alternative; the equivalent-ish code in Ruby for the example code here would be something like this:

   values = s
      .partition('?')[-1]
      .split('&')
      .map { |key_value| key_value.partition('=')[-1] }

You can write these nice functional pipelines where you just read the code top-to-bottom and see step-by-step what is being done to the data on each line. You don't have to jump up-and-down around the code when reading it, and you don't have to keep too much context in your head when reading it.

This is one of the reasons why I vastly prefer Ruby over Python for most data processing tasks. I wish more languages would support this style of programming.

reply

---

https://gleam.run/news/v0.25-introducing-use-expressions/

---

i havent read these links yet:

rigoleto 56 minutes ago

parent next [–]

I wish OCaml had something like F#'s lightweight syntax: https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref... https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/verbose-syntax

reply

bitbckt 18 minutes ago

root parent next [–]

It's been tried https://people.csail.mit.edu/mikelin/ocaml+twt/

reply

runevault 43 minutes ago

root parent prev next [–]

I'd never seen the verbose syntax for f#, I thought you had to write it the whitespace dependent way. Huh.

reply

---

" I like the new arrow but I don’t like the

characters.

Suggest an alternative character that could be used to delimit the left side of the lambda.

Since we’re using the arrow to delimit the arguments from the return value, the second

is redundant, so remove it.

let add = :x, y -> x + y let sum = add(1, 2)

Here, we have removed the second : character after the arguments list, since it is not necessary for the syntax or functionality of the lambda. This makes the syntax a little simpler and easier to read.

This is interesting. I haven’t even considered this syntax, but it surprisingly works! Let’s keep going and add some more nontypical features. " -- https://judehunter.dev/blog/chatgpt-helped-me-design-a-brand-new-programming-language

---

amw-zero 11 hours ago

link flag

Effects are the obvious feature of Koka. But, it actually also has a lot of interesting syntactic features. It has universal call syntax, which it calls dot selection (i.e. f(a, b) is equivalent to a.f(b)). It has trailing closure syntax, similar to Ruby and Swift. You can even drop braces for trailing closures with indentation. Similarly, it supports a with statement which cleans up passing trailing closures as well. The example they use in the docs for that is:

with x <- list(1,10).foreach println(x)

which is equivalent to this in Ruby:

(1..10).each {

iputs i }

I think these features are interesting, and it makes it a very complete language vs. just a research language based on effects.

---

" And, finally,  $ doesn’t guarantee any interface at all – it just tells you (and the compiler) to think of the variable as a single entity (technical name: Scalar).

What does it mean to treat a variable as a single entity? Well, imagine I’ve got a grocery list with five ingredients on it. Saying that I’ve got one thing (a list) is true, but saying that I’ve got five things (the foods) is also true from a certain point of view. Using  $ versus  @ or  % expresses this difference in Raku. Thus, if I use  @grocery-list with a for loop, the body of the loop will be executed five times. But if I use $grocery-list, the loop will get executed just once (with the list as its argument). " -- https://raku-advent.blog/2022/12/20/sigils/

---

" The first of these perks is interpolation. In Raku, every sigiled variable is eligible for interpolation in the right sort of string. The exact details depend on the sigil and aren’t worth getting into here (mostly based on how the characters are typically used in strings – it’s kind of nice that “daniel@codesections.com” doesn’t interpolate by default). You can selectively enable/disable interpolation for specific sigials or temporarily enable interpolation in strings that normally don’t allow it with \qq[ ] (like JavaScript’s? ${ }). Between this, its Unicode support, and rationalized regex DSL system, I’m prepared to confidently claim that Raku’s text manipulation facilities significantly outdo any language I’ve tried. " -- https://raku-advent.blog/2022/12/20/sigils/

---

" The second perk is a bit of syntax sugar that only applies to  &-sigiled variables but that’s responsible for a fair bit of Raku’s distinctive look. We already said that you can invoke &-sigiled variables with syntax like &foo(). But due to sugar associated with &, you can also invoke them with by omitting both the & and the parentheses. Thus, in Raku code, you typically only see a  & when someone is doing something with a function other than calling it (such as passing it to a higher order function). I’ve previously blogged about how you can write Raku with extra parens to give it a syntax and semantics surprisingly close to lisp’s, so it’s only fair to point out that, thanks to this  & perk, it’s possible to write Raku with basically no parentheses at all. " -- https://raku-advent.blog/2022/12/20/sigils/

---

https://docs.raku.org/language/variables#index-entry-Twigil

---

" First, there’s the config keyword. If you write config var n=1, the compiler will automatically add a --n flag to the binary. As someone who 1) loves having configurable program, and 2) hates wrangling CLI libraries, a quick-and-dirty way to add single-variable flags seems like an obvious win.

Second, you can write the sequence 1, 2, … n-1 as 1..<n. It’s an elegant extension to the standard .. operator languages like Ruby and TLA+ use. "

---

"

kebab-case

As seen in most lisps. Instead of naming things two_words or TwoWords?, you can use the name two-words. It’s easier to write and easier to read. Of course the reason why nonlisps can’t do that is because they have infix minus, and it’s ambiguous whether x-y is the expression x minus y or the invocation of the x-y function. Seems like a bad tradeoff, though. How often do you use -, and how often do you write multiword functions? Just say that x-y is always a function, and if you want math you can use spaces like the rest of us.

(Granted this couldn’t be added to existing languages without breaking everything, but maybe worth considering if you’re making a new language?)

Symbols

Ruby has a special data type called a symbol, written like :this.1 A symbol compares equal to itself and has no other functionality. It replaces single-word strings— instead of writing dict["employee_id"], you write dict.

The advantage of having symbols is that it makes strings easier to work with. In most languages, strings are used to represent a lot of different things: tokens, text, structured data, code, etc. If you see the string “book”, it’s not clear without context whether it’s a dictionary key, or a text field with just “book” in it, or a trivial CSV, or what. With symbols, you can at least rule it out the former case, because then it’d instead be :book.

Dedicated testing syntax

As seen in D’s unit-test blocks on functions and P’s monitors. While it makes sense to keep the actual testing as library code, testing is so universal in larger software projects that it sounds nice to give it syntactic support. https://dlang.org/spec/unittest.html https://p-org.github.io/P/manual/monitors/ " -- [4]

"

7 calvin 10 hours ago

link flag

For examples of these not mentioned: Erlang has symbols called atoms, VB.NET has date literals (and XML literals).

    ~
    apg 6 hours ago | link | flag | 

Why stop there? In Common Lisp reader macros allow you to pretty much do whatever you want! Want a date literal? Cool. Want to mix JSON with sexpressions? No problem! Want literal regex? Yup, can do that too.

" -- [5]

"

~ roryokane edited 3 hours ago

link flag

Examples of symbols:

    As the article mentioned, Ruby has symbols: :example
    As you mentioned, Erlang has atoms: example
    Elixir has atoms: :example
    Clojure
        has keywords, which are generally used as keys within maps: :example
        has symbols, which generally refer to variables when metaprogramming: 'example
            Many other Lisps such as Scheme and Common Lisp have symbols and use the same syntax for them.

" -- [6]

" ~ ianbicking 5 hours ago

link flag

Yeah… it feels like single-line strings are fixing something that’s no longer a problem: strings accidentally being unclosed and it causing confusing errors. With syntax highlighting and halfway decent error messages it’s not that important.

BUT, the one case where I’d like a multi-line string syntax is something like:

def run_query(): sql = """ SELECT * FROM x """

Where I’d like that literal to actually resolve to "SELECT * FROM x" – dropping the leading and trailing empty line and any consistent leading whitespace (with a special case for internal empty lines).

    ~
    xiaq 4 hours ago | link | flag | 

Val has this: https://github.com/val-lang/specification/blob/main/spec.md#string-literals

And I’m quite sure to have seen other languages do this, but can’t recall now. " -- [7]

" ~ agent281 5 hours ago

link flag

If you like the Frink date literal, you may also like Elixir’s sigils. It’s used for date, time, regex and more. You can even create your own custom sigils. " -- [8]

agent281 10 days ago

link

If you like the Frink date literal, you may also like Elixir’s sigils. It’s used for date, time, regex and more. You can even create your own custom sigils.

    4
    doug-moen 9 days ago | link | 

Thanks. I like sigils better than Lisp reader macros, because you can parse a sigil using a context free grammar, without executing code from the module that defines the sigil. The ability to parse a program without executing it is valuable in a lot of contexts.


" 5 ianbicking 5 hours ago

link flag

Here’s some other microfeatures I like:

    In Python and others, f"{var=}" meaning f"var={var}"
    I really like JavaScript’s {key} being equivalent to {key: key} … it rewards consistent naming
    I like C#‘s (fairly new) slice notation of list[1..^1] which is equivalent to Python’s list[1:-1]. Using negative numbers to indicate counting from the end is cute but can lead to errors.
    I’m a little ambivalent about obj?.prop meaning something like obj && obj.prop … but it’s definitely a microfeature and I guess is useful.
    I also like C#‘s constructors like x = new X {prop1 = val, prop2 = val} which is kind of like x = new X(); x.prop1=val; x.prop2=val. There’s lots of different constructor patterns like this… I’m not sure which I like best, though I do find simplistic implementations like Python and JavaScript to be tedious." -- [9]

" .

~ ilyash 2 hours ago

Comments Section
link flag

In Next Generation Shell I’ve experimented by adding

section "arbitrary comment" { code here }

and this is staying in the language. It looks good. That’s instead of

  1. blah section - start code here
  2. blah section - end

Later, since NGS knows about sections, I can potentially add section info to stack traces (also maybe logging and debugging messages). At the moment, it’s just an aesthetic comments and (I think) easily skip-able code section when reading. Symbols

I’ve decided not to have symbols in NGS. My opinion (I assume not popular) is that all symbols together is one big enum, instead of having multiple enums which would convey which values are acceptable at each point.

" -- [10]

" ~ Corbin 9 hours ago

link flag

In Monte, we matured the max= syntax example; it happened to be a proposed syntactic extension for E, and we merely followed through with the proposal. We also expanded other augmented syntax. For example, this high-level syntax…

x += y

…would be equivalent to this less-sugared syntax:

x := x.add(y) " -- [11]

 ;)

~ icefox 18 hours ago (unread)

link flag

I would love symbols in more programming languages, but it’s a little tricky to implement in programming languages without a runti–

Oh, I just figured out how to implement them in something with C’s compilation and linking model. Each symbol is a global pointer to a string with its name. Each symbol also has a linker symbol with its name. Make the linker symbols exported and tell the linker to deduplicate and coalesce them, via Black Magic, and it will fix up all references to them automatically. Bingo, each symbol is represented by a unique integer that is a pointer to a unique string, and all symbols with the same string are interned.

Generating new symbols with names not previously mentioned in the program then still requires dynamic memory and some kind of global runtime, but you need to allocate memory to generate new symbols no matter what so the machinery that creates new symbols can be part of the same lib that provides your memory allocator.

ianbicking 35 hours ago

link flag

Yeah… it feels like single-line strings are fixing something that’s no longer a problem: strings accidentally being unclosed and it causing confusing errors. With syntax highlighting and halfway decent error messages it’s not that important.

BUT, the one case where I’d like a multi-line string syntax is something like:

def run_query(): sql = """ SELECT * FROM x """

Where I’d like that literal to actually resolve to "SELECT * FROM x" – dropping the leading and trailing empty line and any consistent leading whitespace (with a special case for internal empty lines).

    ~
    andyc 19 hours ago (unread) | link | flag | 

Oil has that!

https://www.oilshell.org/blog/2021/09/multiline.html#multi-line-string-literals-and-and

The indentation of the closing quote determines the leading whitespace that’s stripped.

And if there’s an initial newline it’s dropped. But the last newline isn’t dropped, which I think makes sense ? (You can leave it off if you want by putting it on the same line as the last line of text.)

oil$ var x = """ > hello > there > """

oil$ = x (Str) 'hello\nthere\n'

FWIW this pretty much comes from Julia, which has Python-like multi-line strings, except they strip leading whitespace

The multi-line strings are meant to supplant the 2 variants of here docs in shell.

There are unfortunately still 3 kinds multi-line strings to go with 3 kinds of shell strings. But I’m trying to reduce that even further:

https://lobste.rs/s/9ttq0x/matchertext_escape_route_from_language#c_vire9r

https://lobste.rs/s/9ttq0x/matchertext_escape_route_from_language#c_tlubcl

~ xiaq 33 hours ago

link flag

Val has this: https://github.com/val-lang/specification/blob/main/spec.md#string-literals

And I’m quite sure to have seen other languages do this, but can’t recall now.

    ~
    iv 28 hours ago (unread) | link | flag | 

Java’s new-ish text blocks do this too!

    ~
    xiaq 22 hours ago (unread) | link | flag | 

Aha, right. For anyone else curious it’s described in JEP 378: Text Blocks.

1 justinpombrio 7 days ago (unread)

link

Zig does that very well. You write:

let sql =
\SELECT *
\FROM x

and you get the string “SELECT * \nFROM x”. There’s no ambiguity about leading spaces. If you wanted a leading space before SELECT or FROM, you’d just put a space there.

I’m just confused why it uses
instead of the more obvious “””.

---

5 briankung 9 days ago

link

I haven’t actually used them, but KDL’s “slashdash” comments to knock out individual elements are pretty interesting. From The KDL Document Language:

    On top of that, KDL supports /- “slashdash” comments, which can be used to comment out individual nodes, arguments, or children:
    // This entire node and its children are all commented out.
    /-mynode "foo" key=1 {
     a
     b
     c
    }
    mynode /-"commented" "not commented" /-key="value" /-{
     a
     b
    }

It’s a little more clear with the syntax highlighting on the site.

    1
    roryokane edited 8 days ago | link | 

Clojure supports something similar with its #_ reader macro, which makes the reader ignore the next form. It’s pretty handy for debugging.

  1. _(println "don't print me") (+ 1 #_2 3) ; equals 4

Clojure also has a comment macro that ignores its body and evaluates to nil. I rarely use it.

    2
    hcs 8 days ago (unread) | link | 

Racket (and maybe other Scheme-family?) has this as well with S-expression comments: #;

Easy to remember since ; is a line comment.

---

---

https://elizarov.medium.com/types-are-moving-to-the-right-22c0ef31dd4a

https://lobste.rs/s/yymnmm/types_are_moving_right

---

1 emiller 7 days ago (unread)

link

Agreed, it makes sense for languages that are going to be used from the command-line. Nextflow handles these under params https://www.nextflow.io/docs/edge/config.html#scope-params in a pretty elegant way!

---

5 briankung 9 days ago

link

I haven’t actually used them, but KDL’s “slashdash” comments to knock out individual elements are pretty interesting. From The KDL Document Language:

    On top of that, KDL supports /- “slashdash” comments, which can be used to comment out individual nodes, arguments, or children:
    // This entire node and its children are all commented out.
    /-mynode "foo" key=1 {
     a
     b
     c
    }
    mynode /-"commented" "not commented" /-key="value" /-{
     a
     b
    }

It’s a little more clear with the syntax highlighting on the site.

    1
    roryokane edited 8 days ago | link | 

Clojure supports something similar with its #_ reader macro, which makes the reader ignore the next form. It’s pretty handy for debugging.

  1. _(println "don't print me") (+ 1 #_2 3) ; equals 4

Clojure also has a comment macro that ignores its body and evaluates to nil. I rarely use it.

    2
    hcs 8 days ago (unread) | link | 

Racket (and maybe other Scheme-family?) has this as well with S-expression comments: #;

Easy to remember since ; is a line comment.

---

msw 9 days ago

link
    I really like python’s “in between” operator. 10 <= x < 20
    Rust’s include_str! macro is super useful as well. Much nicer than Java’s overcomplicated getResourceAsStream() (which is still very nice). Having the ability to include data in an “executable” in a well defined way is great.

---

    ]

3 Corbin 10 days ago

link

In Monte, we matured the max= syntax example; it happened to be a proposed syntactic extension for E, and we merely followed through with the proposal. We also expanded other augmented syntax. For example, this high-level syntax…

x += y

…would be equivalent to this less-sugared syntax:

x := x.add(y)

2 kevinc 9 days ago

link

Optional chaining and defaulting operators are my pick.

1 emiller 7 days ago (unread)

link

Awesome read! I love the three classes of features.

I’ve got one to throw in the mix, Nextflow’s file(). It just handles remote and local files the same, so the end user can use anything from a file on an FTP server, to s3. It just works and stages the files locally.

---

        .

4 msw 9 days ago

link
    I really like python’s “in between” operator. 10 <= x < 20
    Rust’s include_str! macro is super useful as well. Much nicer than Java’s overcomplicated getResourceAsStream() (which is still very nice). Having the ability to include data in an “executable” in a well defined way is great.

-- [12]

---

https://tratt.net/laurie/blog/2023/why_we_need_to_know_lr_and_recursive_descent_parsing_techniques.html argues that you should design grammar as LR even if you implement it as recursive descent.

note: "Every LL(k) grammar is also an LR(k) grammar" -- [13]

---

 "
 ’s quasi-literal syntax

The E language is a veritable cornucopia of interesting programming language ideas. But to pick one that I really like it’s the quasi-literal syntax for safely constructing values in other languages: SQL, HTML, etc. There were several iterations of this idea in E, and the documented versions are older I believe. But the basic idea is that you have a generic syntax for constructing multi-line strings with embedded expressions. On the surface this is a familiar idea from many languages, but E has a nice twist on it. For example, given a code snippet like the following

def widgetName := “flibble” def widgetCount := 42 def sql := ``` INSERT INTO widgets(name, count) VALUES($widgetName, ${widgetCount + 1}) ``` db.exec(sql)

what gets constructed here is not a simple string, but rather some kind of Template object/record. That template object has two fields:

    A list of fragments of the literal string before and after each variable reference: “INSERT INTO … VALUES(”, “, ”, and “)” in this case.
    A list of the (evaluated) expression values. In this case, that is the value of the widgetName variable and the result of adding 1 to widgetCount.

The database exec() method then doesn’t take a String, but rather one of these Template objects and it applies appropriate processing based on the contents. In this case, by constructing a prepared statement, something like the following:

def exec(template) { def sql = template.fragments.join(“?”) def stmt = db.prepare(sql) stmt.bind(template.values) stmt.execute() }

Here the (completely made up) code replaces the originally expressions with SQL placeholders in a prepared statement: “INSERT INTO … VALUES(?, ?)”. It then binds those placeholders to the actual values of the expressions passed in the template. (The exact type of the “values” field is questionable: E was dynamically-typed. For now, let’s assume that all values get converted to strings, so “values” is a list of strings: in this example “flibble” and “43”).

This has the effect of allowing the easy use of prepared statements, while having a syntax that is as easy to use as string concatenation. Safety and convenience. The same syntax can be used to provide safe templating when outputting HTML, XML, JSON, whatever.

(E’s actual approach was somewhat different to how I’ve presented it, but I think this version is simpler to understand. E also allowed the same syntax to be used for parsing too, but I think that is less compelling and complicates the feature).

Edit: /u/kn4rf and /u/holo3146 on Reddit point out that this already exists in JavaScript? in the form of tagged template literals, and has been proposed for Java too in JEP-430. Very cool! " https://old.reddit.com/r/programming/comments/10fw1ac/a_few_programming_language_features_id_like_to_see/ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates https://openjdk.org/jeps/430

-- https://neilmadden.blog/2023/01/18/a-few-programming-language-features-id-like-to-see/

    .

~ idrougge 6 hours ago

link flag

The first point sounds a bit like Swift’s ExpressibleByStringInterpolation?. https://davedelong.com/blog/2021/03/04/exploiting-string-interpolation-for-fun-and-for-profit/

~ lorddimwit 6 hours ago

link flag

The quasi-literal syntax reminds me (just coincidentally) of the hoops I jumped through in dpdb to get different types of parameter interpolation strings …interpolated.

---

https://github.com/tc39/proposal-pipeline-operator

---

---

could begin blocks with IDENTIFIER COLON and end them with END IDENTIFIER, eg:

blockname: pp Do stuff inside the block end blockname

and could begin/end anonymous blocks with {}, eg:

{ pp Do stuff inside the block }

(as a formatting choice, single line blocks could be condensed to:

{pp Do stuff inside the block}

)

---

"We propose to change for loop variables declared with := from one-instance-per-loop to one-instance-per-iteration. " -- https://github.com/golang/go/issues/60078 https://go.googlesource.com/proposal/+/master/design/60078-loopvar.md

---

about the 'dt' language and its extensibilty:

" The language is somewhat hackable already, you could define & and plenty of single-symbol “commands” to do whatever you want! Although readability can suffer of course. My only control here is that I’ve intentionally made parsing dt code evaluate in a strict left-to-right order, and avoided some forth-isms like pushing/popping a return stack, or allowing a ' (tick) operator that can semantically access values “to the right” " -- https://lobste.rs/s/b8icdy/dt_duck_tape_for_your_unix_pipes#c_4vquvy "I think avoiding push/pop as a hard & fast rule was a great design decision, and I’ve always hated forward parsing in Forth." -- https://lobste.rs/s/b8icdy/dt_duck_tape_for_your_unix_pipes#c_0cxs7u

---

    ~
    andyc 10 hours ago (unread) | link | flag | 

Yeah, the most common style is -e 'single quoted program', and also you want -v NAME=value like awk for var substitution (sed is notably missing this)

This results in safer string substitution vs. the shell doing it with something like awk -e "x == $NAME". That leads to string injection problems (meant to write a blog post about that)